mongodol.views#
Mapping views that keep working when a mongo store is wrapped by dol.
A mongo collection can serve a store’s whole (key, value) stream in a single
find round trip, so MongoCollectionReader implements a
bulk-read protocol – iter_values, iter_items, contains_value and
contains_item – and exposes it through the values()/items() views
defined here. One query instead of N is the whole point of these views.
The catch is composition. A dol Store wrapper (what
wrap_kvs builds) forwards every attribute it doesn’t define to the store it
wraps. A view that simply calls self._mapping.iter_values() therefore punches
straight through the wrappers and yields raw backend documents, silently skipping
the value transforms the user asked for – breaking the Mapping contract:
list(store.values()) == [store[k] for k in store]
(see i2mint/mongodol#7).
This module resolves the bulk stream explicitly instead of relying on
attribute delegation. Given the store a view was built on, bulk_values()
and bulk_items() walk the wrapper chain inward, remembering each layer they
cross, until they reach a store that actually implements the bulk-read protocol.
The bulk stream is then re-transformed by the crossed layers, innermost first, so
that it lands in exactly the same space as store[k].
A layer may only be crossed if its read path is plain transform composition –
“read from the inner store, then apply _key_of_id/_obj_of_data”, which is
what Store does. A layer that redefines __getitem__ or
__iter__ (wrap_kvs(postget=...), filt_iter, cached_keys, …)
changes values or key sets in ways that cannot be pushed onto a bulk stream, so
the resolver refuses to guess: it raises NoBulkReadPath and the views
fall back to the generic per-key behaviour. That fallback is correct, just one
round trip per key – correctness first, efficiency when it is provable.
Simple use is invisible: build a mongo store, wrap it however you like, and
values()/items() agree with __getitem__. The knobs, for store authors:
Implement the bulk-read methods to provide the fast path.
Set the
BULK_READ_IS_FAITHFUL_ATTRclass attribute toFalse(seedisable_bulk_read()) when a class inherits bulk-read methods that no longer agree with its own__getitem__.
Known limitation. MongoCollectionReader is deliberately a
cursor-level store: s[k] is a pymongo Cursor, while its bulk stream
already yields documents – one per key. The two only line up once a single-doc
layer (MongoCollectionFirstDocReader and friends) has turned cursors into
docs, which is why those are the stores you are meant to wrap. Hanging an
obj_of_data that expects a cursor directly off the raw reader is outside the
protocol: such a transform cannot be pushed onto a doc-level bulk stream, and is
not detectable from here.
Nothing here is mongo-specific; it is a general answer to “how does a store with
a bulk-read fast path compose with dol wrappers?”, and would be a reasonable
thing for dol itself to own one day.
Module Attributes
Bulk-read method yielding a store's values in one backend round trip. |
|
Bulk-read method yielding a store's |
|
Bulk-read method answering "is this value in the store?" in one backend round trip. |
|
Bulk-read method answering "is this item in the store?" in one backend round trip. |
|
Class attribute through which a store declares whether its bulk-read methods are value-equivalent to its own |
|
The |
Functions
|
Ask the backend whether |
|
Ask the backend whether |
|
Iterate |
|
Iterate |
|
Class decorator declaring that inherited bulk-read methods are not to be trusted. |
|
Whether |
|
Whether |
|
Find the store providing bulk-read |
|
Yield |
Classes
|
An |
|
A |
Exceptions
No bulk-read stream can be proven equivalent to the store's per-key reads. |
- mongodol.views.BULK_READ_IS_FAITHFUL_ATTR = '_bulk_read_is_faithful'#
Class attribute through which a store declares whether its bulk-read methods are value-equivalent to its own
__getitem__. It defaults toTrue(a class that implements the protocol is trusted to implement it faithfully). It exists becausedol’s class-decorator wrapping copies the wrapped class’s extra methods onto the wrapper, so a wrapper that redefines value semantics –wrap_kvs(postget=...)– silently inherits bulk-read methods that no longer match it. Such a class sets this toFalse; seedisable_bulk_read().
- mongodol.views.CONTAINS_ITEM_METHOD = 'contains_item'#
Bulk-read method answering “is this item in the store?” in one backend round trip.
- mongodol.views.CONTAINS_VALUE_METHOD = 'contains_value'#
Bulk-read method answering “is this value in the store?” in one backend round trip.
- mongodol.views.INNER_STORE_ATTR = 'store'#
The
dolStoreattribute holding the store a wrapper wraps.
- mongodol.views.ITER_ITEMS_METHOD = 'iter_items'#
Bulk-read method yielding a store’s
(key, value)pairs in one backend round trip.
- mongodol.views.ITER_VALUES_METHOD = 'iter_values'#
Bulk-read method yielding a store’s values in one backend round trip.
- class mongodol.views.MongoItemsView(mapping)[source]#
Bases:
ItemsViewAn
items()view that uses the backend’s bulk read when – and only when – that stream provably equals((k, store[k]) for k in store).
- class mongodol.views.MongoValuesView(mapping)[source]#
Bases:
ValuesViewA
values()view that uses the backend’s bulk read when – and only when – that stream provably equals(store[k] for k in store).
- exception mongodol.views.NoBulkReadPath[source]#
Bases:
ExceptionNo bulk-read stream can be proven equivalent to the store’s per-key reads.
Raised by the resolvers of this module, and caught by the views, which then fall back to the generic (correct, one-round-trip-per-key)
Mappingbehaviour. It is a control-flow signal, not a user-facing error.
- mongodol.views.bulk_contains_item(store, item)[source]#
Ask the backend whether
itemis one ofstore’s items, in one round trip.- Raises:
NoBulkReadPath – when
itemcannot be pushed down to backend space.- Return type:
- mongodol.views.bulk_contains_value(store, v)[source]#
Ask the backend whether
vis one ofstore’s values, in one round trip.- Raises:
NoBulkReadPath – when
vcannot be pushed down to backend space.- Return type:
- mongodol.views.bulk_items(store)[source]#
Iterate
store’s(key, value)pairs via the backend’s bulk-read path.- Raises:
NoBulkReadPath – when the bulk stream cannot be proven equivalent to
((k, store[k]) for k in store).- Return type:
- mongodol.views.bulk_values(store)[source]#
Iterate
store’s values via the backend’s bulk-read path, transforms honoured.- Raises:
NoBulkReadPath – when the bulk stream cannot be proven equivalent to
(store[k] for k in store).- Return type:
- mongodol.views.disable_bulk_read(store_cls)[source]#
Class decorator declaring that inherited bulk-read methods are not to be trusted.
Use it on a class that changes what
__getitem__returns (typically viawrap_kvs(postget=...)) while inheriting – or being handed, bydol’s class-decorator wrapping – bulk-read methods written for the un-changed semantics. Views then take the correct per-key path instead.- Return type:
- mongodol.views.is_crossable(store)[source]#
Whether
storeis aStorelayer whose read path is plain transform composition.Such a layer reads from the store it wraps and applies
_key_of_idto keys and_obj_of_datato values – and nothing else. Those two transforms can be mapped over a bulk stream, so the layer can be “crossed” on the way to the backend’s fast path. A layer that redefines__getitem__(postget) or__iter__(key filtering/caching) cannot.- Return type:
- mongodol.views.provides_bulk_read(store, method_name)[source]#
Whether
store’s own class implements bulk-readmethod_name, faithfully.“Faithfully” means the store has not declared, via
BULK_READ_IS_FAITHFUL_ATTR, that its bulk-read methods disagree with its__getitem__.- Return type:
- mongodol.views.resolve_bulk_source(store, method_name)[source]#
Find the store providing bulk-read
method_name, and the layers crossed to reach it.- Returns:
(source, layers)wherelayersare the crossedStorewrappers, outermost first.- Raises:
NoBulkReadPath – if a layer that cannot be crossed is met before a provider is found.
- mongodol.views.store_layers(store)[source]#
Yield
storethen each store it wraps, outermost first, innermost last.The chain ends at the first non-
Store– the actual backend. Note thatdolis free to insert pass-throughStorelayers of its own, so never assume onewrap_kvscall means exactly one layer.- Return type:
>>> from dol import wrap_kvs >>> layers = list(store_layers(wrap_kvs({'a': 1}, obj_of_data=str))) >>> type(layers[0]).__name__, type(layers[-1]).__name__ ('Store', 'dict') >>> all(isinstance(x, Store) for x in layers[:-1]) True