Skip to content

Commit cd58440

Browse files
committed
Extend parent injector multibindings in child injectors
Allow child injectors to inherit and extend multibindings registered on their parent injectors for both sequence and mapping multibinds. Fixes #297 Signed-off-by: DYNOSuprovo <DYNOSuprovo@users.noreply.github.com>
1 parent d8f707d commit cd58440

3 files changed

Lines changed: 67 additions & 3 deletions

File tree

‎CHANGES‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Injector Change Log
55
------
66

77
- Dropped support for Python 3.8 and 3.9. Python 3.10+ is now required.
8+
- Child injectors now respect and extend parent injector multibindings.
89

910
0.24.0
1011
------

‎injector/__init__.py‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,11 @@ class MultiBinder(Provider, Generic[T]):
330330

331331
_multi_bindings: List['Binding']
332332

333-
def __init__(self, parent: 'Binder') -> None:
333+
def __init__(self, parent: 'Binder', interface: Optional[type] = None) -> None:
334334
self._multi_bindings = []
335335
self._binder = Binder(parent.injector, auto_bind=False, parent=parent)
336+
self._owner_binder = parent
337+
self._interface = interface
336338

337339
@abstractmethod
338340
def multibind(
@@ -348,6 +350,14 @@ def append(self, provider: Provider[T], scope: Type['Scope']) -> None:
348350
self._multi_bindings.append(Binding(pseudo_type, provider, scope))
349351

350352
def get_scoped_providers(self, injector: 'Injector') -> Generator[Provider[T], None, None]:
353+
if self._owner_binder.parent and self._interface is not None:
354+
try:
355+
parent_binding, _ = self._owner_binder.parent._get_binding(self._interface)
356+
if isinstance(parent_binding.provider, MultiBinder):
357+
yield from parent_binding.provider.get_scoped_providers(injector)
358+
except KeyError:
359+
pass
360+
351361
for binding in self._multi_bindings:
352362
scope_binding, _ = self._binder.get_binding(binding.scope)
353363
scope_instance: Scope = scope_binding.provider.get(injector)
@@ -365,6 +375,7 @@ class MultiBindProvider(MultiBinder[List[T]]):
365375
def multibind(
366376
self, interface: type, to: Any, scope: Union['ScopeDecorator', Type['Scope'], None]
367377
) -> None:
378+
self._interface = interface
368379
try:
369380
element_type = get_args(_punch_through_alias(interface))[0]
370381
except IndexError:
@@ -391,6 +402,7 @@ class MapBindProvider(MultiBinder[Dict[str, T]]):
391402
def multibind(
392403
self, interface: type, to: Any, scope: Union['ScopeDecorator', Type['Scope'], None]
393404
) -> None:
405+
self._interface = interface
394406
try:
395407
value_type = get_args(_punch_through_alias(interface))[1]
396408
except IndexError:
@@ -582,9 +594,9 @@ def _get_multi_binder(self, interface: type) -> MultiBinder:
582594
and issubclass(interface, dict)
583595
or _get_origin(_punch_through_alias(interface)) is dict
584596
):
585-
multi_binder = MapBindProvider(self)
597+
multi_binder = MapBindProvider(self, interface)
586598
else:
587-
multi_binder = MultiBindProvider(self)
599+
multi_binder = MultiBindProvider(self, interface)
588600
binding = self.create_binding(interface, multi_binder)
589601
self._bindings[interface] = binding
590602
else:

‎injector_test.py‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,57 @@ def configure(binder: Binder) -> None:
923923
assert injector.get(PluginA) is not injector.get(PluginA)
924924

925925

926+
def test_multibinds_are_extended_by_child_injectors() -> None:
927+
parent_injector = Injector()
928+
parent_injector.binder.multibind(List[str], to=['parent name'])
929+
930+
child_injector = parent_injector.create_child_injector()
931+
child_injector.binder.multibind(List[str], to=['child name'])
932+
933+
assert parent_injector.get(List[str]) == ['parent name']
934+
assert child_injector.get(List[str]) == ['parent name', 'child name']
935+
936+
937+
def test_multibind_dict_is_extended_by_child_injectors() -> None:
938+
parent_injector = Injector()
939+
parent_injector.binder.multibind(Dict[str, str], to={'parent': 'p', 'shared': 'from_parent'})
940+
941+
child_injector = parent_injector.create_child_injector()
942+
child_injector.binder.multibind(Dict[str, str], to={'child': 'c', 'shared': 'from_child'})
943+
944+
assert parent_injector.get(Dict[str, str]) == {'parent': 'p', 'shared': 'from_parent'}
945+
assert child_injector.get(Dict[str, str]) == {'parent': 'p', 'shared': 'from_child', 'child': 'c'}
946+
947+
948+
def test_multibind_multi_level_hierarchy_extended_by_child_injectors() -> None:
949+
parent = Injector()
950+
parent.binder.multibind(List[str], to=['parent'])
951+
952+
child = parent.create_child_injector()
953+
child.binder.multibind(List[str], to=['child'])
954+
955+
grandchild = child.create_child_injector()
956+
grandchild.binder.multibind(List[str], to=['grandchild'])
957+
958+
assert parent.get(List[str]) == ['parent']
959+
assert child.get(List[str]) == ['parent', 'child']
960+
assert grandchild.get(List[str]) == ['parent', 'child', 'grandchild']
961+
962+
963+
def test_multibind_skipped_level_child_injector() -> None:
964+
parent = Injector()
965+
parent.binder.multibind(List[str], to=['parent'])
966+
967+
child = parent.create_child_injector()
968+
969+
grandchild = child.create_child_injector()
970+
grandchild.binder.multibind(List[str], to=['grandchild'])
971+
972+
assert parent.get(List[str]) == ['parent']
973+
assert child.get(List[str]) == ['parent']
974+
assert grandchild.get(List[str]) == ['parent', 'grandchild']
975+
976+
926977
def test_regular_bind_and_provider_dont_work_with_multibind():
927978
# We only want multibind and multiprovider to work to avoid confusion
928979

0 commit comments

Comments
 (0)