Skip to content

Commit

Permalink
Add lazy_factories to indices.
Browse files Browse the repository at this point in the history
  • Loading branch information
riga committed Jul 15, 2024
1 parent ca9e5c0 commit ffb6bca
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 2 deletions.
29 changes: 27 additions & 2 deletions order/unique.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class UniqueObjectIndex(CopyMixin):
{"attr": "_cls", "ref": True},
]

def __init__(self, cls, objects=None):
def __init__(self, cls, objects=None, lazy_factories=None):
CopyMixin.__init__(self)

# set the cls using the typed parser
Expand All @@ -117,6 +117,11 @@ def __init__(self, cls, objects=None):
if objects is not None:
self.extend(objects)

# set lazy factory functions mapped to keys
self._lazy_factories = {}
if lazy_factories is not None:
self._lazy_factories.update(lazy_factories)

# save a dot access proxy for easy access of objects via name
self._n = DotAccessProxy(self.get)

Expand Down Expand Up @@ -178,6 +183,14 @@ def cls(self, cls):
def n(self):
return self._n

def add_lazy_factory(self, key, func):
"""
Adds a lazy factory function *func* to the :py:attr:`lazy_factories` for *key*. When
:py:meth:`get` is invoked with *key* and the object is not found, the factory function is
called instead to create a new object.
"""
self._lazy_factories[key] = func

def names(self):
"""
Returns the names of the contained objects in the index.
Expand Down Expand Up @@ -268,7 +281,9 @@ def get(self, obj, default=_no_default):
""" get(obj, default=no_default)
Returns an object that is stored in the index. *obj* might be a *name*, *id*, or an instance
of *cls*. If *default* is given, it is used as the default return value if no such object
could be found. Otherwise, an error is raised.
could be found. Otherwise, if *obj* refers to a known lazy factory function previously
registered with :py:meth:`add_lazy_factory`, the factory is called to create a new object
which is added to the index and returned.
"""
# when it's already an object, do the lookup by it's name
orig_obj = obj
Expand All @@ -283,6 +298,16 @@ def get(self, obj, default=_no_default):
if default != _no_default:
return default

# lazy factory?
if obj in self._lazy_factories:
_obj = self._lazy_factories[obj](self)
if not isinstance(_obj, self._cls):
raise TypeError(
"lazy factory function '{}' of {} produced object of wrong type: {}".format(
obj, self, _obj,
))
return self.add(_obj, overwrite=True)

raise ValueError("object '{}' not known to index '{}'".format(orig_obj, self))

def get_first(self, default=_no_default):
Expand Down
15 changes: 15 additions & 0 deletions tests/test_unique.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,21 @@ def test_add(self):
with self.assertRaises(DuplicateIdException):
idx.add("baz", 2)

def test_lazy_factory(self):
C, idx = self.make_index()
self.assertEqual(len(idx), 3)

self.assertFalse(idx.has("lazy"))

def factory(_):
return C("lazy", 4)

idx.add_lazy_factory("lazy", factory)

self.assertFalse(idx.has("lazy"))
self.assertIsInstance(idx.get("lazy"), C)
self.assertTrue(idx.has("lazy"))

def test_extend(self):
C, idx = self.make_index()
self.assertEqual(len(idx), 3)
Expand Down

0 comments on commit ffb6bca

Please sign in to comment.