-
Notifications
You must be signed in to change notification settings - Fork 92
feat: Support instantiation with multibind #277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
… instantiated the the injector
…on-generic list/dict
:param interface: typing.Dict or typing.List instance to bind to. | ||
:param to: Instance, class to bind to, or an explicit :class:`Provider` | ||
subclass. Must provide a list or a dictionary, depending on the interface. | ||
:param interface: A generic list[T] or dict[str, T] type to bind to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to use PEP 585 styled type hints. Support for them was introduced in Python 3.9. While this project still supports Python 3.8, I suspect it's only a matter of time until that support gets dropped since 3.8 has reached end-of-life.
injector/__init__.py
Outdated
binder.multibind(list[Interface], to=A) | ||
binder.multibind(list[Interface], to=[B, C()]) | ||
injector.get(list[Interface]) # [<A object at 0x1000>, <B object at 0x2000>, <C object at 0x3000>] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed these examples to illustrate the use of classes and objects, rather than strings. This improves consistency with the bind
documentation.
IMO, people reach for DI libraries to simplify object construction. Injecting strings or other primitive values, while possible, is less beneficial, at least in my experience.
What
Make it possible to multibind to types/classes which then get instantiated when
injector.get(<MultiBoundType>)
is called. Support this both for lists and dictionaries. Update themultibind
documentation accordingly.Example:
Why
In modular software systems, it's often convenient to leverage plug-in patterns to allow a particular module to hook into certain events. The Guice documentation explains this nicely.
Personally, I have used this pattern for lifecycle listeners that get invoked whenever an application starts up and shuts down. Imagine that you have an application that supports different database back-ends and key-value stores. In such a scenario, we typically have separate DI modules for each database system and each key-value store, e.g.
The injector is then configured using the appropriate set of modules, depending on which database and cache we want to employ, e.g.
injector = Injector([PostgreSQLModule, RedisModule])
.Each of these modules can optionally register a lifecycle listener. Here's an example for a listener that closes the connection to redis on shutdown.
The application code then simply asks for all lifecycle listeners and invokes them on startup and shutdown. Here's an example of a FastAPI lifespan that does this
According to @alecthomas (see #121 (comment)) it was always the intention to support this behavior but up until now, users have had to employ workarounds to get this working.
Prior work
This is similar to #197 in that it solves the problem of multibinding to types. However, the API is quite different.
In #197 the proposal is to multibind to a
MultiClassProvider
like soThe implementation in this PR foregoes the need for a special wrapper, allowing one to mix and match classes and instances like so
Moreover, this PR also supports binding to classes without nesting them in lists
It also supports dictionary multibindings
Closes #121