Skip to content

Commit b4d9fcb

Browse files
committed
add scope support to dicts
1 parent 4d57f11 commit b4d9fcb

File tree

2 files changed

+38
-18
lines changed

2 files changed

+38
-18
lines changed

injector/__init__.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
Any,
2929
Callable,
3030
Dict,
31+
Generator,
3132
Generic,
3233
Iterable,
3334
List,
@@ -340,7 +341,7 @@ def __repr__(self) -> str:
340341

341342

342343
@private
343-
class ListOfProviders(Provider, Generic[T]):
344+
class Multibinder(Provider, Generic[T]):
344345
"""Provide a list of instances via other Providers."""
345346

346347
_multi_bindings: List['Binding']
@@ -354,16 +355,7 @@ def append(self, provider: Provider[T], scope: Type['Scope']) -> None:
354355
pseudo_type = type(f"pseudo-type-{id(provider)}", (provider.__class__,), {})
355356
self._multi_bindings.append(Binding(pseudo_type, provider, scope))
356357

357-
def __repr__(self) -> str:
358-
return '%s(%r)' % (type(self).__name__, self._multi_bindings)
359-
360-
361-
class MultiBindProvider(ListOfProviders[List[T]]):
362-
"""Used by :meth:`Binder.multibind` to flatten results of providers that
363-
return sequences."""
364-
365-
def get(self, injector: 'Injector') -> List[T]:
366-
result: List[T] = []
358+
def get_scoped_providers(self, injector: 'Injector') -> Generator[Provider[T], None, None]:
367359
for binding in self._multi_bindings:
368360
if (
369361
isinstance(binding.provider, ClassProvider)
@@ -377,19 +369,31 @@ def get(self, injector: 'Injector') -> List[T]:
377369
scope_binding, _ = self._binder.get_binding(binding.scope)
378370
scope_instance: Scope = scope_binding.provider.get(injector)
379371
provider_instance = scope_instance.get(binding.interface, binding.provider)
380-
instances = _ensure_iterable(provider_instance.get(injector))
372+
yield provider_instance
373+
374+
def __repr__(self) -> str:
375+
return '%s(%r)' % (type(self).__name__, self._multi_bindings)
376+
377+
378+
class MultiBindProvider(Multibinder[List[T]]):
379+
"""Used by :meth:`Binder.multibind` to flatten results of providers that
380+
return sequences."""
381+
382+
def get(self, injector: 'Injector') -> List[T]:
383+
result: List[T] = []
384+
for provider in self.get_scoped_providers(injector):
385+
instances: List[T] = _ensure_iterable(provider.get(injector))
381386
result.extend(instances)
382387
return result
383388

384389

385-
class MapBindProvider(ListOfProviders[Dict[str, T]]):
390+
class MapBindProvider(Multibinder[Dict[str, T]]):
386391
"""A provider for map bindings."""
387392

388393
def get(self, injector: 'Injector') -> Dict[str, T]:
389394
map: Dict[str, T] = {}
390-
for binding in self._multi_bindings:
391-
# TODO: support scope
392-
map.update(binding.provider.get(injector))
395+
for provider in self.get_scoped_providers(injector):
396+
map.update(provider.get(injector))
393397
return map
394398

395399

@@ -552,7 +556,7 @@ def multibind(
552556
:param scope: Optional Scope in which to bind.
553557
"""
554558
if interface not in self._bindings:
555-
provider: ListOfProviders
559+
provider: Multibinder
556560
if (
557561
isinstance(interface, dict)
558562
or isinstance(interface, type)
@@ -566,7 +570,7 @@ def multibind(
566570
self._bindings[interface] = binding
567571
else:
568572
binding = self._bindings[interface]
569-
assert isinstance(binding.provider, ListOfProviders)
573+
assert isinstance(binding.provider, Multibinder)
570574
provider = binding.provider
571575

572576
if isinstance(provider, MultiBindProvider) and isinstance(to, list):

injector_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,22 @@ def configure(binder: Binder) -> None:
754754
assert first_list[2] is second_list[2]
755755

756756

757+
def test_multibind_dict_scopes_applies_to_the_bound_items() -> None:
758+
def configure(binder: Binder) -> None:
759+
binder.multibind(Dict[str, Plugin], to={'a': PluginA}, scope=singleton)
760+
binder.multibind(Dict[str, Plugin], to={'b': PluginB})
761+
binder.multibind(Dict[str, Plugin], to={'c': PluginC}, scope=singleton)
762+
763+
injector = Injector([configure])
764+
first_dict = injector.get(Dict[str, Plugin])
765+
second_dict = injector.get(Dict[str, Plugin])
766+
767+
assert first_dict is not second_dict
768+
assert first_dict['a'] is second_dict['a']
769+
assert first_dict['b'] is not second_dict['b']
770+
assert first_dict['c'] is second_dict['c']
771+
772+
757773
def test_multibind_scopes_does_not_apply_to_the_type_globally() -> None:
758774
def configure(binder: Binder) -> None:
759775
binder.multibind(List[Plugin], to=PluginA, scope=singleton)

0 commit comments

Comments
 (0)