module-dependency 0.0.3__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/core/__init__.py +18 -0
- dependency/core/container/__init__.py +24 -0
- dependency/core/container/injectable.py +20 -0
- dependency/core/declaration/__init__.py +13 -0
- dependency/core/declaration/base.py +19 -0
- dependency/core/declaration/component.py +54 -0
- dependency/core/declaration/dependent.py +47 -0
- dependency/core/declaration/provider.py +90 -0
- dependency/core/exceptions.py +2 -0
- dependency/core/loader.py +27 -0
- dependency/core/module/__init__.py +6 -0
- dependency/core/module/base.py +100 -0
- dependency/core/resolver/__init__.py +24 -0
- dependency/core/resolver/errors.py +27 -0
- dependency/core/resolver/utils.py +24 -0
- module_dependency-0.0.3.dist-info/METADATA +153 -0
- module_dependency-0.0.3.dist-info/RECORD +19 -0
- module_dependency-0.0.3.dist-info/WHEEL +4 -0
- module_dependency-0.0.3.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dependency_injector import providers
|
|
2
|
+
from dependency.core.module.base import Module, module
|
|
3
|
+
from dependency.core.declaration.component import Component, component
|
|
4
|
+
from dependency.core.declaration.provider import HasDependent, Provider, provider
|
|
5
|
+
from dependency.core.declaration.dependent import Dependent, dependent
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"providers",
|
|
9
|
+
"Module",
|
|
10
|
+
"module",
|
|
11
|
+
"Component",
|
|
12
|
+
"component",
|
|
13
|
+
"Provider",
|
|
14
|
+
"provider",
|
|
15
|
+
"Dependent",
|
|
16
|
+
"dependent",
|
|
17
|
+
"HasDependent",
|
|
18
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from dependency_injector import containers, providers
|
|
3
|
+
|
|
4
|
+
class Container(containers.DynamicContainer):
|
|
5
|
+
config: providers.Configuration = providers.Configuration()
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def from_dict(
|
|
9
|
+
config: dict[str, Any],
|
|
10
|
+
required: bool = False
|
|
11
|
+
) -> 'Container':
|
|
12
|
+
container: Container = Container()
|
|
13
|
+
container.config.from_dict(config, required)
|
|
14
|
+
return container
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def from_json(
|
|
18
|
+
file: str,
|
|
19
|
+
required: bool = False,
|
|
20
|
+
envs_required: bool = False
|
|
21
|
+
) -> 'Container':
|
|
22
|
+
container: Container = Container()
|
|
23
|
+
container.config.from_json(file, required, envs_required)
|
|
24
|
+
return container
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dependency_injector import containers, providers
|
|
2
|
+
from dependency.core.container import Container
|
|
3
|
+
|
|
4
|
+
class Injectable:
|
|
5
|
+
def __init__(self,
|
|
6
|
+
inject_name: str,
|
|
7
|
+
inject_cls: type,
|
|
8
|
+
provided_cls: type,
|
|
9
|
+
provider_cls: type = providers.Singleton
|
|
10
|
+
) -> None:
|
|
11
|
+
class Container(containers.DynamicContainer):
|
|
12
|
+
config = providers.Configuration()
|
|
13
|
+
service = provider_cls(provided_cls, config)
|
|
14
|
+
self.inject_name = inject_name
|
|
15
|
+
self.inject_cls = inject_cls
|
|
16
|
+
self.container = Container
|
|
17
|
+
|
|
18
|
+
def populate_container(self, container: Container) -> None:
|
|
19
|
+
setattr(container, self.inject_name, providers.Container(self.container, config=container.config))
|
|
20
|
+
container.wire(modules=[self.inject_cls])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dependency.core.declaration.component import Component, component
|
|
2
|
+
from dependency.core.declaration.provider import HasDependent, Provider, provider
|
|
3
|
+
from dependency.core.declaration.dependent import Dependent, dependent
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Component",
|
|
7
|
+
"component",
|
|
8
|
+
"Provider",
|
|
9
|
+
"provider",
|
|
10
|
+
"Dependent",
|
|
11
|
+
"dependent",
|
|
12
|
+
"HasDependent",
|
|
13
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
|
|
3
|
+
class ABCComponent(ABC):
|
|
4
|
+
def __init__(self, base_cls: type) -> None:
|
|
5
|
+
self.base_cls: type = base_cls
|
|
6
|
+
|
|
7
|
+
def __repr__(self) -> str:
|
|
8
|
+
return self.base_cls.__name__
|
|
9
|
+
|
|
10
|
+
class ABCProvider(ABC):
|
|
11
|
+
def __init__(self, provided_cls: type) -> None:
|
|
12
|
+
self.provided_cls: type = provided_cls
|
|
13
|
+
|
|
14
|
+
def __repr__(self) -> str:
|
|
15
|
+
return self.provided_cls.__name__
|
|
16
|
+
|
|
17
|
+
class ABCDependent(ABC):
|
|
18
|
+
def __repr__(self) -> str:
|
|
19
|
+
return self.__class__.__name__
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Any, Callable, Optional
|
|
2
|
+
from dependency_injector.wiring import Provide
|
|
3
|
+
from dependency.core.exceptions import DependencyError
|
|
4
|
+
from dependency.core.declaration.base import ABCComponent, ABCProvider
|
|
5
|
+
|
|
6
|
+
class Component(ABCComponent):
|
|
7
|
+
"""Component Base Class
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self, base_cls: type) -> None:
|
|
10
|
+
super().__init__(base_cls=base_cls)
|
|
11
|
+
self.__provider: Optional[ABCProvider] = None
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def provider(self) -> Optional[ABCProvider]:
|
|
15
|
+
return self.__provider
|
|
16
|
+
|
|
17
|
+
@provider.setter
|
|
18
|
+
def provider(self, provider: ABCProvider) -> None:
|
|
19
|
+
if self.__provider:
|
|
20
|
+
raise DependencyError(f"Component {self} is already provided by {self.__provider}. Attempted to set new provider: {provider}")
|
|
21
|
+
self.__provider = provider
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def provide(service: Any = None) -> Any: # TODO: provide signature
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def component(interface: type) -> Callable[[type[Component]], Component]:
|
|
28
|
+
"""Decorator for Component class
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
interface (type): Interface class to be used as a base class for the component.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
TypeError: If the wrapped class is not a subclass of Component.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Callable[[type[Component]], Component]: Decorator function that wraps the component class.
|
|
38
|
+
"""
|
|
39
|
+
def wrap(cls: type[Component]) -> Component:
|
|
40
|
+
if not issubclass(cls, Component):
|
|
41
|
+
raise TypeError(f"Class {cls} is not a subclass of Component")
|
|
42
|
+
|
|
43
|
+
class WrapComponent(cls): # type: ignore
|
|
44
|
+
def __init__(self) -> None:
|
|
45
|
+
super().__init__(base_cls=interface)
|
|
46
|
+
|
|
47
|
+
def provide(self,
|
|
48
|
+
service: Any = Provide[f"{interface.__name__}.service"]
|
|
49
|
+
) -> Any:
|
|
50
|
+
if issubclass(service.__class__, Provide):
|
|
51
|
+
raise DependencyError(f"Component {self} was not provided")
|
|
52
|
+
return service
|
|
53
|
+
return WrapComponent()
|
|
54
|
+
return wrap
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Callable, Sequence, cast
|
|
2
|
+
from dependency.core.declaration.base import ABCComponent, ABCProvider, ABCDependent
|
|
3
|
+
|
|
4
|
+
class Dependent(ABCDependent):
|
|
5
|
+
"""Dependent Base Class
|
|
6
|
+
"""
|
|
7
|
+
_dependency_imports: Sequence[ABCComponent]
|
|
8
|
+
_dependency_resolved: bool = False
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
def resolve_dependent(cls, providers: Sequence[ABCProvider]) -> list[str]:
|
|
12
|
+
if cls._dependency_resolved:
|
|
13
|
+
return []
|
|
14
|
+
cls._dependency_resolved = True
|
|
15
|
+
|
|
16
|
+
return [
|
|
17
|
+
component.__repr__()
|
|
18
|
+
for component in cls._dependency_imports
|
|
19
|
+
if not any(
|
|
20
|
+
issubclass(provider.provided_cls, component.base_cls)
|
|
21
|
+
for provider in providers
|
|
22
|
+
)
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
def dependent(
|
|
26
|
+
imports: Sequence[type[ABCComponent]] = [],
|
|
27
|
+
) -> Callable[[type[Dependent]], type[Dependent]]:
|
|
28
|
+
"""Decorator for Dependent class
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
imports (Sequence[type[Component]], optional): List of components to be imported by the dependent. Defaults to [].
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
TypeError: If the wrapped class is not a subclass of Dependent.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Callable[[type[Dependent]], type[Dependent]]: Decorator function that wraps the dependent class.
|
|
38
|
+
"""
|
|
39
|
+
# Cast due to mypy not supporting class decorators
|
|
40
|
+
_imports = cast(Sequence[ABCComponent], imports)
|
|
41
|
+
def wrap(cls: type[Dependent]) -> type[Dependent]:
|
|
42
|
+
if not issubclass(cls, Dependent):
|
|
43
|
+
raise TypeError(f"Class {cls} is not a subclass of Dependent")
|
|
44
|
+
|
|
45
|
+
cls._dependency_imports = _imports
|
|
46
|
+
return cls
|
|
47
|
+
return wrap
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from pprint import pformat
|
|
2
|
+
from typing import Callable, Optional, cast
|
|
3
|
+
from dependency_injector import providers
|
|
4
|
+
from dependency.core.container.injectable import Container, Injectable
|
|
5
|
+
from dependency.core.declaration.base import ABCProvider
|
|
6
|
+
from dependency.core.declaration.component import Component
|
|
7
|
+
from dependency.core.declaration.dependent import Dependent
|
|
8
|
+
|
|
9
|
+
class Provider(ABCProvider):
|
|
10
|
+
"""Provider Base Class
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self,
|
|
13
|
+
imports: list[Component],
|
|
14
|
+
dependents: list[type[Dependent]],
|
|
15
|
+
provided_cls: type,
|
|
16
|
+
inject: Injectable,
|
|
17
|
+
) -> None:
|
|
18
|
+
super().__init__(provided_cls=provided_cls)
|
|
19
|
+
self.provider = inject
|
|
20
|
+
self.imports = imports
|
|
21
|
+
self.dependents = dependents
|
|
22
|
+
|
|
23
|
+
self.__providers: list['Provider'] = []
|
|
24
|
+
|
|
25
|
+
def resolve_dependents(self, dependents: list[type[Dependent]]) -> None:
|
|
26
|
+
self.unresolved_dependents: dict[str, list[str]] = {}
|
|
27
|
+
for dependent in dependents:
|
|
28
|
+
unresolved = dependent.resolve_dependent(self.__providers)
|
|
29
|
+
if len(unresolved) > 0:
|
|
30
|
+
self.unresolved_dependents[dependent.__name__] = unresolved
|
|
31
|
+
if len(self.unresolved_dependents) > 0:
|
|
32
|
+
named_dependents = pformat(self.unresolved_dependents)
|
|
33
|
+
raise TypeError(f"Provider {self} has unresolved dependents:\n{named_dependents}")
|
|
34
|
+
|
|
35
|
+
def resolve(self, container: Container, providers: list['Provider']) -> None:
|
|
36
|
+
self.__providers = providers
|
|
37
|
+
self.resolve_dependents(self.dependents)
|
|
38
|
+
self.provider.populate_container(container)
|
|
39
|
+
|
|
40
|
+
class HasDependent():
|
|
41
|
+
_dependency_provider: Optional[Provider] = None
|
|
42
|
+
|
|
43
|
+
def declare_dependents(self, dependents: list[type[Dependent]]) -> None:
|
|
44
|
+
if self._dependency_provider is None:
|
|
45
|
+
raise TypeError(f"Class {self} provider is not declared yet")
|
|
46
|
+
self._dependency_provider.resolve_dependents(dependents)
|
|
47
|
+
|
|
48
|
+
def provider(
|
|
49
|
+
component: type[Component],
|
|
50
|
+
imports: list[type[Component]] = [],
|
|
51
|
+
dependents: list[type[Dependent]] = [],
|
|
52
|
+
provider: type[providers.Provider] = providers.Singleton
|
|
53
|
+
) -> Callable[[type], Provider]:
|
|
54
|
+
"""Decorator for Provider class
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
component (type[Component]): Component class to be used as a base class for the provider.
|
|
58
|
+
imports (list[type[Component]], optional): List of components to be imported by the provider. Defaults to [].
|
|
59
|
+
dependents (list[type[Dependent]], optional): List of dependents to be declared by the provider. Defaults to [].
|
|
60
|
+
provider (type[providers.Provider], optional): Provider class to be used. Defaults to providers.Singleton.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
TypeError: If the wrapped class is not a subclass of Component declared base class.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Callable[[type], Provider]: Decorator function that wraps the provider class.
|
|
67
|
+
"""
|
|
68
|
+
# Cast due to mypy not supporting class decorators
|
|
69
|
+
_component = cast(Component, component)
|
|
70
|
+
_imports = cast(list[Component], imports)
|
|
71
|
+
def wrap(cls: type) -> Provider:
|
|
72
|
+
if not issubclass(cls, _component.base_cls):
|
|
73
|
+
raise TypeError(f"Class {cls} is not a subclass of {_component.base_cls}")
|
|
74
|
+
|
|
75
|
+
provider_wrap = Provider(
|
|
76
|
+
imports=_imports,
|
|
77
|
+
dependents=dependents,
|
|
78
|
+
provided_cls=cls,
|
|
79
|
+
inject=Injectable(
|
|
80
|
+
inject_name=_component.base_cls.__name__,
|
|
81
|
+
inject_cls=_component.__class__,
|
|
82
|
+
provided_cls=cls,
|
|
83
|
+
provider_cls=provider
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
_component.provider = provider_wrap
|
|
87
|
+
if issubclass(cls, HasDependent):
|
|
88
|
+
cls._dependency_provider = provider_wrap
|
|
89
|
+
return provider_wrap
|
|
90
|
+
return wrap
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pprint import pformat
|
|
3
|
+
from typing import cast
|
|
4
|
+
from dependency.core.module.base import Module
|
|
5
|
+
from dependency.core.container import Container
|
|
6
|
+
from dependency.core.resolver import resolve_dependency_layers
|
|
7
|
+
logger = logging.getLogger("DependencyLoader")
|
|
8
|
+
|
|
9
|
+
def resolve_dependency(container: Container, appmodule: type[Module]) -> None:
|
|
10
|
+
# Cast due to mypy not supporting class decorators
|
|
11
|
+
_appmodule = cast(Module, appmodule)
|
|
12
|
+
logger.info(f"Resolving dependencies in {_appmodule}")
|
|
13
|
+
|
|
14
|
+
unresolved_layers = _appmodule.init_providers()
|
|
15
|
+
resolved_layers = resolve_dependency_layers(unresolved_layers)
|
|
16
|
+
|
|
17
|
+
named_layers = pformat(resolved_layers)
|
|
18
|
+
logger.info(f"Resolved layers:\n{named_layers}")
|
|
19
|
+
|
|
20
|
+
for resolved_layer in resolved_layers:
|
|
21
|
+
for provider in resolved_layer:
|
|
22
|
+
provider.resolve(container, unresolved_layers)
|
|
23
|
+
|
|
24
|
+
container.check_dependencies()
|
|
25
|
+
container.init_resources()
|
|
26
|
+
_appmodule.init_bootstrap()
|
|
27
|
+
logger.info("Dependencies resolved and injected")
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Callable, cast
|
|
3
|
+
from dependency.core.exceptions import DependencyError
|
|
4
|
+
from dependency.core.declaration import Component, Provider
|
|
5
|
+
|
|
6
|
+
class Module(ABC):
|
|
7
|
+
"""Module Base Class
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self,
|
|
10
|
+
module_cls: type,
|
|
11
|
+
imports: list["Module"],
|
|
12
|
+
declaration: list[Component],
|
|
13
|
+
bootstrap: list[Component]
|
|
14
|
+
):
|
|
15
|
+
self.module_cls = module_cls
|
|
16
|
+
self.imports = imports
|
|
17
|
+
self.declaration = declaration
|
|
18
|
+
self.bootstrap = bootstrap
|
|
19
|
+
|
|
20
|
+
def declare_providers(self) -> None:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def modules(self) -> list["Module"]:
|
|
25
|
+
modules = self.imports
|
|
26
|
+
for module in self.imports:
|
|
27
|
+
modules.extend(module.modules)
|
|
28
|
+
return modules
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def providers(self) -> list[Provider]:
|
|
32
|
+
return [
|
|
33
|
+
cast(Provider, component.provider)
|
|
34
|
+
for component in self.declaration
|
|
35
|
+
if not component.provider is None
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def bootstraps(self) -> list[Component]:
|
|
40
|
+
return [
|
|
41
|
+
component
|
|
42
|
+
for component in self.bootstrap
|
|
43
|
+
if not component.provider is None
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
def init_providers(self) -> list[Provider]:
|
|
47
|
+
self.declare_providers()
|
|
48
|
+
providers: list[Provider] = self.providers
|
|
49
|
+
for module in self.imports:
|
|
50
|
+
providers.extend(module.init_providers())
|
|
51
|
+
return providers
|
|
52
|
+
|
|
53
|
+
def init_bootstrap(self) -> None:
|
|
54
|
+
for module in self.imports:
|
|
55
|
+
module.init_bootstrap()
|
|
56
|
+
for component in self.bootstraps:
|
|
57
|
+
try:
|
|
58
|
+
component.provide()
|
|
59
|
+
except Exception as e:
|
|
60
|
+
raise DependencyError(f"Failed to bootstrap {component}: {e}") from e
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
return self.module_cls.__name__
|
|
64
|
+
|
|
65
|
+
def module(
|
|
66
|
+
imports: list[type[Module]] = [],
|
|
67
|
+
declaration: list[type[Component]] = [],
|
|
68
|
+
bootstrap: list[type[Component]] = [],
|
|
69
|
+
) -> Callable[[type[Module]], Module]:
|
|
70
|
+
"""Decorator for Module class
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
imports (list[type[Module]], optional): List of modules to be imported by the module. Defaults to [].
|
|
74
|
+
declaration (list[type[Component]], optional): List of components to be declared by the module. Defaults to [].
|
|
75
|
+
bootstrap (list[type[Component]], optional): List of components to be bootstrapped by the module. Defaults to [].
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
TypeError: If the wrapped class is not a subclass of Module.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Callable[[type[Module]], Module]: Decorator function that wraps the module class.
|
|
82
|
+
"""
|
|
83
|
+
# Cast due to mypy not supporting class decorators
|
|
84
|
+
_declaration = cast(list[Component], declaration)
|
|
85
|
+
_imports = cast(list[Module], imports)
|
|
86
|
+
_bootstrap = cast(list[Component], bootstrap)
|
|
87
|
+
def wrap(cls: type[Module]) -> Module:
|
|
88
|
+
if not issubclass(cls, Module):
|
|
89
|
+
raise TypeError(f"Class {cls} is not a subclass of Module")
|
|
90
|
+
|
|
91
|
+
class WrapModule(cls): # type: ignore
|
|
92
|
+
def __init__(self) -> None:
|
|
93
|
+
super().__init__(
|
|
94
|
+
module_cls=cls,
|
|
95
|
+
declaration=_declaration,
|
|
96
|
+
imports=_imports,
|
|
97
|
+
bootstrap=_bootstrap,
|
|
98
|
+
)
|
|
99
|
+
return WrapModule()
|
|
100
|
+
return wrap
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dependency.core import Provider
|
|
2
|
+
from dependency.core.resolver.errors import raise_dependency_error
|
|
3
|
+
from dependency.core.resolver.utils import provider_is_resolved
|
|
4
|
+
|
|
5
|
+
def resolve_dependency_layers(unresolved_providers: list[Provider]) -> list[list[Provider]]:
|
|
6
|
+
resolved_layers: list[list[Provider]] = []
|
|
7
|
+
|
|
8
|
+
while unresolved_providers:
|
|
9
|
+
new_layer = [
|
|
10
|
+
provider
|
|
11
|
+
for provider in unresolved_providers
|
|
12
|
+
if provider_is_resolved(provider, resolved_layers)
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
if not new_layer:
|
|
16
|
+
raise_dependency_error(unresolved_providers, resolved_layers)
|
|
17
|
+
resolved_layers.append(new_layer)
|
|
18
|
+
|
|
19
|
+
unresolved_providers = [
|
|
20
|
+
provider
|
|
21
|
+
for provider in unresolved_providers
|
|
22
|
+
if provider not in new_layer
|
|
23
|
+
]
|
|
24
|
+
return resolved_layers
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dependency.core.exceptions import DependencyError
|
|
3
|
+
from dependency.core.declaration import Component
|
|
4
|
+
from dependency.core.declaration.provider import Provider
|
|
5
|
+
from dependency.core.resolver.utils import provider_unresolved
|
|
6
|
+
logger = logging.getLogger("DependencyLoader")
|
|
7
|
+
|
|
8
|
+
def provider_detect_error(
|
|
9
|
+
provider: Provider,
|
|
10
|
+
unresolved_providers: list[Provider],
|
|
11
|
+
resolved_layers: list[list[Provider]]
|
|
12
|
+
) -> tuple[list[Component], list[Component]]:
|
|
13
|
+
deps_circular: list[Component] = []
|
|
14
|
+
deps_missing: list[Component] = []
|
|
15
|
+
|
|
16
|
+
for dep in provider_unresolved(provider, resolved_layers):
|
|
17
|
+
# TODO: Check for circular dependencies
|
|
18
|
+
deps_missing.append(dep)
|
|
19
|
+
|
|
20
|
+
logger.error(f"Provider {provider} has unresolved dependencies: {deps_missing}")
|
|
21
|
+
return deps_circular, deps_missing
|
|
22
|
+
|
|
23
|
+
def raise_dependency_error(providers: list[Provider], resolved_layers: list[list[Provider]]) -> None:
|
|
24
|
+
for provider in providers:
|
|
25
|
+
provider_detect_error(provider, providers, resolved_layers)
|
|
26
|
+
|
|
27
|
+
raise DependencyError("Dependencies cannot be resolved")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dependency.core.declaration import Component, Provider
|
|
2
|
+
from dependency.core.declaration.component import ABCComponent
|
|
3
|
+
|
|
4
|
+
def dep_in_layers(dep: ABCComponent, layers: list[list[Provider]]) -> bool:
|
|
5
|
+
return any(
|
|
6
|
+
issubclass(res.provided_cls, dep.base_cls)
|
|
7
|
+
for layer in layers
|
|
8
|
+
for res in layer
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
def provider_is_resolved(provider: Provider, resolved_layers: list[list[Provider]]) -> bool:
|
|
12
|
+
dependencies = provider.imports
|
|
13
|
+
return all(
|
|
14
|
+
dep_in_layers(dep, resolved_layers)
|
|
15
|
+
for dep in dependencies
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def provider_unresolved(provider: Provider, resolved_layers: list[list[Provider]]) -> list[Component]:
|
|
19
|
+
dependencies = provider.imports
|
|
20
|
+
return [
|
|
21
|
+
dep
|
|
22
|
+
for dep in dependencies
|
|
23
|
+
if not dep_in_layers(dep, resolved_layers)
|
|
24
|
+
]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: module_dependency
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: A dependency management tool for Python projects.
|
|
5
|
+
Project-URL: Homepage, https://github.com/fabaindaiz/module-injection
|
|
6
|
+
Project-URL: Issues, https://github.com/fabaindaiz/module-injection/issues
|
|
7
|
+
Author-email: Fabian D <github.clapping767@passmail.net>
|
|
8
|
+
License-Expression: GPL-3.0-or-later
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: dependency-injection
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Requires-Dist: dependency-injector
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# module-injection
|
|
21
|
+
|
|
22
|
+
This repository contains experiments and examples for managing dependencies using dependency injection with class decorators in Python projects. The structures and patterns demonstrated here are flexible and can be adapted to suit various project needs.
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
|
|
26
|
+
The goal of this project is to showcase different approaches to dependency management, focusing on modularity, flexibility, and ease of use. While the provided examples are specific, the underlying concepts can be applied to a wide range of scenarios.
|
|
27
|
+
|
|
28
|
+
## Core Components
|
|
29
|
+
|
|
30
|
+
The project is built around four components that implement different aspects of dependency management:
|
|
31
|
+
|
|
32
|
+
### 1. Module
|
|
33
|
+
- Acts as a container for organizing and grouping related dependencies
|
|
34
|
+
- Facilitates modular design and hierarchical structuring of application components
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from dependency.core import Module, module
|
|
38
|
+
from plugin........component import SomeService, SomeServiceComponent
|
|
39
|
+
|
|
40
|
+
@module(
|
|
41
|
+
declaration=[ # Declare all components that are part of the module
|
|
42
|
+
SomeServiceComponent,
|
|
43
|
+
... # Every used component must be declared in some module
|
|
44
|
+
],
|
|
45
|
+
bootstrap=[ # Bootstrap components will be started on loading
|
|
46
|
+
SomeServiceComponent
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
class SomeModule(Module):
|
|
50
|
+
"""This is a module class.
|
|
51
|
+
Here the providers are declared and provided
|
|
52
|
+
"""
|
|
53
|
+
def declare_providers(self):
|
|
54
|
+
from plugin........provider import ImplementedSomeService
|
|
55
|
+
return [ # Here you declare the providers for the components
|
|
56
|
+
ImplementedSomeService,
|
|
57
|
+
... # Only one provider per component is allowed
|
|
58
|
+
]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Component
|
|
62
|
+
- Defines abstract interfaces or contracts for dependencies
|
|
63
|
+
- Promotes loose coupling and enables easier testing and maintenance
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from abc import ABC, abstractmethod
|
|
67
|
+
from dependency.core import Component, component
|
|
68
|
+
|
|
69
|
+
class SomeService(ABC):
|
|
70
|
+
"""This is the interface for a new component."""
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def method(self, ...) -> ...:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@component(
|
|
76
|
+
interface=SomeService, # Declares the interface used by the component
|
|
77
|
+
)
|
|
78
|
+
class SomeServiceComponent(Component):
|
|
79
|
+
"""This is the component class.
|
|
80
|
+
Provider selected for this component will injected here.
|
|
81
|
+
Components are only started when provided or bootstrapped.
|
|
82
|
+
"""
|
|
83
|
+
pass
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. Provider
|
|
87
|
+
- Delivers concrete implementations of Components
|
|
88
|
+
- Manages the lifecycle and injection of dependency objects
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from dependency_injector import providers
|
|
92
|
+
from dependency.core import provider
|
|
93
|
+
from plugin........component import SomeService, SomeServiceComponent
|
|
94
|
+
from plugin..other_component import OtherService, OtherServiceComponent
|
|
95
|
+
|
|
96
|
+
@provider(
|
|
97
|
+
component=SomeServiceComponent, # Declares the component to be provided
|
|
98
|
+
imports=[OtherService, ...], # List of dependencies (components) that are needed
|
|
99
|
+
provider=providers.Singleton # Provider type (Singleton, Factory)
|
|
100
|
+
)
|
|
101
|
+
class ImplementedSomeService(SomeService):
|
|
102
|
+
"""This is a provider class. Here the component is implemented.
|
|
103
|
+
Providers are injected into the respective components when provided.
|
|
104
|
+
"""
|
|
105
|
+
def __init__(self, cfg: dict, **kwargs) -> None:
|
|
106
|
+
"""Init method will be called when the provider is stared.
|
|
107
|
+
This will happen once for singleton and every time for factories.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
# Once declared, i can use the dependencies for the class.
|
|
111
|
+
self.dependency: OtherService = OtherServiceComponent.provide()
|
|
112
|
+
|
|
113
|
+
def method(self, ...) -> ...:
|
|
114
|
+
"""Methods declared in the interface must be implemented."""
|
|
115
|
+
do_something()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 4. Dependent
|
|
119
|
+
- Represents a class produced by a Provider that requires dependencies
|
|
120
|
+
- Allows to provide standalone classed without the need to define new providers
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from dependency.core import Dependent, dependent
|
|
124
|
+
|
|
125
|
+
@dependent(
|
|
126
|
+
imports=[SomeComponent, ...], # List of dependencies (components) that are needed
|
|
127
|
+
)
|
|
128
|
+
class SomeDependent(Interface, Dependent):
|
|
129
|
+
"""This is the dependent class.
|
|
130
|
+
Dependents must be declared in the provider that provides them.
|
|
131
|
+
Dependents can be instantiated without the need to define new providers.
|
|
132
|
+
"""
|
|
133
|
+
def method(self, ...) -> ...:
|
|
134
|
+
pass
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
These components work together to create a powerful and flexible dependency injection system, allowing for more maintainable and testable Python applications.
|
|
138
|
+
|
|
139
|
+
## Usage Examples
|
|
140
|
+
|
|
141
|
+
This repository includes a practical example demonstrating how to use the framework. You can find this example in the `example` directory. It showcases the implementation of the core components and how they interact to manage dependencies effectively in a sample application.
|
|
142
|
+
|
|
143
|
+
## Future Work
|
|
144
|
+
|
|
145
|
+
This project is a work in progress, and there are several improvements and enhancements planned for the future. Some of the areas that will be explored include:
|
|
146
|
+
- Improve component registration and resolution mechanisms
|
|
147
|
+
- Improve ways to handle module definitions and configurations
|
|
148
|
+
- Explore more advanced dependency injection patterns and use cases
|
|
149
|
+
- Enhance validation and error handling mechanisms of the framework
|
|
150
|
+
|
|
151
|
+
## Aknowledgements
|
|
152
|
+
|
|
153
|
+
This project depends on [dependency-injector](https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html). This library provides a robust and flexible framework for managing dependencies in Python projects.
|