Source code for injectable.injection.inject
from typing import TypeVar, Union, Type, List, Sequence
from injectable.common_utils import get_dependency_name
from injectable.errors import InjectionError
from injectable.constants import DEFAULT_NAMESPACE
from injectable.injection.injection_utils import (
get_namespace_injectables,
filter_by_group,
resolve_single_injectable,
get_dependency_registry_type,
)
T = TypeVar("T")
[docs]def inject(
dependency: Union[Type[T], str],
*,
namespace: str = None,
group: str = None,
exclude_groups: Sequence[str] = None,
lazy: bool = False,
optional: bool = False,
) -> T:
"""
Injects the requested dependency by instantiating a new instance of it or a
singleton instance if specified by the injectable. Returns an instance of the
requested dependency.
One can use this method directly for injecting dependencies though this is not
recommended. Use the :meth:`@autowired <injectable.autowired>` decorator and the
:class:`Autowired <injectable.Autowired>` type annotation for dependency injection
to be automatically wired to a function's call instead.
Will log a warning indicating that the injection container is empty when invoked
before :meth:`load_injection_container <injectable.load_injection_container>` is
called.
Raises
:class:`InjectionError <injectable.errors.InjectionError>`
when unable to resolve the requested dependency. This can be due to a variety of
reasons: the requested dependency wasn't loaded into the container; the namespace
isn't correct; the group isn't correct; there are multiple injectables for the
dependency and none or multiple are marked as primary. When parameter ``optional``
is ``True`` no error will be raised when no injectable that matches requested
qualifier/class and group is found in the specified namespace though in ambiguous
cases that resolving a primary injectable is impossible an error will still be
raised.
:param dependency: class, base class or qualifier of the dependency to be used for
lookup among the registered injectables.
:param namespace: (optional) namespace in which to look for the dependency. Defaults
to :const:`injectable.constants.DEFAULT_NAMESPACE`.
:param group: (optional) group to filter out other injectables outside of this
group. Defaults to None.
:param exclude_groups: (optional) list of groups to be excluded. Defaults to None.
:param lazy: (optional) when True will return an instance which will automatically
initialize itself when first used but not before that. Defaults to False.
:param optional: (optional) when True this function returns None if no injectable
matches the qualifier/class and group inside the specified namespace instead
of raising an :class:`InjectionError <injectable.errors.InjectionError>`.
Ambiguous cases where resolving a primary injectable is impossible will
still raise :class:`InjectionError <injectable.errors.InjectionError>`.
Defaults to False.
Usage::
>>> from foo import Foo
>>> from injectable import inject
>>>
>>> class Bar:
... def __init__(self, foo: Foo = None):
... self.foo = foo or inject(Foo)
"""
dependency_name = get_dependency_name(dependency)
registry_type = get_dependency_registry_type(dependency)
matches = get_namespace_injectables(
dependency_name, registry_type, namespace or DEFAULT_NAMESPACE
)
if not matches:
if not optional:
raise InjectionError(
f"No injectable matches {registry_type.value} '{dependency_name}'"
)
return None
if group is not None or exclude_groups is not None:
matches = filter_by_group(matches, group, exclude_groups)
if not matches:
if not optional:
raise InjectionError(
f"No injectable for {registry_type.value} '{dependency_name}'"
f" matches group '{group}'"
)
return None
injectable = resolve_single_injectable(dependency_name, registry_type, matches)
return injectable.get_instance(lazy=lazy)
[docs]def inject_multiple(
dependency: Union[Type[T], str],
*,
namespace: str = None,
group: str = None,
exclude_groups: Sequence[str] = None,
lazy: bool = False,
optional: bool = False,
) -> List[T]:
"""
Injects all injectables that resolves to the specified dependency. Returns a list of
instances matching the requested dependency.
One can use this method directly for injecting dependencies though this is not
recommended. Use the :meth:`@autowired <injectable.autowired>` decorator and the
:class:`Autowired <injectable.Autowired>` type annotation for dependency injection
to be automatically wired to a function's call instead.
Logs a warning indicating that the injection container is empty when invoked before
:meth:`load_injection_container <injectable.load_injection_container>` is called.
Raises
:class:`InjectionError <injectable.errors.InjectionError>`
when unable to resolve the requested dependency. This can be due to a variety of
reasons: there is no injectable loaded into the container that matches the
dependency; the namespace isn't correct; the group specifications aren't correct.
When parameter ``optional`` is ``True`` no error will be raised when no injectable
that matches requested qualifier/class and group is found in the specified
namespace.
:param dependency: class, base class or qualifier of the dependency to be used for
lookup among the registered injectables.
:param namespace: (optional) namespace in which to look for the dependency. Defaults
to :const:`injectable.constants.DEFAULT_NAMESPACE`.
:param group: (optional) group to filter out other injectables outside of this
group. Defaults to None.
:param exclude_groups: (optional) list of groups to be excluded. Defaults to None.
:param lazy: (optional) when True will returned instances will automatically
initialize themselves when first used but not before that. Defaults to
False.
:param optional: (optional) when True this function returns an empty list if no
injectable matches the qualifier/class and group inside the specified
namespace. Defaults to False.
Usage::
>>> from com import AbstractService
>>> from injectable import inject_multiple
>>> from typing import Sequence
>>>
>>> class Foo:
... def __init__(self, services: Sequence[AbstractService] = None):
... self.services = services or inject_multiple(AbstractService)
"""
dependency_name = get_dependency_name(dependency)
registry_type = get_dependency_registry_type(dependency)
matches = get_namespace_injectables(
dependency_name, registry_type, namespace or DEFAULT_NAMESPACE
)
if not matches:
if not optional:
raise InjectionError(
f"No injectable matches {registry_type.value} '{dependency_name}'"
)
return []
if group is not None or exclude_groups is not None:
matches = filter_by_group(
matches,
group,
exclude_groups,
)
if not matches:
if not optional:
raise InjectionError(
f"No injectable for {registry_type.value} '{dependency_name}'"
f" matches group '{group}'"
)
return []
return [inj.get_instance(lazy=lazy) for inj in matches]