orionis 0.319.0__py3-none-any.whl → 0.321.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/container/container.py +138 -739
- orionis/container/contracts/container.py +61 -15
- orionis/container/resolver.py +404 -0
- orionis/container/validators/__init__.py +0 -0
- orionis/container/validators/implements.py +67 -0
- orionis/container/validators/is_abstract_class.py +34 -0
- orionis/container/validators/is_callable.py +30 -0
- orionis/container/validators/is_concrete_class.py +34 -0
- orionis/container/validators/is_instance.py +32 -0
- orionis/container/validators/is_not_subclass.py +32 -0
- orionis/container/validators/is_subclass.py +32 -0
- orionis/container/validators/is_valid_alias.py +37 -0
- orionis/container/validators/lifetime.py +53 -0
- orionis/metadata/framework.py +1 -1
- {orionis-0.319.0.dist-info → orionis-0.321.0.dist-info}/METADATA +1 -1
- {orionis-0.319.0.dist-info → orionis-0.321.0.dist-info}/RECORD +20 -9
- {orionis-0.319.0.dist-info → orionis-0.321.0.dist-info}/WHEEL +0 -0
- {orionis-0.319.0.dist-info → orionis-0.321.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.319.0.dist-info → orionis-0.321.0.dist-info}/top_level.txt +0 -0
- {orionis-0.319.0.dist-info → orionis-0.321.0.dist-info}/zip-safe +0 -0
orionis/container/container.py
CHANGED
@@ -4,13 +4,18 @@ from orionis.container.contracts.container import IContainer
|
|
4
4
|
from orionis.container.entities.binding import Binding
|
5
5
|
from orionis.container.enums.lifetimes import Lifetime
|
6
6
|
from orionis.container.exceptions.container_exception import OrionisContainerException
|
7
|
-
from orionis.container.
|
7
|
+
from orionis.container.resolver import Resolver
|
8
|
+
from orionis.container.validators.implements import ImplementsAbstractMethods
|
9
|
+
from orionis.container.validators.is_abstract_class import IsAbstractClass
|
10
|
+
from orionis.container.validators.is_callable import IsCallable
|
11
|
+
from orionis.container.validators.is_concrete_class import IsConcreteClass
|
12
|
+
from orionis.container.validators.is_instance import IsInstance
|
13
|
+
from orionis.container.validators.is_not_subclass import IsNotSubclass
|
14
|
+
from orionis.container.validators.is_subclass import IsSubclass
|
15
|
+
from orionis.container.validators.is_valid_alias import IsValidAlias
|
16
|
+
from orionis.container.validators.lifetime import LifetimeValidator
|
8
17
|
from orionis.services.introspection.abstract.reflection_abstract import ReflectionAbstract
|
9
18
|
from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
|
10
|
-
from orionis.services.introspection.concretes.reflection_concrete import ReflectionConcrete
|
11
|
-
from orionis.services.introspection.dependencies.entities.resolved_dependencies import ResolvedDependency
|
12
|
-
from orionis.services.introspection.instances.reflection_instance import ReflectionInstance
|
13
|
-
from orionis.services.introspection.reflection import Reflection
|
14
19
|
|
15
20
|
class Container(IContainer):
|
16
21
|
|
@@ -29,9 +34,7 @@ class Container(IContainer):
|
|
29
34
|
_initialized = False
|
30
35
|
|
31
36
|
def __new__(
|
32
|
-
cls
|
33
|
-
*args,
|
34
|
-
**kwargs
|
37
|
+
cls
|
35
38
|
) -> 'Container':
|
36
39
|
"""
|
37
40
|
Creates and returns a singleton instance of the class.
|
@@ -40,21 +43,26 @@ class Container(IContainer):
|
|
40
43
|
of the class exists. If an instance does not exist, it acquires a lock to
|
41
44
|
ensure thread safety and creates the instance. Subsequent calls return the
|
42
45
|
existing instance.
|
43
|
-
|
44
|
-
----------
|
45
|
-
*args : tuple
|
46
|
-
Variable length argument list.
|
47
|
-
**kwargs : dict
|
48
|
-
Arbitrary keyword arguments.
|
46
|
+
|
49
47
|
Returns
|
50
48
|
-------
|
51
49
|
Container
|
52
50
|
The singleton instance of the class.
|
53
51
|
"""
|
52
|
+
|
53
|
+
# Check if the class has an existing instance
|
54
54
|
if cls._instance is None:
|
55
|
+
|
56
|
+
# Acquire the lock to ensure thread safety during instance creation
|
55
57
|
with cls._lock:
|
58
|
+
|
59
|
+
# If the instance is still None, create a new instance
|
56
60
|
if cls._instance is None:
|
61
|
+
|
62
|
+
# Call the superclass's __new__ method to create a new instance
|
57
63
|
cls._instance = super(Container, cls).__new__(cls)
|
64
|
+
|
65
|
+
# Return the existing instance of the class
|
58
66
|
return cls._instance
|
59
67
|
|
60
68
|
def __init__(
|
@@ -75,362 +83,19 @@ class Container(IContainer):
|
|
75
83
|
- Initialization occurs only once per class, regardless of the number of instances.
|
76
84
|
- The container registers itself under the IContainer interface to allow for dependency injection.
|
77
85
|
"""
|
86
|
+
|
87
|
+
# Check if the class has been initialized
|
78
88
|
if not self.__class__._initialized:
|
89
|
+
|
90
|
+
# Initialize the container's internal state
|
79
91
|
self.__bindings = {}
|
80
92
|
self.__aliasses = {}
|
81
|
-
self.__class__._initialized = True
|
82
|
-
self.instance(IContainer, self)
|
83
|
-
|
84
|
-
def __dropService(
|
85
|
-
self,
|
86
|
-
abstract: Callable[..., Any] = None,
|
87
|
-
alias: str = None
|
88
|
-
) -> None:
|
89
|
-
"""
|
90
|
-
Drops a service from the container.
|
91
|
-
|
92
|
-
Parameters
|
93
|
-
----------
|
94
|
-
abstract : Callable[..., Any]
|
95
|
-
The abstract type or interface to be removed.
|
96
|
-
alias : str, optional
|
97
|
-
The alias of the service to be removed. If not provided, the service will be removed by its abstract type.
|
98
|
-
|
99
|
-
Raises
|
100
|
-
------
|
101
|
-
OrionisContainerException
|
102
|
-
If the service does not exist in the container.
|
103
|
-
"""
|
104
|
-
|
105
|
-
# If abstract is provided
|
106
|
-
if abstract:
|
107
|
-
|
108
|
-
# Remove the abstract service from the bindings if it exists
|
109
|
-
if abstract in self.__bindings:
|
110
|
-
del self.__bindings[abstract]
|
111
|
-
|
112
|
-
# Remove the default alias (module + class name) from aliases if it exists
|
113
|
-
abs_alias = ReflectionAbstract(abstract).getModuleWithClassName()
|
114
|
-
if abs_alias in self.__aliasses:
|
115
|
-
del self.__aliasses[abs_alias]
|
116
|
-
|
117
|
-
# If a custom alias is provided
|
118
|
-
if alias:
|
119
|
-
|
120
|
-
# Remove it from the aliases dictionary if it exists
|
121
|
-
if alias in self.__aliasses:
|
122
|
-
del self.__aliasses[alias]
|
123
|
-
|
124
|
-
# Remove the binding associated with the alias
|
125
|
-
if alias in self.__bindings:
|
126
|
-
del self.__bindings[alias]
|
127
|
-
|
128
|
-
def __ensureIsCallable(
|
129
|
-
self,
|
130
|
-
value: Any
|
131
|
-
) -> None:
|
132
|
-
"""
|
133
|
-
Ensures that the provided value is callable.
|
134
|
-
|
135
|
-
Parameters
|
136
|
-
----------
|
137
|
-
value : Any
|
138
|
-
The value to check.
|
139
|
-
|
140
|
-
Raises
|
141
|
-
------
|
142
|
-
OrionisContainerTypeError
|
143
|
-
If the value is not callable.
|
144
|
-
"""
|
145
|
-
|
146
|
-
if not callable(value):
|
147
|
-
raise OrionisContainerTypeError(
|
148
|
-
f"Expected a callable type, but got {type(value).__name__} instead."
|
149
|
-
)
|
150
|
-
|
151
|
-
def __ensureAliasType(
|
152
|
-
self,
|
153
|
-
value: Any
|
154
|
-
) -> None:
|
155
|
-
"""
|
156
|
-
Ensures that the provided value is a valid alias of type str and does not contain invalid characters.
|
157
|
-
|
158
|
-
Parameters
|
159
|
-
----------
|
160
|
-
value : Any
|
161
|
-
The value to check.
|
162
|
-
|
163
|
-
Raises
|
164
|
-
------
|
165
|
-
OrionisContainerTypeError
|
166
|
-
If the value is not of type str or contains invalid characters.
|
167
|
-
|
168
|
-
Notes
|
169
|
-
-----
|
170
|
-
This method validates that a given value is a string and does not contain characters
|
171
|
-
that could cause errors when resolving dependencies (e.g., whitespace, special symbols).
|
172
|
-
"""
|
173
|
-
|
174
|
-
# Check if the value is a string
|
175
|
-
if not isinstance(value, str):
|
176
|
-
raise OrionisContainerTypeError(
|
177
|
-
f"Expected a string type for alias, but got {type(value).__name__} instead."
|
178
|
-
)
|
179
|
-
|
180
|
-
# Define a set of invalid characters for aliases
|
181
|
-
invalid_chars = set(' \t\n\r\x0b\x0c!@#$%^&*()[]{};:,/<>?\\|`~"\'')
|
182
|
-
if any(char in invalid_chars for char in value):
|
183
|
-
raise OrionisContainerTypeError(
|
184
|
-
f"Alias '{value}' contains invalid characters. "
|
185
|
-
"Aliases must not contain whitespace or special symbols."
|
186
|
-
)
|
187
|
-
|
188
|
-
def __ensureAbstractClass(
|
189
|
-
self,
|
190
|
-
abstract: Callable[..., Any],
|
191
|
-
lifetime: str
|
192
|
-
) -> None:
|
193
|
-
"""
|
194
|
-
Ensures that the provided abstract is an abstract class.
|
195
|
-
|
196
|
-
Parameters
|
197
|
-
----------
|
198
|
-
abstract : Callable[..., Any]
|
199
|
-
The class intended to represent the abstract type.
|
200
|
-
lifetime : str
|
201
|
-
The service lifetime descriptor, used for error messages.
|
202
|
-
|
203
|
-
Raises
|
204
|
-
------
|
205
|
-
OrionisContainerTypeError
|
206
|
-
If the abstract class check fails.
|
207
|
-
"""
|
208
|
-
try:
|
209
|
-
ReflectionAbstract.ensureIsAbstractClass(abstract)
|
210
|
-
except Exception as e:
|
211
|
-
raise OrionisContainerTypeError(
|
212
|
-
f"Unexpected error registering {lifetime} service: {e}"
|
213
|
-
) from e
|
214
|
-
|
215
|
-
def __ensureConcreteClass(
|
216
|
-
self,
|
217
|
-
concrete: Callable[..., Any],
|
218
|
-
lifetime: str
|
219
|
-
) -> None:
|
220
|
-
"""
|
221
|
-
Ensures that the provided concrete is a concrete (non-abstract) class.
|
222
|
-
|
223
|
-
Parameters
|
224
|
-
----------
|
225
|
-
concrete : Callable[..., Any]
|
226
|
-
The class intended to represent the concrete implementation.
|
227
|
-
lifetime : str
|
228
|
-
The service lifetime descriptor, used for error messages.
|
229
|
-
|
230
|
-
Raises
|
231
|
-
------
|
232
|
-
OrionisContainerTypeError
|
233
|
-
If the concrete class check fails.
|
234
|
-
"""
|
235
|
-
try:
|
236
|
-
ReflectionConcrete.ensureIsConcreteClass(concrete)
|
237
|
-
except Exception as e:
|
238
|
-
raise OrionisContainerTypeError(
|
239
|
-
f"Unexpected error registering {lifetime} service: {e}"
|
240
|
-
) from e
|
241
|
-
|
242
|
-
def __ensureIsSubclass(
|
243
|
-
self,
|
244
|
-
abstract: Callable[..., Any],
|
245
|
-
concrete: Callable[..., Any]
|
246
|
-
) -> None:
|
247
|
-
"""
|
248
|
-
Validates that the concrete class is a subclass of the provided abstract class.
|
249
|
-
|
250
|
-
Parameters
|
251
|
-
----------
|
252
|
-
abstract : Callable[..., Any]
|
253
|
-
The abstract base class or interface.
|
254
|
-
concrete : Callable[..., Any]
|
255
|
-
The concrete implementation class to check.
|
256
|
-
|
257
|
-
Raises
|
258
|
-
------
|
259
|
-
OrionisContainerException
|
260
|
-
If the concrete class is NOT a subclass of the abstract class.
|
261
|
-
|
262
|
-
Notes
|
263
|
-
-----
|
264
|
-
This method ensures that the concrete implementation inherits from the abstract class,
|
265
|
-
which is required for proper dependency injection and interface enforcement.
|
266
|
-
"""
|
267
|
-
if not issubclass(concrete, abstract):
|
268
|
-
raise OrionisContainerException(
|
269
|
-
"The concrete class must inherit from the provided abstract class. "
|
270
|
-
"Please ensure that the concrete class is a subclass of the specified abstract class."
|
271
|
-
)
|
272
|
-
|
273
|
-
def __ensureIsNotSubclass(
|
274
|
-
self,
|
275
|
-
abstract: Callable[..., Any],
|
276
|
-
concrete: Callable[..., Any]
|
277
|
-
) -> None:
|
278
|
-
"""
|
279
|
-
Validates that the concrete class is NOT a subclass of the provided abstract class.
|
280
|
-
|
281
|
-
Parameters
|
282
|
-
----------
|
283
|
-
abstract : Callable[..., Any]
|
284
|
-
The abstract base class or interface.
|
285
|
-
concrete : Callable[..., Any]
|
286
|
-
The concrete implementation class to check.
|
287
|
-
|
288
|
-
Raises
|
289
|
-
------
|
290
|
-
OrionisContainerException
|
291
|
-
If the concrete class IS a subclass of the abstract class.
|
292
|
-
|
293
|
-
Notes
|
294
|
-
-----
|
295
|
-
This method ensures that the concrete implementation does NOT inherit from the abstract class.
|
296
|
-
"""
|
297
|
-
if issubclass(concrete, abstract):
|
298
|
-
raise OrionisContainerException(
|
299
|
-
"The concrete class must NOT inherit from the provided abstract class. "
|
300
|
-
"Please ensure that the concrete class is not a subclass of the specified abstract class."
|
301
|
-
)
|
302
|
-
|
303
|
-
def __ensureInstance(
|
304
|
-
self,
|
305
|
-
instance: Any
|
306
|
-
) -> None:
|
307
|
-
"""
|
308
|
-
Ensures that the provided object is a valid instance.
|
309
|
-
|
310
|
-
Parameters
|
311
|
-
----------
|
312
|
-
instance : Any
|
313
|
-
The object to be validated as an instance.
|
314
|
-
|
315
|
-
Raises
|
316
|
-
------
|
317
|
-
OrionisContainerTypeError
|
318
|
-
If the provided object is not a valid instance.
|
319
|
-
|
320
|
-
Notes
|
321
|
-
-----
|
322
|
-
This method uses ReflectionInstance to verify that the given object
|
323
|
-
is a proper instance (not a class or abstract type). If the check fails,
|
324
|
-
an OrionisContainerTypeError is raised with a descriptive message.
|
325
|
-
"""
|
326
|
-
try:
|
327
|
-
ReflectionInstance.ensureIsInstance(instance)
|
328
|
-
except Exception as e:
|
329
|
-
raise OrionisContainerTypeError(
|
330
|
-
f"Error registering instance: {e}"
|
331
|
-
) from e
|
332
|
-
|
333
|
-
def __ensureImplementation(
|
334
|
-
self,
|
335
|
-
*,
|
336
|
-
abstract: Callable[..., Any] = None,
|
337
|
-
concrete: Callable[..., Any] = None,
|
338
|
-
instance: Any = None
|
339
|
-
) -> None:
|
340
|
-
"""
|
341
|
-
Ensures that a concrete class or instance implements all abstract methods defined in an abstract class.
|
342
|
-
|
343
|
-
Parameters
|
344
|
-
----------
|
345
|
-
abstract : Callable[..., Any]
|
346
|
-
The abstract class containing abstract methods.
|
347
|
-
concrete : Callable[..., Any], optional
|
348
|
-
The concrete class that should implement the abstract methods.
|
349
|
-
instance : Any, optional
|
350
|
-
The instance that should implement the abstract methods.
|
351
|
-
|
352
|
-
Raises
|
353
|
-
------
|
354
|
-
OrionisContainerException
|
355
|
-
If the concrete class or instance does not implement all abstract methods defined in the abstract class.
|
356
|
-
|
357
|
-
Notes
|
358
|
-
-----
|
359
|
-
This method checks that all abstract methods in the given abstract class are implemented
|
360
|
-
in the provided concrete class or instance. If any methods are missing, an exception is raised with
|
361
|
-
details about the missing implementations.
|
362
|
-
"""
|
363
|
-
if abstract is None:
|
364
|
-
raise OrionisContainerException("Abstract class must be provided for implementation check.")
|
365
|
-
|
366
|
-
abstract_methods = getattr(abstract, '__abstractmethods__', set())
|
367
|
-
if not abstract_methods:
|
368
|
-
raise OrionisContainerException(
|
369
|
-
f"The abstract class '{abstract.__name__}' does not define any abstract methods. "
|
370
|
-
"An abstract class must have at least one abstract method."
|
371
|
-
)
|
372
|
-
|
373
|
-
target = concrete if concrete is not None else instance
|
374
|
-
if target is None:
|
375
|
-
raise OrionisContainerException("Either concrete class or instance must be provided for implementation check.")
|
376
93
|
|
377
|
-
|
378
|
-
|
379
|
-
abstract_name = abstract.__name__
|
380
|
-
|
381
|
-
not_implemented = []
|
382
|
-
for method in abstract_methods:
|
383
|
-
if not hasattr(target, str(method).replace(f"_{abstract_name}", f"_{target_name}")):
|
384
|
-
not_implemented.append(method)
|
385
|
-
|
386
|
-
if not_implemented:
|
387
|
-
formatted_methods = "\n • " + "\n • ".join(not_implemented)
|
388
|
-
raise OrionisContainerException(
|
389
|
-
f"'{target_name}' does not implement the following abstract methods defined in '{abstract_name}':{formatted_methods}\n"
|
390
|
-
"Please ensure that all abstract methods are implemented."
|
391
|
-
)
|
392
|
-
|
393
|
-
def __getService(
|
394
|
-
self,
|
395
|
-
abstract_or_alias: Any
|
396
|
-
) -> Binding:
|
397
|
-
"""
|
398
|
-
Retrieves the binding for the requested abstract type or alias.
|
399
|
-
|
400
|
-
Parameters
|
401
|
-
----------
|
402
|
-
abstract_or_alias : Any
|
403
|
-
The abstract class, interface, or alias (str) to retrieve.
|
404
|
-
|
405
|
-
Returns
|
406
|
-
-------
|
407
|
-
Binding
|
408
|
-
The binding associated with the requested abstract type or alias.
|
409
|
-
"""
|
410
|
-
return self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
|
411
|
-
|
412
|
-
def __getFirstService(
|
413
|
-
self,
|
414
|
-
abstract_or_aliasses: list
|
415
|
-
) -> Binding:
|
416
|
-
"""
|
417
|
-
Retrieves the first binding from a list of abstract types or aliases.
|
418
|
-
|
419
|
-
Parameters
|
420
|
-
----------
|
421
|
-
abstract_or_aliasses : list
|
422
|
-
A list of abstract classes, interfaces, or aliases (str) to retrieve.
|
94
|
+
# Set the initialized flag to True to prevent re-initialization
|
95
|
+
self.__class__._initialized = True
|
423
96
|
|
424
|
-
|
425
|
-
|
426
|
-
Binding
|
427
|
-
The first binding found in the container for the provided abstract types or aliases.
|
428
|
-
"""
|
429
|
-
for item in abstract_or_aliasses:
|
430
|
-
binding = self.__getService(item)
|
431
|
-
if binding:
|
432
|
-
return binding
|
433
|
-
return None
|
97
|
+
# Register the container itself as a service
|
98
|
+
self.instance(IContainer, self)
|
434
99
|
|
435
100
|
def transient(
|
436
101
|
self,
|
@@ -472,28 +137,28 @@ class Container(IContainer):
|
|
472
137
|
"""
|
473
138
|
|
474
139
|
# Ensure that abstract is an abstract class
|
475
|
-
|
140
|
+
IsAbstractClass(abstract, Lifetime.TRANSIENT)
|
476
141
|
|
477
142
|
# Ensure that concrete is a concrete class
|
478
|
-
|
143
|
+
IsConcreteClass(concrete, Lifetime.TRANSIENT)
|
479
144
|
|
480
145
|
# Ensure that concrete is NOT a subclass of abstract
|
481
146
|
if enforce_decoupling:
|
482
|
-
|
147
|
+
IsNotSubclass(abstract, concrete)
|
483
148
|
|
484
149
|
# Validate that concrete is a subclass of abstract
|
485
150
|
else:
|
486
|
-
|
151
|
+
IsSubclass(abstract, concrete)
|
487
152
|
|
488
153
|
# Ensure implementation
|
489
|
-
|
154
|
+
ImplementsAbstractMethods(
|
490
155
|
abstract=abstract,
|
491
156
|
concrete=concrete
|
492
157
|
)
|
493
158
|
|
494
159
|
# Ensure that the alias is a valid string if provided
|
495
160
|
if alias:
|
496
|
-
|
161
|
+
IsValidAlias(alias)
|
497
162
|
|
498
163
|
# Extract the module and class name for the alias
|
499
164
|
else:
|
@@ -501,7 +166,7 @@ class Container(IContainer):
|
|
501
166
|
alias = rf_asbtract.getModuleWithClassName()
|
502
167
|
|
503
168
|
# If the service is already registered, drop it
|
504
|
-
self.
|
169
|
+
self.drop(abstract, alias)
|
505
170
|
|
506
171
|
# Register the service with transient lifetime
|
507
172
|
self.__bindings[abstract] = Binding(
|
@@ -557,34 +222,34 @@ class Container(IContainer):
|
|
557
222
|
"""
|
558
223
|
|
559
224
|
# Ensure that abstract is an abstract class
|
560
|
-
|
225
|
+
IsAbstractClass(abstract, Lifetime.SINGLETON)
|
561
226
|
|
562
227
|
# Ensure that concrete is a concrete class
|
563
|
-
|
228
|
+
IsConcreteClass(concrete, Lifetime.SINGLETON)
|
564
229
|
|
565
230
|
# Ensure that concrete is NOT a subclass of abstract
|
566
231
|
if enforce_decoupling:
|
567
|
-
|
232
|
+
IsNotSubclass(abstract, concrete)
|
568
233
|
|
569
234
|
# Validate that concrete is a subclass of abstract
|
570
235
|
else:
|
571
|
-
|
236
|
+
IsSubclass(abstract, concrete)
|
572
237
|
|
573
238
|
# Ensure implementation
|
574
|
-
|
239
|
+
ImplementsAbstractMethods(
|
575
240
|
abstract=abstract,
|
576
241
|
concrete=concrete
|
577
242
|
)
|
578
243
|
|
579
244
|
# Ensure that the alias is a valid string if provided
|
580
245
|
if alias:
|
581
|
-
|
246
|
+
IsValidAlias(alias)
|
582
247
|
else:
|
583
248
|
rf_asbtract = ReflectionAbstract(abstract)
|
584
249
|
alias = rf_asbtract.getModuleWithClassName()
|
585
250
|
|
586
251
|
# If the service is already registered, drop it
|
587
|
-
self.
|
252
|
+
self.drop(abstract, alias)
|
588
253
|
|
589
254
|
# Register the service with singleton lifetime
|
590
255
|
self.__bindings[abstract] = Binding(
|
@@ -640,34 +305,34 @@ class Container(IContainer):
|
|
640
305
|
"""
|
641
306
|
|
642
307
|
# Ensure that abstract is an abstract class
|
643
|
-
|
308
|
+
IsAbstractClass(abstract, Lifetime.SCOPED)
|
644
309
|
|
645
310
|
# Ensure that concrete is a concrete class
|
646
|
-
|
311
|
+
IsConcreteClass(concrete, Lifetime.SCOPED)
|
647
312
|
|
648
313
|
# Ensure that concrete is NOT a subclass of abstract
|
649
314
|
if enforce_decoupling:
|
650
|
-
|
315
|
+
IsNotSubclass(abstract, concrete)
|
651
316
|
|
652
317
|
# Validate that concrete is a subclass of abstract
|
653
318
|
else:
|
654
|
-
|
319
|
+
IsSubclass(abstract, concrete)
|
655
320
|
|
656
321
|
# Ensure implementation
|
657
|
-
|
322
|
+
ImplementsAbstractMethods(
|
658
323
|
abstract=abstract,
|
659
324
|
concrete=concrete
|
660
325
|
)
|
661
326
|
|
662
327
|
# Ensure that the alias is a valid string if provided
|
663
328
|
if alias:
|
664
|
-
|
329
|
+
IsValidAlias(alias)
|
665
330
|
else:
|
666
331
|
rf_asbtract = ReflectionAbstract(abstract)
|
667
332
|
alias = rf_asbtract.getModuleWithClassName()
|
668
333
|
|
669
334
|
# If the service is already registered, drop it
|
670
|
-
self.
|
335
|
+
self.drop(abstract, alias)
|
671
336
|
|
672
337
|
# Register the service with scoped lifetime
|
673
338
|
self.__bindings[abstract] = Binding(
|
@@ -721,34 +386,34 @@ class Container(IContainer):
|
|
721
386
|
"""
|
722
387
|
|
723
388
|
# Ensure that the abstract is an abstract class
|
724
|
-
|
389
|
+
IsAbstractClass(abstract, f"Instance {Lifetime.SINGLETON}")
|
725
390
|
|
726
391
|
# Ensure that the instance is a valid instance
|
727
|
-
|
392
|
+
IsInstance(instance)
|
728
393
|
|
729
394
|
# Ensure that instance is NOT a subclass of abstract
|
730
395
|
if enforce_decoupling:
|
731
|
-
|
396
|
+
IsNotSubclass(abstract, instance.__class__)
|
732
397
|
|
733
398
|
# Validate that instance is a subclass of abstract
|
734
399
|
else:
|
735
|
-
|
400
|
+
IsSubclass(abstract, instance.__class__)
|
736
401
|
|
737
402
|
# Ensure implementation
|
738
|
-
|
403
|
+
ImplementsAbstractMethods(
|
739
404
|
abstract=abstract,
|
740
405
|
instance=instance
|
741
406
|
)
|
742
407
|
|
743
408
|
# Ensure that the alias is a valid string if provided
|
744
409
|
if alias:
|
745
|
-
|
410
|
+
IsValidAlias(alias)
|
746
411
|
else:
|
747
412
|
rf_asbtract = ReflectionAbstract(abstract)
|
748
413
|
alias = rf_asbtract.getModuleWithClassName()
|
749
414
|
|
750
415
|
# If the service is already registered, drop it
|
751
|
-
self.
|
416
|
+
self.drop(abstract, alias)
|
752
417
|
|
753
418
|
# Register the instance with the abstract type
|
754
419
|
self.__bindings[abstract] = Binding(
|
@@ -796,27 +461,15 @@ class Container(IContainer):
|
|
796
461
|
OrionisContainerException
|
797
462
|
If the lifetime is not allowed for the function signature.
|
798
463
|
"""
|
464
|
+
|
799
465
|
# Normalize and validate the lifetime parameter
|
800
|
-
|
801
|
-
if isinstance(lifetime, str):
|
802
|
-
lifetime_key = lifetime.strip().upper()
|
803
|
-
if lifetime_key in Lifetime.__members__:
|
804
|
-
lifetime = Lifetime[lifetime_key]
|
805
|
-
else:
|
806
|
-
valid = ', '.join(Lifetime.__members__.keys())
|
807
|
-
raise OrionisContainerTypeError(
|
808
|
-
f"Invalid lifetime '{lifetime}'. Valid options are: {valid}."
|
809
|
-
)
|
810
|
-
else:
|
811
|
-
raise OrionisContainerTypeError(
|
812
|
-
f"Lifetime must be of type str or Lifetime enum, got {type(lifetime).__name__}."
|
813
|
-
)
|
466
|
+
lifetime = LifetimeValidator(lifetime)
|
814
467
|
|
815
468
|
# Ensure that the alias is a valid string
|
816
|
-
|
469
|
+
IsValidAlias(alias)
|
817
470
|
|
818
471
|
# Validate that the function is callable
|
819
|
-
|
472
|
+
IsCallable(fn)
|
820
473
|
|
821
474
|
# Inspect the function signature
|
822
475
|
params = ReflectionCallable(fn).getDependencies()
|
@@ -828,7 +481,7 @@ class Container(IContainer):
|
|
828
481
|
)
|
829
482
|
|
830
483
|
# If the service is already registered, drop it
|
831
|
-
self.
|
484
|
+
self.drop(None, alias)
|
832
485
|
|
833
486
|
# Register the function with the specified alias and lifetime
|
834
487
|
self.__bindings[alias] = Binding(
|
@@ -869,369 +522,115 @@ class Container(IContainer):
|
|
869
522
|
or abstract_or_alias in self.__aliasses
|
870
523
|
)
|
871
524
|
|
872
|
-
def
|
525
|
+
def getBinding(
|
873
526
|
self,
|
874
|
-
abstract_or_alias: Any
|
875
|
-
|
876
|
-
**kwargs: dict
|
877
|
-
) -> Any:
|
527
|
+
abstract_or_alias: Any
|
528
|
+
) -> Binding:
|
878
529
|
"""
|
879
|
-
|
530
|
+
Retrieves the binding for the requested abstract type or alias.
|
880
531
|
|
881
532
|
Parameters
|
882
533
|
----------
|
883
534
|
abstract_or_alias : Any
|
884
|
-
The abstract class, interface, or alias (str) to
|
885
|
-
*args : tuple
|
886
|
-
Positional arguments to pass to the constructor of the resolved service.
|
887
|
-
**kwargs : dict
|
888
|
-
Keyword arguments to pass to the constructor of the resolved service.
|
535
|
+
The abstract class, interface, or alias (str) to retrieve.
|
889
536
|
|
890
537
|
Returns
|
891
538
|
-------
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
Raises
|
896
|
-
------
|
897
|
-
OrionisContainerException
|
898
|
-
If the requested service is not registered in the container.
|
539
|
+
Binding
|
540
|
+
The binding associated with the requested abstract type or alias.
|
899
541
|
"""
|
900
|
-
|
901
|
-
binding = self.__getService(abstract_or_alias)
|
902
|
-
|
903
|
-
# Check if the requested service is registered in the container
|
904
|
-
if not binding:
|
905
|
-
raise OrionisContainerException(
|
906
|
-
f"The requested service '{abstract_or_alias}' is not registered in the container."
|
907
|
-
)
|
908
|
-
|
909
|
-
# Handle based on binding type and lifetime
|
910
|
-
if binding.lifetime == Lifetime.TRANSIENT:
|
911
|
-
return self.__resolveTransient(binding, *args, **kwargs)
|
912
|
-
elif binding.lifetime == Lifetime.SINGLETON:
|
913
|
-
return self.__resolveSingleton(binding, *args, **kwargs)
|
914
|
-
elif binding.lifetime == Lifetime.SCOPED:
|
915
|
-
# TODO: Implement scoped lifetime resolution
|
916
|
-
raise OrionisContainerException(
|
917
|
-
"Scoped lifetime resolution is not yet implemented."
|
918
|
-
)
|
542
|
+
return self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
|
919
543
|
|
920
|
-
def
|
544
|
+
def drop(
|
545
|
+
self,
|
546
|
+
abstract: Callable[..., Any] = None,
|
547
|
+
alias: str = None
|
548
|
+
) -> None:
|
921
549
|
"""
|
922
|
-
|
550
|
+
Drops a service from the container by removing its bindings and aliases.
|
551
|
+
This method allows removing registered services from the dependency injection container,
|
552
|
+
either by their abstract type or by their alias. When a service is dropped,
|
553
|
+
all its bindings and aliases are removed from the container.
|
923
554
|
|
924
|
-
|
925
|
-
----------
|
926
|
-
binding : Binding
|
927
|
-
The binding to resolve.
|
928
|
-
*args : tuple
|
929
|
-
Positional arguments to pass to the constructor.
|
930
|
-
**kwargs : dict
|
931
|
-
Keyword arguments to pass to the constructor.
|
932
|
-
|
933
|
-
Returns
|
555
|
+
Warning
|
934
556
|
-------
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
else:
|
944
|
-
return self.__instantiateConcreteReflective(binding.concrete)
|
945
|
-
|
946
|
-
# If the binding has a function defined
|
947
|
-
elif binding.function:
|
948
|
-
if args or kwargs:
|
949
|
-
return self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
950
|
-
else:
|
951
|
-
return self.__instantiateCallableReflective(binding.function)
|
952
|
-
|
953
|
-
# If neither concrete class nor function is defined
|
954
|
-
else:
|
955
|
-
raise OrionisContainerException(
|
956
|
-
"Cannot resolve transient binding: neither a concrete class nor a function is defined."
|
957
|
-
)
|
557
|
+
Using this method irresponsibly can severely damage the system's logic.
|
558
|
+
Only use it when you are certain about the consequences, as removing
|
559
|
+
critical services may lead to system failures and unexpected behavior.
|
560
|
+
abstract : Callable[..., Any], optional
|
561
|
+
The abstract type or interface to be removed from the container.
|
562
|
+
If provided, both the binding and the default alias for this type will be removed.
|
563
|
+
The alias of the service to be removed. If provided, both the alias entry
|
564
|
+
and any associated binding will be removed.
|
958
565
|
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
Parameters
|
964
|
-
----------
|
965
|
-
binding : Binding
|
966
|
-
The binding to resolve.
|
967
|
-
*args : tuple
|
968
|
-
Positional arguments to pass to the constructor (only used if instance doesn't exist yet).
|
969
|
-
**kwargs : dict
|
970
|
-
Keyword arguments to pass to the constructor (only used if instance doesn't exist yet).
|
971
|
-
|
972
|
-
Returns
|
973
|
-
-------
|
974
|
-
Any
|
975
|
-
The singleton instance of the requested service.
|
976
|
-
"""
|
977
|
-
# Return existing instance if available
|
978
|
-
if binding.instance:
|
979
|
-
return binding.instance
|
980
|
-
|
981
|
-
# Create instance if needed
|
982
|
-
if binding.concrete:
|
983
|
-
if args or kwargs:
|
984
|
-
binding.instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
985
|
-
else:
|
986
|
-
binding.instance = self.__instantiateConcreteReflective(binding.concrete)
|
987
|
-
return binding.instance
|
988
|
-
|
989
|
-
# If the binding has a function defined
|
990
|
-
elif binding.function:
|
991
|
-
if args or kwargs:
|
992
|
-
result = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
993
|
-
else:
|
994
|
-
result = self.__instantiateCallableReflective(binding.function)
|
995
|
-
|
996
|
-
# Store the result directly as the singleton instance
|
997
|
-
# We don't automatically invoke factory function results anymore
|
998
|
-
binding.instance = result
|
999
|
-
return binding.instance
|
1000
|
-
|
1001
|
-
# If neither concrete class nor function is defined
|
1002
|
-
else:
|
1003
|
-
raise OrionisContainerException(
|
1004
|
-
"Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
|
1005
|
-
)
|
1006
|
-
|
1007
|
-
def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
|
566
|
+
Notes
|
567
|
+
-----
|
568
|
+
At least one parameter (abstract or alias) must be provided for the method to take effect.
|
569
|
+
If both are provided, both will be processed independently.
|
1008
570
|
"""
|
1009
|
-
Instantiates a concrete class with the provided arguments.
|
1010
|
-
|
1011
|
-
Parameters
|
1012
|
-
----------
|
1013
|
-
concrete : Callable[..., Any]
|
1014
|
-
Class to instantiate.
|
1015
|
-
*args : tuple
|
1016
|
-
Positional arguments to pass to the constructor.
|
1017
|
-
**kwargs : dict
|
1018
|
-
Keyword arguments to pass to the constructor.
|
1019
571
|
|
1020
|
-
|
1021
|
-
|
1022
|
-
object
|
1023
|
-
A new instance of the specified concrete class.
|
1024
|
-
"""
|
572
|
+
# If abstract is provided
|
573
|
+
if abstract:
|
1025
574
|
|
1026
|
-
|
1027
|
-
|
575
|
+
# Remove the abstract service from the bindings if it exists
|
576
|
+
if abstract in self.__bindings:
|
577
|
+
del self.__bindings[abstract]
|
1028
578
|
|
1029
|
-
#
|
1030
|
-
|
579
|
+
# Remove the default alias (module + class name) from aliases if it exists
|
580
|
+
abs_alias = ReflectionAbstract(abstract).getModuleWithClassName()
|
581
|
+
if abs_alias in self.__aliasses:
|
582
|
+
del self.__aliasses[abs_alias]
|
1031
583
|
|
1032
|
-
|
584
|
+
# If a custom alias is provided
|
585
|
+
if alias:
|
1033
586
|
|
1034
|
-
#
|
1035
|
-
|
1036
|
-
|
1037
|
-
signature = rf_concrete.getConstructorSignature()
|
587
|
+
# Remove it from the aliases dictionary if it exists
|
588
|
+
if alias in self.__aliasses:
|
589
|
+
del self.__aliasses[alias]
|
1038
590
|
|
1039
|
-
#
|
1040
|
-
|
1041
|
-
|
1042
|
-
f"Expected constructor signature: [{signature}]"
|
1043
|
-
) from e
|
591
|
+
# Remove the binding associated with the alias
|
592
|
+
if alias in self.__bindings:
|
593
|
+
del self.__bindings[alias]
|
1044
594
|
|
1045
|
-
def
|
595
|
+
def make(
|
596
|
+
self,
|
597
|
+
abstract_or_alias: Any,
|
598
|
+
*args: tuple,
|
599
|
+
**kwargs: dict
|
600
|
+
) -> Any:
|
1046
601
|
"""
|
1047
|
-
|
602
|
+
Resolves and returns an instance of the requested service.
|
1048
603
|
|
1049
604
|
Parameters
|
1050
605
|
----------
|
1051
|
-
|
1052
|
-
The
|
606
|
+
abstract_or_alias : Any
|
607
|
+
The abstract class, interface, or alias (str) to resolve.
|
1053
608
|
*args : tuple
|
1054
|
-
Positional arguments to pass to the
|
609
|
+
Positional arguments to pass to the constructor of the resolved service.
|
1055
610
|
**kwargs : dict
|
1056
|
-
Keyword arguments to pass to the
|
1057
|
-
|
1058
|
-
Returns
|
1059
|
-
-------
|
1060
|
-
Any
|
1061
|
-
The result of the callable.
|
1062
|
-
"""
|
1063
|
-
|
1064
|
-
# Try to invoke the callable with the provided arguments
|
1065
|
-
try:
|
1066
|
-
|
1067
|
-
# If the callable is a function, invoke it directly
|
1068
|
-
return fn(*args, **kwargs)
|
1069
|
-
|
1070
|
-
except TypeError as e:
|
1071
|
-
|
1072
|
-
# If invocation fails, use ReflectionCallable to get function name and signature
|
1073
|
-
rf_callable = ReflectionCallable(fn)
|
1074
|
-
function_name = rf_callable.getName()
|
1075
|
-
signature = rf_callable.getSignature()
|
1076
|
-
|
1077
|
-
# Raise an exception with detailed information about the failure
|
1078
|
-
raise OrionisContainerException(
|
1079
|
-
f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
|
1080
|
-
f"Expected function signature: [{signature}]"
|
1081
|
-
) from e
|
1082
|
-
|
1083
|
-
def __instantiateConcreteReflective(self, concrete: Callable[..., Any]) -> Any:
|
1084
|
-
"""
|
1085
|
-
Instantiates a concrete class reflectively, resolving its dependencies from the container.
|
1086
|
-
|
1087
|
-
Parameters
|
1088
|
-
----------
|
1089
|
-
concrete : Callable[..., Any]
|
1090
|
-
The concrete class to instantiate.
|
1091
|
-
|
1092
|
-
Returns
|
1093
|
-
-------
|
1094
|
-
Any
|
1095
|
-
A new instance of the concrete class.
|
1096
|
-
"""
|
1097
|
-
# Resolve dependencies for the concrete class
|
1098
|
-
params = self.__resolveDependencies(concrete, is_class=True)
|
1099
|
-
|
1100
|
-
# Instantiate the concrete class with resolved dependencies
|
1101
|
-
return concrete(**params)
|
1102
|
-
|
1103
|
-
def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
|
1104
|
-
"""
|
1105
|
-
Invokes a callable reflectively, resolving its dependencies from the container.
|
1106
|
-
|
1107
|
-
Parameters
|
1108
|
-
----------
|
1109
|
-
fn : Callable[..., Any]
|
1110
|
-
The callable to invoke.
|
611
|
+
Keyword arguments to pass to the constructor of the resolved service.
|
1111
612
|
|
1112
613
|
Returns
|
1113
614
|
-------
|
1114
615
|
Any
|
1115
|
-
|
1116
|
-
"""
|
1117
|
-
|
1118
|
-
# Resolve dependencies for the callable
|
1119
|
-
params = self.__resolveDependencies(fn, is_class=False)
|
1120
|
-
|
1121
|
-
# Invoke the callable with resolved dependencies
|
1122
|
-
return fn(**params)
|
616
|
+
An instance of the requested service.
|
1123
617
|
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
is_class: bool = False
|
1129
|
-
) -> dict:
|
618
|
+
Raises
|
619
|
+
------
|
620
|
+
OrionisContainerException
|
621
|
+
If the requested service is not registered in the container.
|
1130
622
|
"""
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
target : Callable[..., Any]
|
1136
|
-
The target callable or class whose dependencies to resolve.
|
1137
|
-
is_class : bool, optional
|
1138
|
-
Whether the target is a class (True) or a callable (False).
|
623
|
+
if not self.bound(abstract_or_alias):
|
624
|
+
raise OrionisContainerException(
|
625
|
+
f"The requested service '{abstract_or_alias}' is not registered in the container."
|
626
|
+
)
|
1139
627
|
|
1140
|
-
|
1141
|
-
|
1142
|
-
dict
|
1143
|
-
A dictionary of resolved dependencies.
|
1144
|
-
"""
|
1145
|
-
try:
|
1146
|
-
|
1147
|
-
# Use ReflectionConcrete for classes and ReflectionCallable for callables
|
1148
|
-
if is_class:
|
1149
|
-
reflection = ReflectionConcrete(target)
|
1150
|
-
dependencies = reflection.getConstructorDependencies()
|
1151
|
-
name = reflection.getClassName()
|
1152
|
-
|
1153
|
-
# If the target is a callable, use ReflectionCallable
|
1154
|
-
else:
|
1155
|
-
reflection = ReflectionCallable(target)
|
1156
|
-
dependencies = reflection.getDependencies()
|
1157
|
-
name = reflection.getName()
|
1158
|
-
|
1159
|
-
# Check for unresolved dependencies
|
1160
|
-
if dependencies.unresolved:
|
1161
|
-
unresolved_args = ', '.join(dependencies.unresolved)
|
1162
|
-
raise OrionisContainerException(
|
1163
|
-
f"Cannot resolve '{name}' because the following required arguments are missing: [{unresolved_args}]."
|
1164
|
-
)
|
1165
|
-
|
1166
|
-
# Resolve dependencies
|
1167
|
-
params = {}
|
1168
|
-
for param_name, dep in dependencies.resolved.items():
|
1169
|
-
|
1170
|
-
# If the dependency is a ResolvedDependency, resolve it
|
1171
|
-
if isinstance(dep, ResolvedDependency):
|
1172
|
-
|
1173
|
-
# If the dependency is a built-in type, raise an exception
|
1174
|
-
if dep.module_name == 'builtins':
|
1175
|
-
raise OrionisContainerException(
|
1176
|
-
f"Cannot resolve '{name}' because parameter '{param_name}' depends on built-in type '{dep.type.__name__}'."
|
1177
|
-
)
|
1178
|
-
|
1179
|
-
# Try to resolve from container
|
1180
|
-
service = self.__getFirstService([dep.type, dep.full_class_path])
|
1181
|
-
if service:
|
1182
|
-
params[param_name] = self.make(service.alias)
|
1183
|
-
|
1184
|
-
# Try to instantiate directly if it's a concrete class
|
1185
|
-
elif ReflectionConcrete.isConcreteClass(dep.type):
|
1186
|
-
params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=True))
|
1187
|
-
|
1188
|
-
# Try to call directly if it's a callable
|
1189
|
-
elif callable(dep.type) and not isinstance(dep.type, type):
|
1190
|
-
params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=False))
|
1191
|
-
|
1192
|
-
# If the dependency cannot be resolved, raise an exception
|
1193
|
-
else:
|
1194
|
-
raise OrionisContainerException(
|
1195
|
-
f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}' for '{name}'."
|
1196
|
-
)
|
1197
|
-
else:
|
1198
|
-
# Use default value
|
1199
|
-
params[param_name] = dep
|
1200
|
-
|
1201
|
-
# Return the resolved parameters
|
1202
|
-
return params
|
1203
|
-
|
1204
|
-
except ImportError as e:
|
1205
|
-
|
1206
|
-
# Extract module name from the error message if possible
|
1207
|
-
import_msg = str(e)
|
1208
|
-
module_name = target.__module__ if hasattr(target, '__module__') else "unknown module"
|
1209
|
-
|
1210
|
-
# Check for potential circular import patterns
|
1211
|
-
if "circular import" in import_msg.lower() or "cannot import name" in import_msg.lower():
|
1212
|
-
raise OrionisContainerException(
|
1213
|
-
f"Circular import detected while resolving dependencies for '{target.__name__}' in module '{module_name}'.\n"
|
1214
|
-
f"This typically happens when two modules import each other. Consider:\n"
|
1215
|
-
f"1. Restructuring your code to avoid circular dependencies\n"
|
1216
|
-
f"2. Using delayed imports inside methods rather than at module level\n"
|
1217
|
-
f"3. Using dependency injection to break the cycle\n"
|
1218
|
-
f"Original error: {import_msg}"
|
1219
|
-
) from e
|
1220
|
-
else:
|
1221
|
-
raise OrionisContainerException(
|
1222
|
-
f"Import error while resolving dependencies for '{target.__name__}' in module '{module_name}':\n"
|
1223
|
-
f"{import_msg}"
|
1224
|
-
) from e
|
1225
|
-
|
1226
|
-
except Exception as e:
|
1227
|
-
|
1228
|
-
# Get more context about where the error occurred
|
1229
|
-
target_type = "class" if isinstance(target, type) else "function"
|
1230
|
-
target_name = target.__name__ if hasattr(target, '__name__') else str(target)
|
1231
|
-
module_name = target.__module__ if hasattr(target, '__module__') else "unknown module"
|
628
|
+
# Get the binding for the requested abstract type or alias
|
629
|
+
binding = self.getBinding(abstract_or_alias)
|
1232
630
|
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
631
|
+
# Resolve the binding using the Resolver class
|
632
|
+
return Resolver(self).resolve(
|
633
|
+
binding,
|
634
|
+
*args,
|
635
|
+
**kwargs
|
636
|
+
)
|