2. Injecting beans¶
Let’s explore Grundzeug’s injection capabilities in more detail.
2.1. Different types of dependency injection¶
2.1.1. Function kwarg injection¶
The most basic form of dependency injection is function kwarg injection:
from grundzeug.container.di import Inject
def perform_foo(greeting: str, contract_impl: Inject[Contract]) -> None:
contract_impl.foo()
print(greeting)
# Partially apply perform_foo using beans from the
# container, binding a bean that satisfies
# Contract to contract_impl.
injected_func : Callable[[str], None] = container.inject(perform_foo)
# We can still pass arguments to an injected function!
injected_func("hello world")
inject()
inspects the type annotations of the function’s arguments and
determines which arguments should be retrieved from the container.
In this case, the call to inject()
will be equivalent to the following
partial application:
injected_func : Callable[[str], None] = functools.partial(perform_foo, contract_impl=container.resolve[Contract]())
2.1.2. Constructor injection¶
Function kwarg injection can also be used with class constructors:
from grundzeug.container.di import Inject
class FooHolder:
def __init__(greeting: str, contract_impl: Inject[Contract]):
self.greeting = greeting
self.contract_impl = contract_impl
# Partially apply Foo using beans from the
# container, binding a bean that satisfies
# Contract to contract_impl.
injected_foo_holder : Callable[[str], None] = container.inject(FooHolder)
# We can still pass arguments to an injected constructor!
instance = injected_foo("hello world")
instance.contract_impl.foo()
print(instance.greeting) # hello world
You can even do the same thing with dataclasses:
import dataclasses
from grundzeug.container.di import Inject
@dataclasses.dataclass
class FooHolder:
greeting: str
contract_impl: Inject[Contract]
# Partially apply Foo using beans from the
# container, binding a bean that satisfies
# Contract to contract_impl.
injected_foo_holder : Callable[[str], None] = container.inject(FooHolder)
# We can still pass arguments to an injected constructor!
instance = injected_foo("hello world")
instance.contract_impl.foo()
print(instance.greeting) # hello world
2.1.3. Field injection¶
Sometimes, you may want to inject dependencies directly into class fields, without using dataclasses. In this case,
you can decorate your class with injectable()
:
from grundzeug.container.di import Inject, injectable
@injectable
class FooHolder:
contract_impl: Inject[Contract]
def __init__(greeting: str):
self.greeting = greeting
injected_foo_holder : Callable[[str], None] = container.inject(FooHolder)
# We can still pass arguments to an injected constructor!
instance = injected_foo("hello world")
instance.contract_impl.foo()
print(instance.greeting) # hello world
Warning
In this case, instance
will actually be an instance of a wrapper class generated by
injectable()
, which extends FooHolder
.
2.2. Named beans¶
It is possible to inject named beans using the InjectNamed
annotation:
from grundzeug.container.di import InjectNamed
def perform_foo(greeting: str, contract_impl: InjectNamed[Contract, "named_bean"]) -> None:
contract_impl.foo()
print(greeting)
container.register_type[Contract, ContractImpl](bean_name="named_bean")
...
injected_func : Callable[[str], None] = container.inject(perform_foo)
injected_func("hello world")