orionis 0.139.0__py3-none-any.whl → 0.152.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.
- orionis/framework.py +1 -1
- orionis/luminate/application.py +159 -309
- orionis/luminate/console/base/command.py +6 -3
- orionis/luminate/console/command_filter.py +1 -1
- orionis/luminate/console/commands/cache_clear.py +4 -3
- orionis/luminate/console/commands/schedule_work.py +1 -1
- orionis/luminate/console/kernel.py +1 -2
- orionis/luminate/console/output/console.py +1 -1
- orionis/luminate/console/output/executor.py +1 -1
- orionis/luminate/console/output/progress_bar.py +1 -1
- orionis/luminate/console/parser.py +1 -1
- orionis/luminate/container/container.py +361 -272
- orionis/luminate/container/container_integrity.py +235 -0
- orionis/luminate/container/lifetimes.py +13 -0
- orionis/luminate/container/resolve.py +74 -0
- orionis/luminate/contracts/application.py +41 -0
- orionis/luminate/contracts/console/{i_parser.py → parser.py} +0 -1
- orionis/luminate/contracts/container/container.py +346 -0
- orionis/luminate/contracts/container/container_integrity.py +168 -0
- orionis/luminate/contracts/foundation/providers/{i_service_providers_bootstrapper.py → service_providers_bootstrapper.py} +4 -3
- orionis/luminate/facades/commands/commands_facade.py +1 -1
- orionis/luminate/facades/commands/scheduler_facade.py +1 -1
- orionis/luminate/facades/config/config_facade.py +1 -1
- orionis/luminate/facades/environment/environment_facade.py +1 -26
- orionis/luminate/facades/files/path_facade.py +1 -1
- orionis/luminate/facades/log/log_facade.py +1 -1
- orionis/luminate/facades/tests/tests_facade.py +1 -1
- orionis/luminate/foundation/config/config_bootstrapper.py +1 -1
- orionis/luminate/foundation/console/command_bootstrapper.py +1 -1
- orionis/luminate/foundation/environment/environment_bootstrapper.py +1 -1
- orionis/luminate/foundation/exceptions/exception_providers.py +54 -0
- orionis/luminate/foundation/providers/service_providers_bootstrapper.py +53 -45
- orionis/luminate/providers/commands/reactor_commands_service_provider.py +4 -3
- orionis/luminate/providers/commands/scheduler_provider.py +2 -10
- orionis/luminate/providers/config/config_service_provider.py +2 -10
- orionis/luminate/providers/environment/environment__service_provider.py +3 -4
- orionis/luminate/providers/files/paths_provider.py +3 -4
- orionis/luminate/providers/log/log_service_provider.py +3 -2
- orionis/luminate/providers/service_provider.py +2 -6
- orionis/luminate/services/commands/reactor_commands_service.py +6 -16
- orionis/luminate/services/commands/scheduler_service.py +1 -2
- orionis/luminate/services/config/config_service.py +4 -5
- orionis/luminate/services/environment/environment_service.py +25 -2
- orionis/luminate/services/files/path_resolver_service.py +1 -2
- orionis/luminate/services/log/log_service.py +1 -2
- orionis/luminate/support/exception_to_dict.py +1 -1
- orionis/luminate/support/reflection.py +1 -1
- orionis/luminate/support/std.py +1 -1
- {orionis-0.139.0.dist-info → orionis-0.152.0.dist-info}/METADATA +1 -1
- {orionis-0.139.0.dist-info → orionis-0.152.0.dist-info}/RECORD +82 -78
- orionis/luminate/container/types.py +0 -51
- orionis/luminate/contracts/container/i_container.py +0 -239
- orionis/luminate/contracts/container/i_types.py +0 -16
- /orionis/luminate/contracts/console/base/{i_command.py → command.py} +0 -0
- /orionis/luminate/contracts/console/{i_command_filter.py → command_filter.py} +0 -0
- /orionis/luminate/contracts/console/{i_kernel.py → kernel.py} +0 -0
- /orionis/luminate/contracts/console/output/{i_console.py → console.py} +0 -0
- /orionis/luminate/contracts/console/output/{i_executor.py → executor.py} +0 -0
- /orionis/luminate/contracts/console/output/{i_progress_bar.py → progress_bar.py} +0 -0
- /orionis/luminate/contracts/console/{i_task_manager.py → task_manager.py} +0 -0
- /orionis/luminate/contracts/facades/commands/{i_commands_facade.py → commands_facade.py} +0 -0
- /orionis/luminate/contracts/facades/commands/{i_scheduler_facade.py → scheduler_facade.py} +0 -0
- /orionis/luminate/contracts/facades/config/{i_config_facade.py → config_facade.py} +0 -0
- /orionis/luminate/contracts/facades/environment/{i_environment_facade.py → environment_facade.py} +0 -0
- /orionis/luminate/contracts/facades/files/{i_path_facade.py → path_facade.py} +0 -0
- /orionis/luminate/contracts/facades/log/{i_log_facade.py → log_facade.py} +0 -0
- /orionis/luminate/contracts/facades/tests/{i_tests_facade.py → tests_facade.py} +0 -0
- /orionis/luminate/contracts/foundation/{i_bootstraper.py → bootstraper.py} +0 -0
- /orionis/luminate/contracts/foundation/config/{i_config_bootstrapper.py → config_bootstrapper.py} +0 -0
- /orionis/luminate/contracts/foundation/console/{i_command_bootstrapper.py → command_bootstrapper.py} +0 -0
- /orionis/luminate/contracts/foundation/environment/{i_environment_bootstrapper.py → environment_bootstrapper.py} +0 -0
- /orionis/luminate/contracts/providers/{i_service_provider.py → service_provider.py} +0 -0
- /orionis/luminate/contracts/services/commands/{i_reactor_commands_service.py → reactor_commands_service.py} +0 -0
- /orionis/luminate/contracts/services/commands/{i_schedule_service.py → schedule_service.py} +0 -0
- /orionis/luminate/contracts/services/config/{i_config_service.py → config_service.py} +0 -0
- /orionis/luminate/contracts/services/environment/{i_environment_service.py → environment_service.py} +0 -0
- /orionis/luminate/contracts/services/files/{i_path_resolver_service.py → path_resolver_service.py} +0 -0
- /orionis/luminate/contracts/services/log/{i_log_service.py → log_service.py} +0 -0
- /orionis/luminate/contracts/support/{i_exception_to_dict.py → exception_to_dict.py} +0 -0
- /orionis/luminate/contracts/support/{i_reflection.py → reflection.py} +0 -0
- /orionis/luminate/contracts/support/{i_std.py → std.py} +0 -0
- {orionis-0.139.0.dist-info → orionis-0.152.0.dist-info}/LICENCE +0 -0
- {orionis-0.139.0.dist-info → orionis-0.152.0.dist-info}/WHEEL +0 -0
- {orionis-0.139.0.dist-info → orionis-0.152.0.dist-info}/entry_points.txt +0 -0
- {orionis-0.139.0.dist-info → orionis-0.152.0.dist-info}/top_level.txt +0 -0
@@ -1,16 +1,11 @@
|
|
1
1
|
import inspect
|
2
|
-
from collections import deque
|
3
2
|
from threading import Lock
|
4
|
-
from typing import Callable, Any, Dict,
|
5
|
-
from
|
3
|
+
from typing import Callable, Any, Dict, Deque, Optional, Type, get_origin, get_args
|
4
|
+
from collections import deque
|
5
|
+
from orionis.luminate.container.container_integrity import ContainerIntegrity
|
6
|
+
from orionis.luminate.container.lifetimes import Lifetime
|
6
7
|
from orionis.luminate.container.exception import OrionisContainerException, OrionisContainerValueError, OrionisContainerTypeError
|
7
|
-
from orionis.luminate.container.
|
8
|
-
|
9
|
-
BINDING = 'binding'
|
10
|
-
TRANSIENT = 'transient'
|
11
|
-
SINGLETON = 'singleton'
|
12
|
-
SCOPED = 'scoped'
|
13
|
-
INSTANCE = 'instance'
|
8
|
+
from orionis.luminate.contracts.container.container import IContainer
|
14
9
|
|
15
10
|
class Container(IContainer):
|
16
11
|
"""
|
@@ -24,398 +19,473 @@ class Container(IContainer):
|
|
24
19
|
_lock = Lock()
|
25
20
|
|
26
21
|
@classmethod
|
27
|
-
def
|
22
|
+
def destroy(cls):
|
28
23
|
"""
|
29
|
-
|
24
|
+
Destroys the container instance.
|
30
25
|
"""
|
31
26
|
cls._instance = None
|
32
|
-
super().__new__(cls)
|
33
27
|
|
34
28
|
def __new__(cls):
|
29
|
+
"""
|
30
|
+
Create a new instance of the container.
|
31
|
+
"""
|
35
32
|
if cls._instance is None:
|
36
33
|
with cls._lock:
|
37
34
|
if cls._instance is None:
|
38
35
|
cls._instance = super().__new__(cls)
|
39
|
-
cls._instance._bindings = {}
|
40
|
-
cls._instance._transients = {}
|
41
|
-
cls._instance._singletons = {}
|
42
|
-
cls._instance._scoped_services = {}
|
43
|
-
cls._instance._instances = {}
|
44
|
-
cls._instance._aliases = {}
|
45
36
|
cls._instance._scoped_instances = {}
|
46
|
-
cls._instance.
|
37
|
+
cls._instance._singleton_instances = {}
|
38
|
+
cls._instance._instances_services = {}
|
39
|
+
cls._instance._transient_services = {}
|
40
|
+
cls._instance._scoped_services = {}
|
41
|
+
cls._instance._singleton_services = {}
|
42
|
+
cls._instance._aliases_services = {}
|
43
|
+
cls._instance.instance(IContainer, cls._instance)
|
47
44
|
return cls._instance
|
48
45
|
|
49
|
-
def
|
46
|
+
def bind(self, abstract: Callable[..., Any], concrete: Callable[..., Any], lifetime: str = Lifetime.TRANSIENT.value) -> None:
|
50
47
|
"""
|
51
|
-
|
48
|
+
Binds an abstract type to a concrete implementation with a specified lifetime.
|
52
49
|
|
53
50
|
Parameters
|
54
51
|
----------
|
52
|
+
abstract : Callable[..., Any]
|
53
|
+
The abstract base type or alias to be bound.
|
55
54
|
concrete : Callable[..., Any]
|
56
|
-
The
|
57
|
-
|
58
|
-
|
59
|
-
-------
|
60
|
-
str
|
61
|
-
The fully qualified name of the class.
|
55
|
+
The concrete implementation to associate with the abstract type.
|
56
|
+
lifetime : str
|
57
|
+
The lifecycle of the binding. Must be one of 'transient', 'scoped', or 'singleton'.
|
62
58
|
|
63
59
|
Raises
|
64
60
|
------
|
65
61
|
OrionisContainerValueError
|
66
|
-
If
|
62
|
+
If an invalid lifetime is provided or the concrete implementation is None.
|
63
|
+
|
64
|
+
Examples
|
65
|
+
--------
|
66
|
+
>>> container.bind(MyService, MyServiceImplementation, "singleton")
|
67
67
|
"""
|
68
|
-
if concrete.__module__ == "__main__":
|
69
|
-
raise OrionisContainerValueError("Cannot register a class from the main module in the container.")
|
70
68
|
|
71
|
-
|
69
|
+
if lifetime not in [member.value for member in Lifetime]:
|
70
|
+
raise OrionisContainerValueError(f"Invalid lifetime type '{lifetime}'.")
|
71
|
+
|
72
|
+
if concrete is None:
|
73
|
+
raise OrionisContainerValueError("Concrete implementation cannot be None when binding a service.")
|
74
|
+
|
75
|
+
abstract = abstract or concrete
|
76
|
+
ContainerIntegrity.ensureIsCallable(concrete)
|
77
|
+
ContainerIntegrity.ensureNotMain(concrete)
|
78
|
+
|
79
|
+
service_entry = {
|
80
|
+
"concrete": concrete,
|
81
|
+
"async": inspect.iscoroutinefunction(concrete)
|
82
|
+
}
|
83
|
+
|
84
|
+
service_registry = {
|
85
|
+
Lifetime.TRANSIENT.value: self._transient_services,
|
86
|
+
Lifetime.SCOPED.value: self._scoped_services,
|
87
|
+
Lifetime.SINGLETON.value: self._singleton_services
|
88
|
+
}
|
89
|
+
|
90
|
+
if ContainerIntegrity.isAbstract(abstract):
|
91
|
+
ContainerIntegrity.ensureImplementation(abstract, concrete)
|
92
|
+
service_registry[lifetime][abstract] = service_entry
|
93
|
+
return
|
94
|
+
|
95
|
+
if ContainerIntegrity.isAlias(abstract):
|
96
|
+
service_registry[lifetime][abstract] = service_entry
|
97
|
+
return
|
98
|
+
|
99
|
+
raise OrionisContainerValueError(f"Invalid abstract type '{abstract}'. It must be a valid alias or an abstract class.")
|
100
|
+
|
101
|
+
def transient(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
|
72
102
|
"""
|
73
|
-
|
103
|
+
Registers a service with a transient lifetime.
|
74
104
|
|
75
105
|
Parameters
|
76
106
|
----------
|
77
|
-
|
78
|
-
The
|
107
|
+
abstract : Callable[..., Any]
|
108
|
+
The abstract base type or alias to be bound.
|
109
|
+
concrete : Callable[..., Any]
|
110
|
+
The concrete implementation to associate with the abstract type.
|
79
111
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
If the service is already registered.
|
112
|
+
Examples
|
113
|
+
--------
|
114
|
+
>>> container.transient(MyService, MyServiceImplementation)
|
84
115
|
"""
|
85
|
-
if self.has(obj):
|
86
|
-
raise OrionisContainerValueError(f"The service ({str(obj)}) is already registered in the container.")
|
87
116
|
|
88
|
-
|
117
|
+
self.bind(abstract, concrete, Lifetime.TRANSIENT.value)
|
118
|
+
|
119
|
+
def scoped(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
|
89
120
|
"""
|
90
|
-
|
121
|
+
Registers a service with a scoped lifetime.
|
91
122
|
|
92
123
|
Parameters
|
93
124
|
----------
|
125
|
+
abstract : Callable[..., Any]
|
126
|
+
The abstract base type or alias to be bound.
|
94
127
|
concrete : Callable[..., Any]
|
95
|
-
The implementation to
|
128
|
+
The concrete implementation to associate with the abstract type.
|
96
129
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
130
|
+
Examples
|
131
|
+
--------
|
132
|
+
>>> container.scoped(MyService, MyServiceImplementation)
|
133
|
+
"""
|
134
|
+
|
135
|
+
self.bind(abstract, concrete, Lifetime.SCOPED.value)
|
136
|
+
|
137
|
+
def singleton(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
|
138
|
+
"""
|
139
|
+
Registers a service with a singleton lifetime.
|
140
|
+
|
141
|
+
Parameters
|
142
|
+
----------
|
143
|
+
abstract : Callable[..., Any]
|
144
|
+
The abstract base type or alias to be bound.
|
145
|
+
concrete : Callable[..., Any]
|
146
|
+
The concrete implementation to associate with the abstract type.
|
147
|
+
|
148
|
+
Examples
|
149
|
+
--------
|
150
|
+
>>> container.singleton(MyService, MyServiceImplementation)
|
101
151
|
"""
|
102
|
-
if not callable(concrete):
|
103
|
-
raise OrionisContainerTypeError(f"The implementation '{str(concrete)}' must be callable or an instantiable class.")
|
104
152
|
|
105
|
-
|
153
|
+
self.bind(abstract, concrete, Lifetime.SINGLETON.value)
|
154
|
+
|
155
|
+
def instance(self, abstract: Callable[..., Any], instance: Any) -> None:
|
106
156
|
"""
|
107
|
-
|
157
|
+
Registers an already instantiated object in the container.
|
108
158
|
|
109
159
|
Parameters
|
110
160
|
----------
|
161
|
+
abstract : Callable[..., Any]
|
162
|
+
The abstract base type or alias to be bound.
|
111
163
|
instance : Any
|
112
|
-
The instance to
|
164
|
+
The instance to be stored.
|
113
165
|
|
114
166
|
Raises
|
115
167
|
------
|
116
168
|
OrionisContainerValueError
|
117
|
-
If the instance is
|
118
|
-
"""
|
119
|
-
if not isinstance(instance, object) or instance.__class__.__module__ in ['builtins', 'abc']:
|
120
|
-
raise OrionisContainerValueError(f"The instance '{str(instance)}' must be a valid object.")
|
169
|
+
If the instance is None.
|
121
170
|
|
122
|
-
|
123
|
-
|
124
|
-
|
171
|
+
Examples
|
172
|
+
--------
|
173
|
+
>>> container.instance(MyService, my_service_instance)
|
125
174
|
"""
|
126
|
-
self._scoped_instances = {}
|
127
175
|
|
128
|
-
|
129
|
-
|
130
|
-
Bind a callable to the container.
|
131
|
-
This method ensures that the provided callable is not the main function,
|
132
|
-
is unique within the container, and is indeed callable. It then creates
|
133
|
-
a unique key for the callable based on its module and name, and stores
|
134
|
-
the callable in the container's bindings.
|
135
|
-
Args:
|
136
|
-
concrete (Callable[..., Any]): The callable to be bound to the container.
|
137
|
-
Returns:
|
138
|
-
str: The unique key generated for the callable.
|
139
|
-
"""
|
140
|
-
self._ensureNotMain(concrete)
|
141
|
-
self._ensureUniqueService(concrete)
|
142
|
-
self._ensureIsCallable(concrete)
|
143
|
-
|
144
|
-
key = f"{concrete.__module__}.{concrete.__name__}"
|
145
|
-
self._bindings[key] = {
|
146
|
-
'concrete': concrete,
|
147
|
-
'module': concrete.__module__,
|
148
|
-
'name': concrete.__name__,
|
149
|
-
'type': BINDING
|
150
|
-
}
|
176
|
+
if instance is None:
|
177
|
+
raise OrionisContainerValueError("The provided instance cannot be None.")
|
151
178
|
|
152
|
-
|
179
|
+
ContainerIntegrity.ensureIsInstance(instance)
|
153
180
|
|
154
|
-
|
181
|
+
if ContainerIntegrity.isAbstract(abstract):
|
182
|
+
ContainerIntegrity.ensureImplementation(abstract, instance.__class__)
|
183
|
+
self._instances_services[abstract] = instance
|
184
|
+
return
|
185
|
+
|
186
|
+
if ContainerIntegrity.isAlias(abstract):
|
187
|
+
self._instances_services[abstract] = instance
|
188
|
+
return
|
189
|
+
|
190
|
+
raise OrionisContainerValueError(f"Invalid abstract type '{abstract}'. It must be a valid alias or an abstract class.")
|
191
|
+
|
192
|
+
def bound(self, abstract_or_alias: Callable[..., Any]) -> bool:
|
155
193
|
"""
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
194
|
+
Checks if a service or alias is bound in the container.
|
195
|
+
|
196
|
+
Parameters
|
197
|
+
----------
|
198
|
+
abstract_or_alias : Callable[..., Any]
|
199
|
+
The abstract type or alias to check.
|
200
|
+
|
201
|
+
Returns
|
202
|
+
-------
|
203
|
+
bool
|
204
|
+
True if the service is bound, False otherwise.
|
205
|
+
|
206
|
+
Examples
|
207
|
+
--------
|
208
|
+
>>> container.bound(MyService)
|
209
|
+
True
|
162
210
|
"""
|
163
|
-
self._ensureNotMain(concrete)
|
164
|
-
self._ensureUniqueService(concrete)
|
165
|
-
self._ensureIsCallable(concrete)
|
166
211
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
212
|
+
service_dicts = [
|
213
|
+
self._instances_services,
|
214
|
+
self._transient_services,
|
215
|
+
self._scoped_services,
|
216
|
+
self._singleton_services,
|
217
|
+
self._aliases_services
|
218
|
+
]
|
219
|
+
return any(abstract_or_alias in service_dict for service_dict in service_dicts)
|
174
220
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
"""
|
179
|
-
Registers a callable as a singleton in the container.
|
180
|
-
This method ensures that the provided callable is not the main module,
|
181
|
-
is unique within the container, and is indeed callable. It then registers
|
182
|
-
the callable as a singleton, storing it in the container's singleton registry.
|
183
|
-
Args:
|
184
|
-
concrete (Callable[..., Any]): The callable to be registered as a singleton.
|
185
|
-
Returns:
|
186
|
-
str: The key under which the singleton is registered in the container.
|
187
|
-
"""
|
188
|
-
self._ensureNotMain(concrete)
|
189
|
-
self._ensureUniqueService(concrete)
|
190
|
-
self._ensureIsCallable(concrete)
|
191
|
-
|
192
|
-
key = f"{concrete.__module__}.{concrete.__name__}"
|
193
|
-
self._singletons[key] = {
|
194
|
-
'concrete': concrete,
|
195
|
-
'module': concrete.__module__,
|
196
|
-
'name': concrete.__name__,
|
197
|
-
'type': SINGLETON
|
198
|
-
}
|
221
|
+
def has(self, abstract_or_alias: Callable[..., Any]) -> bool:
|
222
|
+
"""
|
223
|
+
Alias for `bound()` method.
|
199
224
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
Registers a callable as a scoped service.
|
205
|
-
This method ensures that the provided callable is not the main service,
|
206
|
-
is unique, and is indeed callable. It then registers the callable in the
|
207
|
-
scoped services dictionary with relevant metadata.
|
208
|
-
Args:
|
209
|
-
concrete (Callable[..., Any]): The callable to be registered as a scoped service.
|
210
|
-
Returns:
|
211
|
-
str: The key under which the callable is registered in the scoped services dictionary.
|
212
|
-
"""
|
213
|
-
self._ensureNotMain(concrete)
|
214
|
-
self._ensureUniqueService(concrete)
|
215
|
-
self._ensureIsCallable(concrete)
|
216
|
-
|
217
|
-
key = f"{concrete.__module__}.{concrete.__name__}"
|
218
|
-
self._scoped_services[key] = {
|
219
|
-
'concrete': concrete,
|
220
|
-
'module': concrete.__module__,
|
221
|
-
'name': concrete.__name__,
|
222
|
-
'type': SCOPED
|
223
|
-
}
|
225
|
+
Parameters
|
226
|
+
----------
|
227
|
+
abstract_or_alias : Callable[..., Any]
|
228
|
+
The abstract type or alias to check.
|
224
229
|
|
225
|
-
|
230
|
+
Returns
|
231
|
+
-------
|
232
|
+
bool
|
233
|
+
True if the service is bound, False otherwise.
|
226
234
|
|
227
|
-
|
235
|
+
Examples
|
236
|
+
--------
|
237
|
+
>>> container.has(MyService)
|
238
|
+
True
|
228
239
|
"""
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
str: The key under which the instance is registered in the container.
|
240
|
+
|
241
|
+
return self.bound(abstract_or_alias)
|
242
|
+
|
243
|
+
def alias(self, alias: Callable[..., Any], abstract: Callable[..., Any]) -> None:
|
234
244
|
"""
|
235
|
-
|
236
|
-
self._ensureUniqueService(instance)
|
237
|
-
self._ensureIsInstance(instance)
|
245
|
+
Creates an alias for an existing abstract binding.
|
238
246
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
'type': INSTANCE
|
246
|
-
}
|
247
|
+
Parameters
|
248
|
+
----------
|
249
|
+
alias : Callable[..., Any]
|
250
|
+
The alias name.
|
251
|
+
abstract : Callable[..., Any]
|
252
|
+
The existing abstract type to alias.
|
247
253
|
|
248
|
-
|
254
|
+
Raises
|
255
|
+
------
|
256
|
+
OrionisContainerValueError
|
257
|
+
If the abstract type is not registered or the alias is already in use.
|
249
258
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
Args:
|
254
|
-
alias (str): The alias name to be used for the service.
|
255
|
-
concrete (Any): The actual service instance or callable to be aliased.
|
256
|
-
Raises:
|
257
|
-
OrionisContainerException: If the concrete instance is not a valid object or if the alias is a primitive type.
|
259
|
+
Examples
|
260
|
+
--------
|
261
|
+
>>> container.alias("DatabaseService", MyDatabaseService)
|
258
262
|
"""
|
259
263
|
|
260
|
-
if self.
|
261
|
-
raise
|
264
|
+
if not self.has(abstract):
|
265
|
+
raise OrionisContainerValueError(f"Abstract '{abstract}' is not registered in the container.")
|
262
266
|
|
263
|
-
if
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
elif isinstance(concrete, object) and concrete.__class__.__module__ not in ['builtins', 'abc']:
|
269
|
-
cls_concrete = concrete.__class__
|
270
|
-
current_key = f"{cls_concrete.__module__}.{cls_concrete.__name__}"
|
271
|
-
elif callable(concrete):
|
272
|
-
current_key = f"{concrete.__module__}.{concrete.__name__}"
|
267
|
+
if alias in self._aliases_services:
|
268
|
+
raise OrionisContainerValueError(f"Alias '{alias}' is already in use.")
|
269
|
+
|
270
|
+
if not ContainerIntegrity.isAlias(abstract):
|
271
|
+
raise OrionisContainerValueError(f"Invalid target abstract type: {abstract}. It must be an alias.")
|
273
272
|
|
274
|
-
self.
|
273
|
+
self._aliases_services[alias] = abstract
|
275
274
|
|
276
|
-
def
|
275
|
+
def isAlias(self, name: str) -> bool:
|
277
276
|
"""
|
278
|
-
Checks if a
|
277
|
+
Checks if a given name is an alias.
|
279
278
|
|
280
279
|
Parameters
|
281
280
|
----------
|
282
|
-
|
283
|
-
The
|
281
|
+
name : str
|
282
|
+
The name to check.
|
284
283
|
|
285
284
|
Returns
|
286
285
|
-------
|
287
286
|
bool
|
288
|
-
True if the
|
287
|
+
True if the name is an alias, False otherwise.
|
288
|
+
|
289
|
+
Raises
|
290
|
+
------
|
291
|
+
OrionisContainerTypeError
|
292
|
+
If the name is not a string.
|
293
|
+
|
294
|
+
Examples
|
295
|
+
--------
|
296
|
+
>>> container.isAlias("DatabaseService")
|
297
|
+
True
|
289
298
|
"""
|
290
|
-
if isinstance(obj, str):
|
291
|
-
return obj in self._aliases or obj in (
|
292
|
-
self._bindings | self._transients | self._singletons | self._scoped_services | self._instances
|
293
|
-
)
|
294
299
|
|
295
|
-
if isinstance(
|
296
|
-
|
297
|
-
|
300
|
+
if not isinstance(name, str):
|
301
|
+
raise OrionisContainerTypeError("The name must be a valid string.")
|
302
|
+
return name in self._aliases_services
|
298
303
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
self._bindings | self._transients | self._singletons | self._scoped_services | self._aliases
|
303
|
-
)
|
304
|
+
def getBindings(self) -> Dict[str, Any]:
|
305
|
+
"""
|
306
|
+
Retrieves all registered service bindings.
|
304
307
|
|
305
|
-
|
308
|
+
Returns
|
309
|
+
-------
|
310
|
+
dict
|
311
|
+
A dictionary containing all instances, transient, scoped, singleton, and alias services.
|
306
312
|
|
307
|
-
|
313
|
+
Examples
|
314
|
+
--------
|
315
|
+
>>> container.getBindings()
|
308
316
|
"""
|
309
|
-
|
317
|
+
|
318
|
+
return {
|
319
|
+
"instances": self._instances_services,
|
320
|
+
"transient": self._transient_services,
|
321
|
+
"scoped": self._scoped_services,
|
322
|
+
"singleton": self._singleton_services,
|
323
|
+
"aliases": self._aliases_services
|
324
|
+
}
|
325
|
+
|
326
|
+
def getAlias(self, name: str) -> Callable[..., Any]:
|
327
|
+
"""
|
328
|
+
Retrieves the abstract type associated with an alias.
|
310
329
|
|
311
330
|
Parameters
|
312
331
|
----------
|
313
|
-
|
314
|
-
The
|
332
|
+
name : str
|
333
|
+
The alias name.
|
315
334
|
|
316
335
|
Returns
|
317
336
|
-------
|
318
|
-
|
319
|
-
|
337
|
+
Callable[..., Any]
|
338
|
+
The abstract type associated with the alias.
|
339
|
+
|
340
|
+
Raises
|
341
|
+
------
|
342
|
+
OrionisContainerValueError
|
343
|
+
If the alias is not registered.
|
344
|
+
|
345
|
+
Examples
|
346
|
+
--------
|
347
|
+
>>> container.getAlias("DatabaseService")
|
348
|
+
<class 'MyDatabaseService'>
|
320
349
|
"""
|
321
|
-
return self.has(abstract)
|
322
350
|
|
323
|
-
|
351
|
+
if not isinstance(name, str):
|
352
|
+
raise OrionisContainerValueError("The name must be a valid string.")
|
353
|
+
|
354
|
+
if name not in self._aliases_services:
|
355
|
+
raise OrionisContainerValueError(f"Alias '{name}' is not registered in the container.")
|
356
|
+
|
357
|
+
return self._aliases_services[name]
|
358
|
+
|
359
|
+
def forgetScopedInstances(self) -> None:
|
360
|
+
"""
|
361
|
+
Clears all scoped instances.
|
362
|
+
|
363
|
+
Examples
|
364
|
+
--------
|
365
|
+
>>> container.forgetScopedInstances()
|
324
366
|
"""
|
325
|
-
|
367
|
+
|
368
|
+
self._scoped_instances = {}
|
369
|
+
|
370
|
+
def newRequest(self) -> None:
|
371
|
+
"""
|
372
|
+
Resets scoped instances to handle a new request.
|
373
|
+
|
374
|
+
Examples
|
375
|
+
--------
|
376
|
+
>>> container.newRequest()
|
377
|
+
"""
|
378
|
+
|
379
|
+
self.forgetScopedInstances()
|
380
|
+
|
381
|
+
async def make(self, abstract_or_alias: Callable[..., Any]) -> Any:
|
382
|
+
"""
|
383
|
+
Resolves and instantiates a service from the container.
|
326
384
|
|
327
385
|
Parameters
|
328
386
|
----------
|
329
|
-
|
330
|
-
The
|
387
|
+
abstract_or_alias : Callable[..., Any]
|
388
|
+
The abstract type or alias to resolve.
|
331
389
|
|
332
390
|
Returns
|
333
391
|
-------
|
334
392
|
Any
|
335
|
-
|
393
|
+
The instantiated service.
|
336
394
|
|
337
395
|
Raises
|
338
396
|
------
|
339
397
|
OrionisContainerException
|
340
|
-
If the service is not found
|
398
|
+
If the service is not found.
|
399
|
+
|
400
|
+
Examples
|
401
|
+
--------
|
402
|
+
>>> service = await container.make(MyService)
|
341
403
|
"""
|
342
404
|
|
343
|
-
|
405
|
+
if abstract_or_alias in self._aliases_services:
|
406
|
+
abstract_or_alias = self._aliases_services[abstract_or_alias]
|
344
407
|
|
345
|
-
if
|
346
|
-
|
408
|
+
if abstract_or_alias in self._instances_services:
|
409
|
+
return self._instances_services[abstract_or_alias]
|
347
410
|
|
348
|
-
if
|
349
|
-
|
411
|
+
if abstract_or_alias in self._singleton_services:
|
412
|
+
if abstract_or_alias not in self._singleton_instances:
|
413
|
+
service = self._singleton_services[abstract_or_alias]
|
414
|
+
self._singleton_instances[abstract_or_alias] = await service['concrete']() if service['async'] else service['concrete']()
|
415
|
+
return self._singleton_instances[abstract_or_alias]
|
350
416
|
|
351
|
-
if
|
352
|
-
|
417
|
+
if abstract_or_alias in self._scoped_services:
|
418
|
+
if abstract_or_alias not in self._scoped_instances:
|
419
|
+
service = self._scoped_services[abstract_or_alias]
|
420
|
+
self._scoped_instances[abstract_or_alias] = await service['concrete']() if service['async'] else service['concrete']()
|
421
|
+
return self._scoped_instances[abstract_or_alias]
|
353
422
|
|
354
|
-
if
|
355
|
-
|
423
|
+
if abstract_or_alias in self._transient_services:
|
424
|
+
service = self._transient_services[abstract_or_alias]
|
425
|
+
return await service['concrete']() if service['async'] else service['concrete']()
|
356
426
|
|
357
|
-
|
358
|
-
self._instances[key] = {'instance': self._resolve(self._singletons[key]['concrete'])}
|
359
|
-
return self._instances[key]['instance']
|
427
|
+
raise OrionisContainerException(f"No binding found for '{abstract_or_alias}' in the container.")
|
360
428
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
return self._scoped_instances[key]
|
429
|
+
def _resolve(self, concrete: Callable[..., Any], resolving: Optional[Deque[Type]] = None) -> Any:
|
430
|
+
"""
|
431
|
+
Resolves dependencies recursively and instantiates a class.
|
365
432
|
|
366
|
-
|
367
|
-
|
433
|
+
Parameters
|
434
|
+
----------
|
435
|
+
concrete : Callable[..., Any]
|
436
|
+
The concrete implementation to instantiate.
|
437
|
+
resolving : Optional[Deque[Type]], optional
|
438
|
+
A queue to track resolving dependencies and prevent circular dependencies.
|
368
439
|
|
369
|
-
|
370
|
-
|
440
|
+
Returns
|
441
|
+
-------
|
442
|
+
Any
|
443
|
+
The instantiated object.
|
371
444
|
|
372
|
-
|
445
|
+
Raises
|
446
|
+
------
|
447
|
+
OrionisContainerException
|
448
|
+
If circular dependencies are detected or instantiation fails.
|
373
449
|
|
374
|
-
|
450
|
+
Examples
|
451
|
+
--------
|
452
|
+
>>> instance = container._resolve(MyClass)
|
375
453
|
"""
|
376
|
-
Resolve and instantiate a given service class or function.
|
377
454
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
455
|
+
if resolving is None:
|
456
|
+
resolving = deque()
|
457
|
+
|
458
|
+
if concrete in resolving:
|
459
|
+
raise OrionisContainerException(f"Circular dependency detected for {concrete}.")
|
460
|
+
|
461
|
+
resolving.append(concrete)
|
382
462
|
|
383
|
-
# Step 1: Retrieve the constructor signature of the class or callable.
|
384
463
|
try:
|
385
464
|
signature = inspect.signature(concrete)
|
386
465
|
except ValueError as e:
|
387
466
|
raise OrionisContainerException(f"Unable to inspect signature of {concrete}: {str(e)}")
|
388
467
|
|
389
|
-
# Step 2: Prepare a dictionary for resolved dependencies and a queue for unresolved ones.
|
390
468
|
resolved_dependencies: Dict[str, Any] = {}
|
391
469
|
unresolved_dependencies = deque()
|
392
470
|
|
393
|
-
# Step 3: Iterate through the parameters of the constructor.
|
394
471
|
for param_name, param in signature.parameters.items():
|
395
|
-
|
396
|
-
# Skip 'self' in methods
|
397
472
|
if param_name == 'self':
|
398
473
|
continue
|
399
474
|
|
400
|
-
# Handle parameters that are VAR_POSITIONAL or VAR_KEYWORD
|
401
475
|
if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD):
|
402
476
|
continue
|
403
477
|
|
404
|
-
# If parameter has no annotation and no default value, it's unresolved
|
405
478
|
if param.annotation is param.empty and param.default is param.empty:
|
406
479
|
unresolved_dependencies.append(param_name)
|
407
480
|
continue
|
408
481
|
|
409
|
-
# Resolve parameters with default values (without annotations)
|
410
482
|
if param.default is not param.empty:
|
411
483
|
resolved_dependencies[param_name] = param.default
|
412
484
|
continue
|
413
485
|
|
414
|
-
# Resolve dependencies based on annotations (excluding primitive types)
|
415
486
|
if param.annotation is not param.empty:
|
416
487
|
param_type = param.annotation
|
417
488
|
|
418
|
-
# Check if it's a generic type, get the origin type
|
419
489
|
if get_origin(param_type) is not None:
|
420
490
|
param_type = get_args(param_type)[0]
|
421
491
|
|
@@ -423,36 +493,55 @@ class Container(IContainer):
|
|
423
493
|
if self.has(param_type):
|
424
494
|
resolved_dependencies[param_name] = self.make(f"{param_type.__module__}.{param_type.__name__}")
|
425
495
|
else:
|
426
|
-
resolved_dependencies[param_name] = self._resolve_dependency(param_type)
|
496
|
+
resolved_dependencies[param_name] = self._resolve_dependency(param_type, resolving)
|
427
497
|
else:
|
428
498
|
resolved_dependencies[param_name] = param_type
|
429
499
|
|
430
|
-
# Step 4: Resolve any remaining unresolved dependencies.
|
431
500
|
while unresolved_dependencies:
|
432
501
|
dep_name = unresolved_dependencies.popleft()
|
433
502
|
if dep_name not in resolved_dependencies:
|
434
|
-
resolved_dependencies[dep_name] = self._resolve_dependency(dep_name)
|
503
|
+
resolved_dependencies[dep_name] = self._resolve_dependency(dep_name, resolving)
|
435
504
|
|
436
|
-
# Step 5: Instantiate the class with resolved dependencies.
|
437
505
|
try:
|
438
|
-
|
506
|
+
instance = concrete(**resolved_dependencies)
|
507
|
+
resolving.pop()
|
508
|
+
return instance
|
439
509
|
except Exception as e:
|
440
510
|
raise OrionisContainerException(f"Failed to instantiate {concrete}: {str(e)}")
|
441
511
|
|
442
|
-
def _resolve_dependency(self, dep_type: Any) -> Any:
|
512
|
+
def _resolve_dependency(self, dep_type: Any, resolving: Optional[Deque[Type]] = None) -> Any:
|
443
513
|
"""
|
444
|
-
Resolves a dependency
|
514
|
+
Resolves a dependency by instantiating or retrieving it from the container.
|
445
515
|
|
446
|
-
|
447
|
-
|
516
|
+
Parameters
|
517
|
+
----------
|
518
|
+
dep_type : Any
|
519
|
+
The dependency type to resolve.
|
520
|
+
resolving : Optional[Deque[Type]], optional
|
521
|
+
A queue to track resolving dependencies.
|
522
|
+
|
523
|
+
Returns
|
524
|
+
-------
|
525
|
+
Any
|
526
|
+
The resolved dependency.
|
527
|
+
|
528
|
+
Raises
|
529
|
+
------
|
530
|
+
OrionisContainerException
|
531
|
+
If the dependency cannot be resolved.
|
532
|
+
|
533
|
+
Examples
|
534
|
+
--------
|
535
|
+
>>> dependency = container._resolve_dependency(MyDependency)
|
448
536
|
"""
|
449
|
-
|
537
|
+
|
538
|
+
if resolving is None:
|
539
|
+
resolving = deque()
|
540
|
+
|
450
541
|
if isinstance(dep_type, type):
|
451
542
|
if self.has(dep_type):
|
452
|
-
# Resolves the service through the container
|
453
543
|
return self.make(f"{dep_type.__module__}.{dep_type.__name__}")
|
454
544
|
else:
|
455
|
-
|
456
|
-
return self._resolve(dep_type)
|
545
|
+
return self._resolve(dep_type, resolving)
|
457
546
|
|
458
|
-
raise OrionisContainerException(f"Cannot resolve dependency of type {dep_type}")
|
547
|
+
raise OrionisContainerException(f"Cannot resolve dependency of type {dep_type}")
|