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