thorwhalen / umpyre

Materials for python coaching
MIT License
1 stars 0 forks source link

MongoValueViews and better wrapping... #38

Open thorwhalen opened 3 years ago

thorwhalen commented 3 years ago

Had the problem that the special values(), returning a MongoValuesView, wasn't being carried over when wrapped (by kv_wrap: https://github.com/i2mint/mongodol/blob/de97a7e36e591bbc9d82885b8c1626e2cd9e048a/mongodol/base.py#L90

To hack it through, did this: https://github.com/i2mint/py2store/commit/3dbdb04f590e701790154c14fa9808b3a0958f8f

Which worked:

from mongodol.tests.util import clear_all_and_populate, get_test_collection_persister, mk_dflt_mgc
s = get_test_collection_persister()
assert type(s.values()).__name__ == 'MongoValuesView'

from py2store import wrap_kvs

ss = wrap_kvs(s)
assert type(ss.values()).__name__ == 'MongoValuesView'

But...

image

Resolved by taking distinct out of MongoValuesView. It seems it's better that way, but it also sheds light on the limitations of the wrapping mechanism.

thorwhalen commented 3 years ago

Use factory methods to create the view classes instead (Gang of Four design patterns, not suprisingly, calls this the : Factory method pattern):

class BaseMapping:
    # factories for mapping view objects
    # that subclasses can replace
    KeysView = BaseKeysView
    ValuesView = BaseValuesView
    ItemsView = BaseItemsView

    def keys(self):
        # each of these methods use the factory method on self,
        # here that's self.KeysView(), and expect it to take specific arguments.
        return self.KeysView(self)

    def values(self):
        return self.ValuesView(self)

    def items(self):
        return self.ItemsView(self)

class SpecialKeysView(BaseMapping.KeysView):   # or SpecialKeysView(BaseKeysView)
    def extra_method(self):
        # ...

class SpecialMapping:
    KeysView = SpecialKeysView
    # ...

sm = SpecialMapping()
type(sm.keys())  # will be SpecialKeysView

If your subclassed factories need to accept extra arguments, you could use a partial(), or have the __init__ method pull in more information from the standard context that is passed in (in the above examples, BaseMapping.keys() passes in the mapping object, self, to each factory call).

thorwhalen commented 3 years ago

Tests in mongodol/tests/not_working.py:test_mongo_values_view_when_wrapping

thorwhalen commented 3 years ago

Attempt at the above (still not working):

from collections import Mapping
from py2store import KvReader
from collections import (
    KeysView as BaseKeysView,
    ValuesView as BaseValuesView,
    ItemsView as BaseItemsView,
)

class BaseMapping(dict):
    # factories for mapping view objects
    # that subclasses can replace
    KeysView = BaseKeysView
    ValuesView = BaseValuesView
    ItemsView = BaseItemsView

    def keys(self):
        # each of these methods use the factory method on self,
        # here that's self.KeysView(), and expect it to take specific arguments.
        return self.KeysView(self)

    def values(self):
        return self.ValuesView(self)

    def items(self):
        return self.ItemsView(self)

class SpecialValuesView(BaseKeysView):   # or SpecialKeysView(BaseKeysView)
    def distinct(self):
        return set(v for k, v in self._mapping.items())  # TODO: How to use super values() to get values?

class SpecialMapping(BaseMapping):
    KeysView = SpecialKeysView
    ValuesView = SpecialValuesView

from py2store import wrap_kvs

t = SpecialMapping(a=1, b=2, c=1)
assert str(type(sm.keys())) == "<class '__main__.SpecialKeysView'>"
t.values().distinct() == {1, 2}

# But...
tt = wrap_kvs(t)
assert str(type(tt.values())) == "<class 'collections.abc.ValuesView'>"  # TODO: Needs to be SpecialKeysView!!!

# And same for type wrapping:
TTT = wrap_kvs(SpecialMapping)
ttt = TTT(a=1, b=2, c=1)
assert str(type(ttt.values())) == "<class 'collections.abc.ValuesView'>"  # TODO: Needs to be SpecialKeysView!!!