2929 cast ,
3030 Dict ,
3131 Generic ,
32+ get_args ,
3233 Iterable ,
3334 List ,
3435 Optional ,
@@ -244,6 +245,10 @@ class UnknownArgument(Error):
244245 """Tried to mark an unknown argument as noninjectable."""
245246
246247
248+ class InvalidInterface (Error ):
249+ """Cannot bind to the specified interface."""
250+
251+
247252class Provider (Generic [T ]):
248253 """Provides class instances."""
249254
@@ -355,7 +360,11 @@ class MultiBindProvider(ListOfProviders[List[T]]):
355360 return sequences."""
356361
357362 def get (self , injector : 'Injector' ) -> List [T ]:
358- return [i for provider in self ._providers for i in provider .get (injector )]
363+ result : List [T ] = []
364+ for provider in self ._providers :
365+ instances : List [T ] = _ensure_iterable (provider .get (injector ))
366+ result .extend (instances )
367+ return result
359368
360369
361370class MapBindProvider (ListOfProviders [Dict [str , T ]]):
@@ -368,6 +377,16 @@ def get(self, injector: 'Injector') -> Dict[str, T]:
368377 return map
369378
370379
380+ @private
381+ class KeyValueProvider (Provider [Dict [str , T ]]):
382+ def __init__ (self , key : str , inner_provider : Provider [T ]) -> None :
383+ self ._key = key
384+ self ._provider = inner_provider
385+
386+ def get (self , injector : 'Injector' ) -> Dict [str , T ]:
387+ return {self ._key : self ._provider .get (injector )}
388+
389+
371390_BindingBase = namedtuple ('_BindingBase' , 'interface provider scope' )
372391
373392
@@ -468,7 +487,7 @@ def bind(
468487 def multibind (
469488 self ,
470489 interface : Type [List [T ]],
471- to : Union [List [T ] , Callable [..., List [T ]], Provider [List [T ]]],
490+ to : Union [List [Union [ T , Type [ T ]]] , Callable [..., List [T ]], Provider [List [T ]], Type [ T ]],
472491 scope : Union [Type ['Scope' ], 'ScopeDecorator' , None ] = None ,
473492 ) -> None : # pragma: no cover
474493 pass
@@ -477,7 +496,7 @@ def multibind(
477496 def multibind (
478497 self ,
479498 interface : Type [Dict [K , V ]],
480- to : Union [Dict [K , V ], Callable [..., Dict [K , V ]], Provider [Dict [K , V ]]],
499+ to : Union [Dict [K , Union [ V , Type [ V ]] ], Callable [..., Dict [K , V ]], Provider [Dict [K , V ]]],
481500 scope : Union [Type ['Scope' ], 'ScopeDecorator' , None ] = None ,
482501 ) -> None : # pragma: no cover
483502 pass
@@ -489,22 +508,27 @@ def multibind(
489508
490509 A multi-binding contributes values to a list or to a dictionary. For example::
491510
492- binder.multibind(List[str], to=['some', 'strings'])
493- binder.multibind(List[str], to=['other', 'strings'])
494- injector.get(List[str]) # ['some', 'strings', 'other', 'strings']
511+ binder.multibind(list[Interface], to=A)
512+ binder.multibind(list[Interface], to=[B, C()])
513+ injector.get(list[Interface])
514+ # [<A object at 0x1000>, <B object at 0x2000>, <C object at 0x3000>]
495515
496- binder.multibind(Dict[str, int], to={'key': 11})
497- binder.multibind(Dict[str, int], to={'other_key': 33})
498- injector.get(Dict[str, int]) # {'key': 11, 'other_key': 33}
516+ binder.multibind(dict[str, Interface], to={'key': A})
517+ binder.multibind(dict[str, Interface], to={'other_key': B})
518+ injector.get(dict[str, Interface])
519+ # {'key': <A object at 0x1000>, 'other_key': <B object at 0x2000>}
499520
500521 .. versionchanged:: 0.17.0
501522 Added support for using `typing.Dict` and `typing.List` instances as interfaces.
502523 Deprecated support for `MappingKey`, `SequenceKey` and single-item lists and
503524 dictionaries as interfaces.
504525
505- :param interface: typing.Dict or typing.List instance to bind to.
506- :param to: Instance, class to bind to, or an explicit :class:`Provider`
507- subclass. Must provide a list or a dictionary, depending on the interface.
526+ :param interface: A generic list[T] or dict[str, T] type to bind to.
527+
528+ :param to: A list/dict to bind to, where the values are either instances or classes implementing T.
529+ Can also be an explicit :class:`Provider` or a callable that returns a list/dict.
530+ For lists, this can also be a class implementing T (e.g. multibind(list[T], to=A))
531+
508532 :param scope: Optional Scope in which to bind.
509533 """
510534 if interface not in self ._bindings :
@@ -524,7 +548,27 @@ def multibind(
524548 binding = self ._bindings [interface ]
525549 provider = binding .provider
526550 assert isinstance (provider , ListOfProviders )
527- provider .append (self .provider_for (interface , to ))
551+
552+ if isinstance (provider , MultiBindProvider ) and isinstance (to , list ):
553+ try :
554+ element_type = get_args (_punch_through_alias (interface ))[0 ]
555+ except IndexError :
556+ raise InvalidInterface (
557+ f"Use typing.List[T] or list[T] to specify the element type of the list"
558+ )
559+ for element in to :
560+ provider .append (self .provider_for (element_type , element ))
561+ elif isinstance (provider , MapBindProvider ) and isinstance (to , dict ):
562+ try :
563+ value_type = get_args (_punch_through_alias (interface ))[1 ]
564+ except IndexError :
565+ raise InvalidInterface (
566+ f"Use typing.Dict[K, V] or dict[K, V] to specify the value type of the dict"
567+ )
568+ for key , value in to .items ():
569+ provider .append (KeyValueProvider (key , self .provider_for (value_type , value )))
570+ else :
571+ provider .append (self .provider_for (interface , to ))
528572
529573 def install (self , module : _InstallableModuleType ) -> None :
530574 """Install a module into this binder.
@@ -696,6 +740,12 @@ def _is_specialization(cls: type, generic_class: Any) -> bool:
696740 return origin is generic_class or issubclass (origin , generic_class )
697741
698742
743+ def _ensure_iterable (item_or_list : Union [T , List [T ]]) -> List [T ]:
744+ if isinstance (item_or_list , list ):
745+ return item_or_list
746+ return [item_or_list ]
747+
748+
699749def _punch_through_alias (type_ : Any ) -> type :
700750 if (
701751 sys .version_info < (3 , 10 )
0 commit comments