Skip to content

Commit

Permalink
Move some functionality from container to the registry
Browse files Browse the repository at this point in the history
  • Loading branch information
maldoinc committed Aug 3, 2024
1 parent 2e9498a commit 0d23ff3
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 40 deletions.
63 changes: 24 additions & 39 deletions wireup/ioc/dependency_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class DependencyContainer:
"""

__slots__ = (
"__service_registry",
"__registry",
"__initialized_objects",
"__active_overrides",
"__override_manager",
Expand All @@ -62,13 +62,11 @@ class DependencyContainer:

def __init__(self, parameter_bag: ParameterBag) -> None:
""":param parameter_bag: ParameterBag instance holding parameter information."""
self.__service_registry: ServiceRegistry = ServiceRegistry()
self.__registry = ServiceRegistry()
self.__initialized_objects: dict[ContainerObjectIdentifier, Any] = {}
self.__active_overrides: dict[ContainerObjectIdentifier, Any] = {}
self.__params: ParameterBag = parameter_bag
self.__override_manager: OverrideManager = OverrideManager(
self.__active_overrides, self.__service_registry.is_type_with_qualifier_known
)
self.__override_manager = OverrideManager(self.__active_overrides, self.__registry.is_type_with_qualifier_known)

def get(self, klass: type[T], qualifier: Qualifier | None = None) -> T:
"""Get an instance of the requested type.
Expand All @@ -82,10 +80,10 @@ def get(self, klass: type[T], qualifier: Qualifier | None = None) -> T:
if res := self.__active_overrides.get((klass, qualifier)):
return res # type: ignore[no-any-return]

self.__assert_dependency_exists(klass, qualifier)
self.__registry.assert_dependency_exists(klass, qualifier)

if self.__service_registry.is_interface_known(klass):
klass = self.__resolve_impl(klass, qualifier)
if self.__registry.is_interface_known(klass):
klass = self.__registry.interface_resolve_impl(klass, qualifier)

if instance := self.__initialized_objects.get((klass, qualifier)):
return instance # type: ignore[no-any-return]
Expand All @@ -97,7 +95,7 @@ def abstract(self, klass: type[T]) -> type[T]:
This type cannot be initialized directly and one of the components implementing this will be injected instead.
"""
self.__service_registry.register_abstract(klass)
self.__registry.register_abstract(klass)

return klass

Expand Down Expand Up @@ -144,19 +142,19 @@ def decorated(decorated_obj: T) -> T:
return decorated

if isinstance(obj, type):
self.__service_registry.register_service(obj, qualifier, lifetime)
self.__registry.register_service(obj, qualifier, lifetime)
return obj

if callable(obj):
self.__service_registry.register_factory(obj, qualifier=qualifier, lifetime=lifetime)
self.__registry.register_factory(obj, qualifier=qualifier, lifetime=lifetime)
return obj

raise InvalidRegistrationTypeError(obj)

@property
def context(self) -> InitializationContext:
"""The initialization context for registered targets. A map between an injection target and its dependencies."""
return self.__service_registry.context
return self.__registry.context

@property
def params(self) -> ParameterBag:
Expand Down Expand Up @@ -187,7 +185,7 @@ def autowire(self, fn: AnyCallable) -> AnyCallable:
* When injecting an interface for which there are multiple implementations you need to supply a qualifier
using annotations.
"""
self.__service_registry.target_init_context(fn)
self.__registry.target_init_context(fn)

if asyncio.iscoroutinefunction(fn):

Expand All @@ -209,10 +207,10 @@ def warmup(self) -> None:
This should be executed once all services are registered with the container. Targets of autowire will not
be affected.
"""
sorter = TopologicalSorter(self.__service_registry.get_dependency_graph())
sorter = TopologicalSorter(self.__registry.get_dependency_graph())

for klass in sorter.static_order():
for qualifier in self.__service_registry.known_impls[klass]:
for qualifier in self.__registry.known_impls[klass]:
if (klass, qualifier) not in self.__initialized_objects:
self.__create_concrete_type(klass, qualifier)

Expand All @@ -223,7 +221,7 @@ def override(self) -> OverrideManager:

def __callable_get_params_to_inject(self, fn: AnyCallable) -> dict[str, Any]:
values_from_parameters: dict[str, Any] = {}
params = self.__service_registry.context.dependencies[fn]
params = self.__registry.context.dependencies[fn]
names_to_remove: set[str] = set()

for name, param in params.items():
Expand All @@ -246,42 +244,42 @@ def __callable_get_params_to_inject(self, fn: AnyCallable) -> dict[str, Any]:
# If autowiring, the container is assumed to be final, so unnecessary entries can be removed
# from the context in order to speed up the autowiring process.
if names_to_remove:
self.__service_registry.context.remove_dependencies(fn, names_to_remove)
self.__registry.context.remove_dependencies(fn, names_to_remove)

return values_from_parameters

def __create_concrete_type(self, klass: type[T], qualifier: Qualifier | None) -> T:
"""Create the real instances of dependencies. Additional dependencies they may have will be lazily created."""
obj_id = klass, qualifier

if fn := self.__service_registry.factory_functions.get(obj_id):
if fn := self.__registry.factory_functions.get(obj_id):
instance = fn(**self.__callable_get_params_to_inject(fn))
else:
args = self.__callable_get_params_to_inject(klass)
instance = klass(**args)

if self.__service_registry.is_impl_singleton(klass):
if self.__registry.is_impl_singleton(klass):
self.__initialized_objects[obj_id] = instance

return instance # type: ignore[no-any-return]

def __get_instance(
self, klass: type[T], qualifier: Qualifier | None, annotation: InjectableType | None = None
) -> T | None:
if self.__service_registry.is_impl_known_from_factory(klass, qualifier):
if self.__registry.is_impl_known_from_factory(klass, qualifier):
# Objects generated from factories do not have qualifiers
return self.__create_concrete_type(klass, None)

if self.__service_registry.is_interface_known(klass):
concrete_class = self.__resolve_impl(klass, qualifier)
if self.__registry.is_interface_known(klass):
concrete_class = self.__registry.interface_resolve_impl(klass, qualifier)
return self.__create_concrete_type(concrete_class, qualifier)

if self.__service_registry.is_impl_known(klass):
if not self.__service_registry.is_impl_with_qualifier_known(klass, qualifier):
if self.__registry.is_impl_known(klass):
if not self.__registry.is_impl_with_qualifier_known(klass, qualifier):
raise UnknownQualifiedServiceRequestedError(
klass,
qualifier,
self.__service_registry.known_impls[klass],
self.__registry.known_impls[klass],
)
return self.__create_concrete_type(klass, qualifier)

Expand All @@ -299,19 +297,6 @@ def __get_instance(

return None

def __resolve_impl(self, klass: type[T], qualifier: Qualifier | None) -> type[T]:
impls = self.__service_registry.known_interfaces.get(klass, {})

if qualifier in impls:
return impls[qualifier]

raise UnknownQualifiedServiceRequestedError(klass, qualifier, set(impls.keys()))

def is_type_known(self, klass: type) -> bool:
"""Given a class type return True if's registered in the container as a service or interface."""
return self.__service_registry.is_impl_known(klass) or self.__service_registry.is_interface_known(klass)

def __assert_dependency_exists(self, klass: type, qualifier: Qualifier | None) -> None:
"""Assert that there exists an impl with that qualifier or an interface with an impl and the same qualifier."""
if not self.__service_registry.is_type_with_qualifier_known(klass, qualifier):
raise UnknownServiceRequestedError(klass)
return self.__registry.is_impl_known(klass) or self.__registry.is_interface_known(klass)
21 changes: 20 additions & 1 deletion wireup/ioc/service_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import inspect
from collections import defaultdict
from inspect import Parameter
from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any, Callable, TypeVar

from wireup.errors import (
DuplicateQualifierForInterfaceError,
DuplicateServiceRegistrationError,
FactoryDuplicateServiceRegistrationError,
FactoryReturnTypeIsEmptyError,
UnknownQualifiedServiceRequestedError,
UnknownServiceRequestedError,
)
from wireup.ioc.initialization_context import InitializationContext
from wireup.ioc.types import AnnotatedParameter, AutowireTarget, ServiceLifetime
Expand All @@ -23,6 +25,9 @@
)


T = TypeVar("T")


class ServiceRegistry:
"""Container class holding service registration info and dependencies among them."""

Expand Down Expand Up @@ -167,3 +172,17 @@ def is_impl_singleton(self, klass: type) -> bool: # noqa: D102

def is_interface_known(self, klass: type) -> bool: # noqa: D102
return klass in self.known_interfaces

def assert_dependency_exists(self, klass: type, qualifier: Qualifier | None) -> None:
"""Assert that there exists an impl or interface with that qualifier."""
if not self.is_type_with_qualifier_known(klass, qualifier):
raise UnknownServiceRequestedError(klass)

def interface_resolve_impl(self, klass: type[T], qualifier: Qualifier | None) -> type[T]:
"""Given an interface and qualifier return the concrete implementation."""
impls = self.known_interfaces.get(klass, {})

if qualifier in impls:
return impls[qualifier]

raise UnknownQualifiedServiceRequestedError(klass, qualifier, set(impls.keys()))

0 comments on commit 0d23ff3

Please sign in to comment.