dependency-injection-pattern 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dependency_injection_pattern-0.0.0.dist-info/METADATA +104 -0
- dependency_injection_pattern-0.0.0.dist-info/RECORD +36 -0
- dependency_injection_pattern-0.0.0.dist-info/WHEEL +5 -0
- dependency_injection_pattern-0.0.0.dist-info/licenses/LICENSE +21 -0
- dependency_injection_pattern-0.0.0.dist-info/top_level.txt +1 -0
- di/__init__.py +13 -0
- di/core/container.py +336 -0
- di/core/default_scope.py +71 -0
- di/core/exceptions/add_exception.py +7 -0
- di/core/exceptions/container_sealed_error.py +5 -0
- di/core/exceptions/factory_provide_exception.py +25 -0
- di/core/exceptions/implementation_exception.py +13 -0
- di/core/exceptions/provide_exception.py +22 -0
- di/core/exceptions/seal_exception.py +9 -0
- di/core/factories/dependency.py +38 -0
- di/core/factories/factory.py +105 -0
- di/core/factories/scoped_factory.py +18 -0
- di/core/factories/singleton_factory.py +32 -0
- di/core/factories/transient_factory.py +17 -0
- di/core/factory.py +24 -0
- di/core/helpers.py +51 -0
- di/core/log.py +4 -0
- di/core/named_service.py +3 -0
- di/core/protocols/container.py +25 -0
- di/core/protocols/factory.py +7 -0
- di/core/protocols/provider.py +24 -0
- di/core/provider.py +85 -0
- di/core/reflection/__init__.py +126 -0
- di/core/reflection/parameter.py +66 -0
- di/core/reflection/parameter_kind.py +9 -0
- di/core/reflection/parameter_mapper.py +55 -0
- di/core/reflection/signature.py +45 -0
- di/core/reflection/undefined.py +24 -0
- di/core/scope.py +15 -0
- di/core/service_request.py +26 -0
- di/exceptions.py +15 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# pragma: no cover
|
|
2
|
+
from typing import overload
|
|
3
|
+
|
|
4
|
+
from di.core.helpers import get_service_name
|
|
5
|
+
|
|
6
|
+
class ProvideException(Exception):
|
|
7
|
+
"""
|
|
8
|
+
The ProvideException is raised when provider cannot provide service.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@overload
|
|
12
|
+
def __init__(self, service: type | str) -> None:
|
|
13
|
+
...
|
|
14
|
+
@overload
|
|
15
|
+
def __init__(self, service: type | str, reason: str) -> None:
|
|
16
|
+
...
|
|
17
|
+
def __init__(self, service: type | str, reason: str | None = None):
|
|
18
|
+
service = get_service_name(service)
|
|
19
|
+
if reason:
|
|
20
|
+
super().__init__(f"Cannot provide service '{service}': {reason}")
|
|
21
|
+
else:
|
|
22
|
+
super().__init__(f"Cannot provide service '{service}'")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# pragma: no cover
|
|
2
|
+
class SealException(Exception):
|
|
3
|
+
"""
|
|
4
|
+
The SealException is raised when container is being sealed and a preliminary check reveals that one or more
|
|
5
|
+
services cannot be provided. This could be due to a singleton service depending on a transient or scoped service which
|
|
6
|
+
isn't allowed..
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, msg: str):
|
|
9
|
+
super().__init__(msg)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
from typingutils import get_optional_type
|
|
3
|
+
|
|
4
|
+
from di.core.helpers import get_provided_service_from_annotation
|
|
5
|
+
from di.core.reflection.undefined import Undefined
|
|
6
|
+
|
|
7
|
+
class Dependency:
|
|
8
|
+
__slots__ = [ "__name", "__service", "__factory", "__optional", "__default" ]
|
|
9
|
+
|
|
10
|
+
def __init__(self, name: str, service: type[Any], default: Any ):
|
|
11
|
+
self.__name = name
|
|
12
|
+
service, self.__optional = cast(tuple[type[Any], bool], get_optional_type(service))
|
|
13
|
+
self.__service, self.__factory = cast(tuple[type[Any], bool], get_provided_service_from_annotation(service))
|
|
14
|
+
self.__default = default
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def name(self) -> str:
|
|
18
|
+
return self.__name # pragma: no cover
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def service(self) -> type[Any]:
|
|
22
|
+
return self.__service
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def is_optional(self) -> bool:
|
|
26
|
+
return self.__optional
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def is_factory(self) -> bool:
|
|
30
|
+
return self.__factory
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def has_default(self) -> bool:
|
|
34
|
+
return self.__default is not Undefined
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def default(self) -> Any:
|
|
38
|
+
return self.__default # pragma: no cover
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from typing import TypeVar, Callable, Generic, Any, cast
|
|
2
|
+
from typingutils import get_optional_type, issubclass_typing, isinstance_typing
|
|
3
|
+
|
|
4
|
+
from di.core.reflection import Undefined, ParameterKind, reflect_function, get_constructor
|
|
5
|
+
from di.core.scope import Scope
|
|
6
|
+
from di.core.named_service import NamedService
|
|
7
|
+
from di.core.service_request import ServiceRequest
|
|
8
|
+
|
|
9
|
+
from di.core.exceptions.implementation_exception import ImplementationException
|
|
10
|
+
from di.core.exceptions.factory_provide_exception import FactoryProvideException
|
|
11
|
+
from di.core.exceptions.provide_exception import ProvideException
|
|
12
|
+
from di.core.protocols.container import Container
|
|
13
|
+
from di.core.factories.dependency import Dependency
|
|
14
|
+
|
|
15
|
+
T = TypeVar('T')
|
|
16
|
+
|
|
17
|
+
class Factory(Generic[T]):
|
|
18
|
+
__slots__ = ["__container", "__service", "__implementation", "__ctor", "__dependencies"]
|
|
19
|
+
|
|
20
|
+
def __init__(self, container: Container, service: type[T], implementation: type[T] | Callable[..., T] | T | None = None):
|
|
21
|
+
self.__container = container
|
|
22
|
+
self.__service = service
|
|
23
|
+
self.__implementation = service if implementation is None else implementation
|
|
24
|
+
self.__dependencies: dict[str, Dependency] = {}
|
|
25
|
+
|
|
26
|
+
if not implementation:
|
|
27
|
+
if hasattr(service, "__origin__"):
|
|
28
|
+
sig = reflect_function(getattr(service, "__call__"), service)
|
|
29
|
+
else:
|
|
30
|
+
sig = get_constructor(service)
|
|
31
|
+
elif isinstance(implementation, type):
|
|
32
|
+
sig = get_constructor(cast(type, implementation))
|
|
33
|
+
if not service is NamedService and not issubclass_typing(implementation, service):
|
|
34
|
+
raise ImplementationException(service, implementation, "Implementation class is not derived from service")
|
|
35
|
+
elif implementation is service:
|
|
36
|
+
raise ImplementationException(service, implementation, "Implementation equals service")
|
|
37
|
+
elif isinstance_typing(implementation, service, recursive=True):
|
|
38
|
+
sig = None
|
|
39
|
+
elif callable(implementation):
|
|
40
|
+
sig = reflect_function(implementation)
|
|
41
|
+
if sig.return_type:
|
|
42
|
+
implementation_type, _ = get_optional_type(sig.return_type)
|
|
43
|
+
else:
|
|
44
|
+
implementation_type = None
|
|
45
|
+
|
|
46
|
+
if service is NamedService:
|
|
47
|
+
pass
|
|
48
|
+
elif not implementation_type:
|
|
49
|
+
raise ImplementationException(service, implementation, "Implementation is missing return annotation")
|
|
50
|
+
elif not issubclass_typing(implementation_type, service):
|
|
51
|
+
raise ImplementationException(service, implementation, "Implementation does not resolve into a class derived from service")
|
|
52
|
+
elif service is NamedService:
|
|
53
|
+
sig = None
|
|
54
|
+
else:
|
|
55
|
+
raise ImplementationException(service, implementation, "Implementation does not resolve into a class derived from service")
|
|
56
|
+
|
|
57
|
+
if sig != None:
|
|
58
|
+
parameters = [ p for p in sig.parameters if p.kind == ParameterKind.POSITIONAL_OR_KEYWORD ]
|
|
59
|
+
missing_annotations = [ p.name for p in parameters if not p.parameter_type ]
|
|
60
|
+
self.__dependencies = { p.name: Dependency(p.name, p.parameter_type, p.default) for p in parameters }
|
|
61
|
+
|
|
62
|
+
if len(missing_annotations) > 0:
|
|
63
|
+
parameters_missing_ann = ", ".join([f"'{p}'" for p in missing_annotations])
|
|
64
|
+
raise ImplementationException(service, implementation, f"Parameter(s) {parameters_missing_ann} missing annotation")
|
|
65
|
+
|
|
66
|
+
def ctor(scope: Scope | None = None) -> T:
|
|
67
|
+
provider = container.provider()
|
|
68
|
+
try:
|
|
69
|
+
args = [
|
|
70
|
+
provider._provide( # pyright: ignore[reportPrivateUsage]
|
|
71
|
+
ServiceRequest(
|
|
72
|
+
p.name,
|
|
73
|
+
cast(type[T], p.parameter_type),
|
|
74
|
+
None if p.default is Undefined else p.default
|
|
75
|
+
),
|
|
76
|
+
scope
|
|
77
|
+
) for p in parameters
|
|
78
|
+
]
|
|
79
|
+
return cast(Callable[[Any], T], self.__implementation)(*args)
|
|
80
|
+
except ProvideException as ex:
|
|
81
|
+
failed_args = {
|
|
82
|
+
p.name: cast(type, p.parameter_type)
|
|
83
|
+
for p in parameters
|
|
84
|
+
if not provider.provides(cast(type, p.parameter_type))
|
|
85
|
+
}
|
|
86
|
+
raise FactoryProvideException(service, failed_args) from ex
|
|
87
|
+
|
|
88
|
+
self.__ctor = ctor
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def container(self) -> Container:
|
|
92
|
+
return self.__container # pragma: no cover
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def service(self) -> type[T]:
|
|
96
|
+
return self .__service
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def dependencies(self) -> dict[str, Dependency]:
|
|
100
|
+
return self.__dependencies
|
|
101
|
+
|
|
102
|
+
def provide(self, scope: Scope | None = None) -> T:
|
|
103
|
+
return self.__ctor(scope)
|
|
104
|
+
|
|
105
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import TypeVar, Callable
|
|
3
|
+
|
|
4
|
+
from di.core.scope import Scope
|
|
5
|
+
from di.core.factories.factory import Factory
|
|
6
|
+
from di.core.protocols.container import Container
|
|
7
|
+
|
|
8
|
+
T = TypeVar('T')
|
|
9
|
+
|
|
10
|
+
class ScopedFactory(Factory[T]):
|
|
11
|
+
__slots__ = []
|
|
12
|
+
|
|
13
|
+
def __init__(self, container: Container, service: type[T], implementation: type[T] | Callable[..., T] | None = None):
|
|
14
|
+
super().__init__(container, service, implementation)
|
|
15
|
+
|
|
16
|
+
def provide(self, scope: Scope | None = None) -> T:
|
|
17
|
+
scope = scope or self.container.default_scope
|
|
18
|
+
return scope.provide(self.service, lambda : Factory[T].provide(self, scope))
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import TypeVar, Callable, cast
|
|
2
|
+
from typingutils import is_type, isinstance_typing
|
|
3
|
+
|
|
4
|
+
from di.core.log import LOG
|
|
5
|
+
from di.core.helpers import get_service_name
|
|
6
|
+
from di.core.scope import Scope
|
|
7
|
+
from di.core.named_service import NamedService
|
|
8
|
+
from di.core.factories.factory import Factory
|
|
9
|
+
from di.core.protocols.container import Container
|
|
10
|
+
|
|
11
|
+
T = TypeVar('T')
|
|
12
|
+
|
|
13
|
+
class SingletonFactory(Factory[T]):
|
|
14
|
+
__slots__ = ["__inst"]
|
|
15
|
+
|
|
16
|
+
def __init__(self, container: Container, service: type[T], implementation: type[T] | Callable[..., T] | T | None = None):
|
|
17
|
+
super().__init__(container, service, implementation)
|
|
18
|
+
|
|
19
|
+
if implementation and is_type(service) and isinstance_typing(implementation, service, recursive=True):
|
|
20
|
+
self.__inst: T | None = cast(T, implementation)
|
|
21
|
+
elif callable(implementation):
|
|
22
|
+
self.__inst: T | None = None
|
|
23
|
+
elif service is NamedService:
|
|
24
|
+
self.__inst: T | None = implementation
|
|
25
|
+
else:
|
|
26
|
+
self.__inst: T | None = None
|
|
27
|
+
|
|
28
|
+
def provide(self, scope: Scope | None = None) -> T:
|
|
29
|
+
if self.__inst is None:
|
|
30
|
+
LOG.debug(f"Creating singleton object for service '{get_service_name(self.service)}'")
|
|
31
|
+
self.__inst = super().provide(scope)
|
|
32
|
+
return self.__inst
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import TypeVar, Callable
|
|
2
|
+
|
|
3
|
+
from di.core.scope import Scope
|
|
4
|
+
from di.core.factories.factory import Factory
|
|
5
|
+
from di.core.protocols.container import Container
|
|
6
|
+
|
|
7
|
+
T = TypeVar('T')
|
|
8
|
+
|
|
9
|
+
class TransientFactory(Factory[T]):
|
|
10
|
+
__slots__ = []
|
|
11
|
+
|
|
12
|
+
def __init__(self, container: Container, service: type[T], implementation: type[T] | Callable[..., T] | None = None):
|
|
13
|
+
super().__init__(container, service, implementation)
|
|
14
|
+
|
|
15
|
+
def provide(self, scope: Scope | None = None) -> T:
|
|
16
|
+
return Factory[T].provide(self, scope)
|
|
17
|
+
|
di/core/factory.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import TypeVar, Generic
|
|
2
|
+
|
|
3
|
+
from di.core.scope import Scope
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
class Factory(Generic[T]):
|
|
8
|
+
"""
|
|
9
|
+
The Factory[T] class is used to get the Provider to provide a factory instead of a service implementation ie. a lazy implementation.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
class SomeClass:
|
|
13
|
+
def __init__(self, service: Factory[Service]):
|
|
14
|
+
svc = service() # => Service
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __new__(cls):
|
|
18
|
+
raise Exception("The Factory class is not intended for instantation") # pragma: no cover
|
|
19
|
+
|
|
20
|
+
def provide(self, scope: Scope | None = None) -> T: # pragma: no cover
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
def __call__(self, scope: Scope | None = None) -> T: # pragma: no cover
|
|
24
|
+
raise NotImplementedError
|
di/core/helpers.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import MutableMapping, Protocol, TypeVar, cast, runtime_checkable
|
|
2
|
+
from di.core.factory import Factory
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from typingutils import AnyType, issubclass_typing, get_type_name
|
|
5
|
+
|
|
6
|
+
from di.core.factory import Factory
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T', covariant=True)
|
|
10
|
+
|
|
11
|
+
@runtime_checkable
|
|
12
|
+
class ContextManager(Protocol[T]): # pragma: no cover
|
|
13
|
+
def __enter__(self) -> T: ...
|
|
14
|
+
def __exit__(self, __exc_type: type[BaseException] | None, __exc_value: BaseException | None, __traceback: TracebackType | None) -> None: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_service_name(service: AnyType | str) -> str: # pragma: no cover
|
|
18
|
+
if isinstance(service, str):
|
|
19
|
+
return service
|
|
20
|
+
elif hasattr(service, "__name__"):
|
|
21
|
+
return getattr(service, "__name__")
|
|
22
|
+
else:
|
|
23
|
+
return get_type_name(service)
|
|
24
|
+
|
|
25
|
+
def clean_service_name(service: str) -> str:
|
|
26
|
+
return service.lower()
|
|
27
|
+
|
|
28
|
+
def get_provided_service_from_annotation(annotation: AnyType) -> tuple[AnyType, bool]:
|
|
29
|
+
if issubclass_typing(annotation, Factory):
|
|
30
|
+
return cast(tuple[type], getattr(annotation, "__args__"))[0], True
|
|
31
|
+
else:
|
|
32
|
+
return annotation, False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__CONTEXT_SERVICES__: MutableMapping[type, bool] = {}
|
|
36
|
+
|
|
37
|
+
def is_context(cls: type) -> bool:
|
|
38
|
+
if not cls in __CONTEXT_SERVICES__:
|
|
39
|
+
# reflection = reflect_class(cls)
|
|
40
|
+
|
|
41
|
+
# if "__enter__" in reflection.functions and "__exit__" in reflection.functions:
|
|
42
|
+
# result = reflection.functions["__enter__"].return_type is cls
|
|
43
|
+
# else:
|
|
44
|
+
# result = False
|
|
45
|
+
if issubclass(cls, ContextManager):
|
|
46
|
+
result = True
|
|
47
|
+
else:
|
|
48
|
+
result = False
|
|
49
|
+
|
|
50
|
+
__CONTEXT_SERVICES__[cls] = result
|
|
51
|
+
return __CONTEXT_SERVICES__[cls]
|
di/core/log.py
ADDED
di/core/named_service.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Protocol, TypeVar, runtime_checkable
|
|
2
|
+
|
|
3
|
+
from di.core.protocols.provider import Provider
|
|
4
|
+
from di.core.protocols.factory import Factory
|
|
5
|
+
from di.core.scope import Scope
|
|
6
|
+
from di.core.service_request import ServiceRequest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
@runtime_checkable
|
|
12
|
+
class Container(Protocol): # pragma: no cover
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def default_scope(self) -> Scope:
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
def provider(self) -> Provider:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def _defines(self, service: str | type) -> bool:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def _get(self, service: type[T] | ServiceRequest[T]) -> Factory[T] | T | None:
|
|
25
|
+
...
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Protocol, Any, TypeVar, overload, runtime_checkable
|
|
2
|
+
|
|
3
|
+
from di.core.scope import Scope
|
|
4
|
+
from di.core.service_request import ServiceRequest
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
@runtime_checkable
|
|
9
|
+
class Provider(Protocol): # pragma: no cover
|
|
10
|
+
|
|
11
|
+
def provides(self, service: str | type[Any]) -> bool:
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
@overload
|
|
15
|
+
def provide(self, service: type[T]) -> T:
|
|
16
|
+
...
|
|
17
|
+
@overload
|
|
18
|
+
def provide(self, service: type[T], scope: Scope) -> T:
|
|
19
|
+
...
|
|
20
|
+
def provide(self, service: type[T], scope: Scope | None = None) -> T:
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def _provide(self, service: type[T] | ServiceRequest[T], scope: Scope | None = None) -> T:
|
|
24
|
+
...
|
di/core/provider.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import TypeVar, Any, cast, overload
|
|
2
|
+
from typingutils import issubclass_typing, get_optional_type
|
|
3
|
+
|
|
4
|
+
from di.core.exceptions.factory_provide_exception import FactoryProvideException
|
|
5
|
+
from di.core.scope import Scope
|
|
6
|
+
from di.core.factory import Factory as PublicFactory
|
|
7
|
+
from di.core.factories.factory import Factory
|
|
8
|
+
from di.core.service_request import ServiceRequest
|
|
9
|
+
from di.core.protocols.container import Container
|
|
10
|
+
|
|
11
|
+
T = TypeVar('T')
|
|
12
|
+
|
|
13
|
+
class Provider:
|
|
14
|
+
"""
|
|
15
|
+
The Provider is connected to the Container and provides the services defined.
|
|
16
|
+
"""
|
|
17
|
+
__slots__ = ["__container"]
|
|
18
|
+
|
|
19
|
+
def __init__(self, container: Container):
|
|
20
|
+
self.__container = container
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def provides(self, service: str | type[Any]) -> bool:
|
|
24
|
+
"""
|
|
25
|
+
Indicates whether or not the specified service can be provided.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
service (str | type[Any]) The service interface or name.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
bool: Returns True if specified service can be provided.
|
|
32
|
+
"""
|
|
33
|
+
return self.__container._defines(service) # pyright: ignore[reportPrivateUsage]
|
|
34
|
+
|
|
35
|
+
@overload
|
|
36
|
+
def provide(self, service: type[T]) -> T:
|
|
37
|
+
"""
|
|
38
|
+
Provides the specified service.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
service (type[Any]) The service interface.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
...
|
|
45
|
+
@overload
|
|
46
|
+
def provide(self, service: type[T], scope: Scope) -> T:
|
|
47
|
+
"""
|
|
48
|
+
Provides the specified service.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
service (type[Any]) The service interface.
|
|
52
|
+
scope (Scope) A custom scope.
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
def provide(self, service: type[T], scope: Scope | None = None) -> T:
|
|
56
|
+
return self._provide(service, scope)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _provide(self, service: type[T] | ServiceRequest[T], scope: Scope | None = None) -> T:
|
|
60
|
+
scope = scope or self.__container.default_scope
|
|
61
|
+
svc_type = service.type if isinstance(service, ServiceRequest) else service
|
|
62
|
+
svc_type, is_optional = get_optional_type(svc_type)
|
|
63
|
+
|
|
64
|
+
if svc_type and issubclass_typing(svc_type, PublicFactory):
|
|
65
|
+
def factory() -> T:
|
|
66
|
+
return self._provide(cast(type[T], getattr(svc_type, "__args__")[0]))
|
|
67
|
+
return factory # pyright: ignore[reportReturnType]
|
|
68
|
+
else:
|
|
69
|
+
result = self.__container._get(cast(type[T], service)) # pyright: ignore[reportPrivateUsage]
|
|
70
|
+
|
|
71
|
+
if result == None:
|
|
72
|
+
# optional requirements may return None
|
|
73
|
+
if is_optional:
|
|
74
|
+
return None # pyright: ignore[reportReturnType]
|
|
75
|
+
else:
|
|
76
|
+
raise FactoryProvideException(svc_type) # pragma: no cover
|
|
77
|
+
|
|
78
|
+
elif isinstance(result, Factory):
|
|
79
|
+
result = cast(T | None, result.provide(scope))
|
|
80
|
+
if result or is_optional:
|
|
81
|
+
return cast(T, result)
|
|
82
|
+
else:
|
|
83
|
+
raise FactoryProvideException(svc_type)
|
|
84
|
+
else:
|
|
85
|
+
return cast(T, result)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
from typingutils import AnyFunction
|
|
3
|
+
from types import FrameType, MethodType
|
|
4
|
+
from sys import modules
|
|
5
|
+
from inspect import Parameter as InspectParameter, FrameInfo, signature as get_signature, stack, unwrap
|
|
6
|
+
|
|
7
|
+
from di.core.reflection.undefined import Undefined
|
|
8
|
+
from di.core.reflection.signature import Signature
|
|
9
|
+
from di.core.reflection.parameter import Parameter
|
|
10
|
+
from di.core.reflection.parameter_kind import ParameterKind
|
|
11
|
+
from di.core.reflection.parameter_mapper import ParameterMapper
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve(
|
|
15
|
+
annotation: str,
|
|
16
|
+
globals: dict[str, Any] | None = None,
|
|
17
|
+
builtins: dict[str, Any] | None = None,
|
|
18
|
+
locals: dict[str, Any] | None = None
|
|
19
|
+
) -> Any:
|
|
20
|
+
if globals: # pragma: no cover
|
|
21
|
+
globals = { **globals , **(builtins or __builtins__)}
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
result = eval(annotation, globals, locals)
|
|
25
|
+
return result
|
|
26
|
+
except:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
# fallback
|
|
30
|
+
for frame in stack()[1:]: # pragma: no cover
|
|
31
|
+
if frame.filename == __file__:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
globals = { **frame.frame.f_globals , **frame.frame.f_builtins}
|
|
35
|
+
locals = frame.frame.f_locals
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
return eval(annotation, globals, locals)
|
|
39
|
+
except:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
raise Exception(f"Unable to resolve {annotation}") # pragma: no cover
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_frame(fn: Any, stack: list[FrameInfo], cls: Any | None) -> FrameType | None: # pragma: no cover
|
|
48
|
+
module = None
|
|
49
|
+
|
|
50
|
+
if hasattr(fn, "__module__"):
|
|
51
|
+
module = modules[getattr(fn, "__module__")]
|
|
52
|
+
elif cls and hasattr(cls, "__module__"):
|
|
53
|
+
module = modules[getattr(cls, "__module__")]
|
|
54
|
+
|
|
55
|
+
if module and hasattr(module, "__file__"):
|
|
56
|
+
for frame in stack:
|
|
57
|
+
if frame.filename == module.__file__:
|
|
58
|
+
frame_locals = frame.frame.f_locals.values()
|
|
59
|
+
if cls is not None and cls in frame_locals:
|
|
60
|
+
return frame.frame
|
|
61
|
+
elif fn in frame_locals:
|
|
62
|
+
return frame.frame
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def reflect_function(fn: AnyFunction, cls: object | None = None) -> Signature:
|
|
66
|
+
fn = unwrap(fn)
|
|
67
|
+
sig = get_signature(fn)
|
|
68
|
+
|
|
69
|
+
if hasattr(fn, "__self__") and ( self := getattr(fn, "__self__") ):
|
|
70
|
+
cls = self
|
|
71
|
+
|
|
72
|
+
parameters = list(sig.parameters.values())
|
|
73
|
+
globals: dict[str, Any] | None = getattr(fn, "__globals__") if hasattr(fn, "__globals__") else None
|
|
74
|
+
builtins: dict[str, Any] | None = getattr(fn, "__builtins__") if hasattr(fn, "__builtins__") else None
|
|
75
|
+
locals: dict[str, Any] | None = None
|
|
76
|
+
|
|
77
|
+
if frame := get_frame(fn, stack()[1:], cls): # pragma: no cover
|
|
78
|
+
locals = frame.f_locals
|
|
79
|
+
|
|
80
|
+
if parameters:
|
|
81
|
+
first_param = parameters[0].name.lower()
|
|
82
|
+
|
|
83
|
+
if isinstance(fn, MethodType) and first_param in ("self", "cls"):
|
|
84
|
+
parameters = parameters[1:] # pragma: no cover
|
|
85
|
+
elif first_param in ("self", "cls") and hasattr(fn, "__self__"):
|
|
86
|
+
parameters = parameters[1:] # pragma: no cover
|
|
87
|
+
elif first_param == "self":
|
|
88
|
+
if hasattr(fn, "__call__") and ( call := getattr(fn, "__call__") ): # pragma: no cover
|
|
89
|
+
if hasattr(call, "__text_signature__") and ( text_sig := getattr(call, "__text_signature__") ):
|
|
90
|
+
if cast(str, text_sig).startswith("($self"):
|
|
91
|
+
parameters = parameters[1:]
|
|
92
|
+
|
|
93
|
+
for index, parameter in enumerate(parameters):
|
|
94
|
+
changed = False
|
|
95
|
+
|
|
96
|
+
if isinstance(parameter.annotation, str):
|
|
97
|
+
parameter_type = resolve(parameter.annotation, globals, builtins, locals)
|
|
98
|
+
parameter = parameter.replace(annotation = parameter_type)
|
|
99
|
+
changed = True
|
|
100
|
+
|
|
101
|
+
if changed:
|
|
102
|
+
parameters[index] = parameter
|
|
103
|
+
|
|
104
|
+
if sig.return_annotation is InspectParameter.empty:
|
|
105
|
+
return_type = Undefined
|
|
106
|
+
elif isinstance(sig.return_annotation, str):
|
|
107
|
+
return_type = resolve(sig.return_annotation, globals, builtins, locals)
|
|
108
|
+
else:
|
|
109
|
+
return_type = sig.return_annotation
|
|
110
|
+
|
|
111
|
+
return Signature(
|
|
112
|
+
ParameterMapper(
|
|
113
|
+
[
|
|
114
|
+
Parameter(
|
|
115
|
+
p.name,
|
|
116
|
+
ParameterKind(p.kind),
|
|
117
|
+
Undefined if p.annotation is InspectParameter.empty else p.annotation,
|
|
118
|
+
Undefined if p.default is InspectParameter.empty else p.default
|
|
119
|
+
) for p in parameters
|
|
120
|
+
]
|
|
121
|
+
), return_type
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_constructor(cls: type[Any]) -> Signature:
|
|
126
|
+
return reflect_function(getattr(cls, "__init__"), cls)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
from typingutils import get_type_name
|
|
3
|
+
from inspect import Parameter as InspectParameter
|
|
4
|
+
|
|
5
|
+
from di.core.reflection.undefined import Undefined
|
|
6
|
+
from di.core.reflection.parameter_kind import ParameterKind
|
|
7
|
+
|
|
8
|
+
class Parameter:
|
|
9
|
+
__slots__ = ["__name", "__kind", "__doc", "__type", "__default"]
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
name: str,
|
|
14
|
+
kind: ParameterKind,
|
|
15
|
+
parameter_type: type[Any],
|
|
16
|
+
default: Any
|
|
17
|
+
):
|
|
18
|
+
self.__name = name
|
|
19
|
+
self.__kind = kind
|
|
20
|
+
self.__type = parameter_type
|
|
21
|
+
self.__default = default
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def name(self) -> str:
|
|
25
|
+
"""The parameter name
|
|
26
|
+
"""
|
|
27
|
+
return self.__name
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def kind(self) -> ParameterKind:
|
|
31
|
+
"""The parameter kind
|
|
32
|
+
"""
|
|
33
|
+
return self.__kind
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def parameter_type(self) -> type[Any]:
|
|
37
|
+
"""The parameter type
|
|
38
|
+
"""
|
|
39
|
+
return self.__type
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def default(self) -> Any:
|
|
43
|
+
"""The parameter default
|
|
44
|
+
"""
|
|
45
|
+
return self.__default
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str:
|
|
48
|
+
str_type = f": {get_type_name(cast(type, self.__type))}" if self.__type and self.__type != Undefined else ""
|
|
49
|
+
str_default = f"={self.__default}" if self.__default else ""
|
|
50
|
+
return f"{self.__name}{str_type}{str_default}"
|
|
51
|
+
|
|
52
|
+
def __eq__(self, o: object) -> bool: # pragma: no cover
|
|
53
|
+
if isinstance(o, Parameter):
|
|
54
|
+
return (
|
|
55
|
+
self.name == o.name
|
|
56
|
+
and self.kind == o.kind
|
|
57
|
+
and self.parameter_type == o.parameter_type
|
|
58
|
+
)
|
|
59
|
+
elif isinstance(o, InspectParameter):
|
|
60
|
+
return (
|
|
61
|
+
self.name == o.name
|
|
62
|
+
and cast(int, self.kind) == o.kind
|
|
63
|
+
and self.parameter_type == o.annotation
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return False
|