#####################################
Injecting beans
#####################################

Let's explore Grundzeug's injection capabilities in more detail.

***************************************
Different types of dependency injection
***************************************

Function kwarg injection
========================

The most basic form of dependency injection is function kwarg injection:

.. code-block:: python

    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")

:py:func:`~grundzeug.container.interface.Injector.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 :py:func:`~grundzeug.container.interface.Injector.inject` will be equivalent to the following
partial application:

.. code-block:: python

    injected_func : Callable[[str], None] = functools.partial(perform_foo, contract_impl=container.resolve[Contract]())


Constructor injection
=====================

Function kwarg injection can also be used with class constructors:


.. code-block:: python

    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:


.. code-block:: python

    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



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 :py:func:`~grundzeug.container.di.injection.injectable`:

.. code-block:: python

    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
    :py:func:`~grundzeug.container.di.injection.injectable`, which extends ``FooHolder``.


***********
Named beans
***********

It is possible to inject named beans using the :py:class:`~grundzeug.container.di.aliases.InjectNamed` annotation:

.. code-block:: python

    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")