Skip to content

Commit 8e6f24b

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 5daed12 commit 8e6f24b

3 files changed

Lines changed: 83 additions & 3 deletions

File tree

‎CHANGES‎

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

77
- Dropped support for Python 3.8 and 3.9. Python 3.10+ is now required.
88
- Use the SPDX license identifier `BSD-3-Clause` in package metadata.
9+
- Child injectors now respect and extend parent injector multibindings.
910

1011
0.24.0
1112
------

‎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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,73 @@ 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+
977+
def test_multibind_child_only_when_parent_has_no_binding() -> None:
978+
parent = Injector()
979+
child = parent.create_child_injector()
980+
child.binder.multibind(List[str], to=['only_child'])
981+
assert child.get(List[str]) == ['only_child']
982+
983+
984+
def test_multibind_child_when_parent_has_non_multibinder_binding() -> None:
985+
parent = Injector()
986+
parent.binder._bindings[List[str]] = parent.binder.create_binding(List[str], ['not_a_multibinder'])
987+
child = parent.create_child_injector()
988+
child.binder.multibind(List[str], to=['child_multibind'])
989+
assert child.get(List[str]) == ['child_multibind']
990+
991+
992+
926993
def test_regular_bind_and_provider_dont_work_with_multibind():
927994
# We only want multibind and multiprovider to work to avoid confusion
928995

0 commit comments

Comments
 (0)