orionis 0.321.0__py3-none-any.whl → 0.322.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 +25 -2
- orionis/container/context/manager.py +94 -0
- orionis/container/context/scope.py +40 -0
- orionis/container/contracts/container.py +1 -1
- orionis/container/resolver.py +54 -5
- orionis/metadata/framework.py +1 -1
- {orionis-0.321.0.dist-info → orionis-0.322.0.dist-info}/METADATA +1 -1
- {orionis-0.321.0.dist-info → orionis-0.322.0.dist-info}/RECORD +13 -16
- orionis/_container/container.py +0 -543
- orionis/_container/container_integrity.py +0 -292
- orionis/_container/exception.py +0 -54
- orionis/_container/lifetimes.py +0 -13
- orionis/_container/resolve.py +0 -64
- /orionis/{_container → container/context}/__init__.py +0 -0
- {orionis-0.321.0.dist-info → orionis-0.322.0.dist-info}/WHEEL +0 -0
- {orionis-0.321.0.dist-info → orionis-0.322.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.321.0.dist-info → orionis-0.322.0.dist-info}/top_level.txt +0 -0
- {orionis-0.321.0.dist-info → orionis-0.322.0.dist-info}/zip-safe +0 -0
orionis/container/container.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import threading
|
2
2
|
from typing import Any, Callable
|
3
|
+
from orionis.container.context.manager import ScopeManager
|
3
4
|
from orionis.container.contracts.container import IContainer
|
4
5
|
from orionis.container.entities.binding import Binding
|
5
6
|
from orionis.container.enums.lifetimes import Lifetime
|
@@ -430,7 +431,7 @@ class Container(IContainer):
|
|
430
431
|
# Return True to indicate successful registration
|
431
432
|
return True
|
432
433
|
|
433
|
-
def
|
434
|
+
def callable(
|
434
435
|
self,
|
435
436
|
alias: str,
|
436
437
|
fn: Callable[..., Any],
|
@@ -633,4 +634,26 @@ class Container(IContainer):
|
|
633
634
|
binding,
|
634
635
|
*args,
|
635
636
|
**kwargs
|
636
|
-
)
|
637
|
+
)
|
638
|
+
|
639
|
+
def createContext(self) -> ScopeManager:
|
640
|
+
"""
|
641
|
+
Creates a new context for managing scoped services.
|
642
|
+
|
643
|
+
This method returns a context manager that can be used with a 'with' statement
|
644
|
+
to control the lifecycle of scoped services.
|
645
|
+
|
646
|
+
Returns
|
647
|
+
-------
|
648
|
+
ScopeManager
|
649
|
+
A context manager for scoped services.
|
650
|
+
|
651
|
+
Usage
|
652
|
+
-------
|
653
|
+
with container.createContext():
|
654
|
+
# Scoped services created here will be disposed when exiting this block
|
655
|
+
service = container.make(IScopedService)
|
656
|
+
...
|
657
|
+
# Scoped services are automatically disposed here
|
658
|
+
"""
|
659
|
+
return ScopeManager()
|
@@ -0,0 +1,94 @@
|
|
1
|
+
from orionis.container.context.scope import ScopedContext
|
2
|
+
|
3
|
+
class ScopeManager:
|
4
|
+
"""
|
5
|
+
A context manager to manage scoped lifetimes in the container.
|
6
|
+
"""
|
7
|
+
def __init__(self):
|
8
|
+
"""
|
9
|
+
Initialize a new ScopeManager with an empty instances dictionary.
|
10
|
+
"""
|
11
|
+
self._instances = {}
|
12
|
+
|
13
|
+
def __getitem__(self, key):
|
14
|
+
"""
|
15
|
+
Get an instance by key.
|
16
|
+
|
17
|
+
Parameters
|
18
|
+
----------
|
19
|
+
key : hashable
|
20
|
+
The key of the instance to retrieve.
|
21
|
+
|
22
|
+
Returns
|
23
|
+
-------
|
24
|
+
object or None
|
25
|
+
The instance associated with the key or None if not found.
|
26
|
+
"""
|
27
|
+
return self._instances.get(key)
|
28
|
+
|
29
|
+
def __setitem__(self, key, value):
|
30
|
+
"""
|
31
|
+
Store an instance by key.
|
32
|
+
|
33
|
+
Parameters
|
34
|
+
----------
|
35
|
+
key : hashable
|
36
|
+
The key to associate with the instance.
|
37
|
+
value : object
|
38
|
+
The instance to store.
|
39
|
+
"""
|
40
|
+
self._instances[key] = value
|
41
|
+
|
42
|
+
def __contains__(self, key):
|
43
|
+
"""
|
44
|
+
Check if a key exists in this scope.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
key : hashable
|
49
|
+
The key to check.
|
50
|
+
|
51
|
+
Returns
|
52
|
+
-------
|
53
|
+
bool
|
54
|
+
True if the key exists in the scope, False otherwise.
|
55
|
+
"""
|
56
|
+
return key in self._instances
|
57
|
+
|
58
|
+
def clear(self):
|
59
|
+
"""
|
60
|
+
Clear all instances from this scope.
|
61
|
+
"""
|
62
|
+
self._instances.clear()
|
63
|
+
|
64
|
+
def __enter__(self):
|
65
|
+
"""
|
66
|
+
Enter the scope context.
|
67
|
+
|
68
|
+
Sets this scope as the current active scope.
|
69
|
+
|
70
|
+
Returns
|
71
|
+
-------
|
72
|
+
ScopeManager
|
73
|
+
This scope manager instance.
|
74
|
+
"""
|
75
|
+
ScopedContext.setCurrentScope(self)
|
76
|
+
return self
|
77
|
+
|
78
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
79
|
+
"""
|
80
|
+
Exit the scope context.
|
81
|
+
|
82
|
+
Clears this scope and the active scope reference.
|
83
|
+
|
84
|
+
Parameters
|
85
|
+
----------
|
86
|
+
exc_type : type or None
|
87
|
+
The exception type if an exception was raised, None otherwise.
|
88
|
+
exc_val : Exception or None
|
89
|
+
The exception instance if an exception was raised, None otherwise.
|
90
|
+
exc_tb : traceback or None
|
91
|
+
The exception traceback if an exception was raised, None otherwise.
|
92
|
+
"""
|
93
|
+
self.clear()
|
94
|
+
ScopedContext.clear()
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import contextvars
|
2
|
+
|
3
|
+
class ScopedContext:
|
4
|
+
"""
|
5
|
+
Holds scoped instances for the current context.
|
6
|
+
"""
|
7
|
+
_active_scope = contextvars.ContextVar("orionis_scope", default=None)
|
8
|
+
|
9
|
+
@classmethod
|
10
|
+
def getCurrentScope(cls):
|
11
|
+
"""
|
12
|
+
Get the currently active scope.
|
13
|
+
|
14
|
+
Returns
|
15
|
+
-------
|
16
|
+
object or None
|
17
|
+
The current active scope or None if no scope is active.
|
18
|
+
"""
|
19
|
+
return cls._active_scope.get()
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def setCurrentScope(cls, scope):
|
23
|
+
"""
|
24
|
+
Set the current active scope.
|
25
|
+
|
26
|
+
Parameters
|
27
|
+
----------
|
28
|
+
scope : object
|
29
|
+
The scope object to set as active.
|
30
|
+
"""
|
31
|
+
cls._active_scope.set(scope)
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
def clear(cls):
|
35
|
+
"""
|
36
|
+
Clear the current active scope.
|
37
|
+
|
38
|
+
Sets the active scope to None.
|
39
|
+
"""
|
40
|
+
cls._active_scope.set(None)
|
orionis/container/resolver.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from typing import Any, Callable
|
2
|
+
from orionis.container.context.scope import ScopedContext
|
2
3
|
from orionis.container.contracts.container import IContainer
|
3
4
|
from orionis.container.entities.binding import Binding
|
4
5
|
from orionis.container.enums.lifetimes import Lifetime
|
@@ -61,10 +62,7 @@ class Resolver:
|
|
61
62
|
elif binding.lifetime == Lifetime.SINGLETON:
|
62
63
|
return self.__resolveSingleton(binding, *args, **kwargs)
|
63
64
|
elif binding.lifetime == Lifetime.SCOPED:
|
64
|
-
|
65
|
-
raise OrionisContainerException(
|
66
|
-
"Scoped lifetime resolution is not yet implemented."
|
67
|
-
)
|
65
|
+
return self.__resolveScoped(binding, *args, **kwargs)
|
68
66
|
|
69
67
|
def __resolveTransient(self, binding: Binding, *args, **kwargs) -> Any:
|
70
68
|
"""
|
@@ -153,6 +151,58 @@ class Resolver:
|
|
153
151
|
"Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
|
154
152
|
)
|
155
153
|
|
154
|
+
def __resolveScoped(self, binding: Binding, *args, **kwargs) -> Any:
|
155
|
+
"""
|
156
|
+
Resolves a service with scoped lifetime.
|
157
|
+
|
158
|
+
Parameters
|
159
|
+
----------
|
160
|
+
binding : Binding
|
161
|
+
The binding to resolve.
|
162
|
+
*args : tuple
|
163
|
+
Positional arguments to pass to the constructor.
|
164
|
+
**kwargs : dict
|
165
|
+
Keyword arguments to pass to the constructor.
|
166
|
+
|
167
|
+
Returns
|
168
|
+
-------
|
169
|
+
Any
|
170
|
+
The scoped instance of the requested service.
|
171
|
+
|
172
|
+
Raises
|
173
|
+
------
|
174
|
+
OrionisContainerException
|
175
|
+
If no scope is active or service can't be resolved.
|
176
|
+
"""
|
177
|
+
scope = ScopedContext.getCurrentScope()
|
178
|
+
if scope is None:
|
179
|
+
raise OrionisContainerException(
|
180
|
+
f"No active scope found while resolving scoped service '{binding.alias}'. "
|
181
|
+
f"Use 'with container.createContext():' to create a scope context."
|
182
|
+
)
|
183
|
+
|
184
|
+
if binding.alias in scope:
|
185
|
+
return scope[binding.alias]
|
186
|
+
|
187
|
+
# Create a new instance
|
188
|
+
if binding.concrete:
|
189
|
+
if args or kwargs:
|
190
|
+
instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
191
|
+
else:
|
192
|
+
instance = self.__instantiateConcreteReflective(binding.concrete)
|
193
|
+
elif binding.function:
|
194
|
+
if args or kwargs:
|
195
|
+
instance = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
196
|
+
else:
|
197
|
+
instance = self.__instantiateCallableReflective(binding.function)
|
198
|
+
else:
|
199
|
+
raise OrionisContainerException(
|
200
|
+
"Cannot resolve scoped binding: neither a concrete class nor a function is defined."
|
201
|
+
)
|
202
|
+
|
203
|
+
scope[binding.alias] = instance
|
204
|
+
return instance
|
205
|
+
|
156
206
|
def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
|
157
207
|
"""
|
158
208
|
Instantiates a concrete class with the provided arguments.
|
@@ -401,4 +451,3 @@ class Resolver:
|
|
401
451
|
|
402
452
|
# Raise a more informative exception
|
403
453
|
raise OrionisContainerException(error_msg) from e
|
404
|
-
|
orionis/metadata/framework.py
CHANGED
@@ -2,12 +2,6 @@ orionis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
orionis/_application.py,sha256=dMjJ0nFcIIOBGb5zr-tHNzcgTOZ1vJ7iMdFAlqSQph0,9405
|
3
3
|
orionis/application.py,sha256=Off5uOUj-IYvvR8DcqLUoBW_98opWa7MQrtqTr0SZGc,292
|
4
4
|
orionis/unittesting.py,sha256=_NU3_sm3R6bUUH_Y-KSPgNVBajUGCtKo_CGgjB1YD5k,2094
|
5
|
-
orionis/_container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
orionis/_container/container.py,sha256=0AOqTNwpN_OtWbq9mBI99qfJ7LMkN71y0lP0JWKzut0,18289
|
7
|
-
orionis/_container/container_integrity.py,sha256=vrqZrkJaP6ghbiAzr-nEul9f_lEWVa2nMUSugQXDfWk,10095
|
8
|
-
orionis/_container/exception.py,sha256=ap1SqYEjQEEHXJJTNmL7V1jrmRjgT5_7geZ95MYkhMA,1691
|
9
|
-
orionis/_container/lifetimes.py,sha256=2lbdiV7R2WlJf1cLD6eBxLnJud_lZvX1IhQH2Djy3Ww,375
|
10
|
-
orionis/_container/resolve.py,sha256=5qVE--fBpbVFiTYM_jXKbjHNssm28aM4cwd49AhiLkY,2231
|
11
5
|
orionis/_contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
6
|
orionis/_contracts/application.py,sha256=ltuDA1mN5P73l89jJto_A96ePJWE02OZ_B2NOPpfeWs,1061
|
13
7
|
orionis/_contracts/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -135,9 +129,12 @@ orionis/console/output/console.py,sha256=TE_Hl720ADd82dbERFSWhkoQRukDQZmETSw4nkw
|
|
135
129
|
orionis/console/output/executor.py,sha256=bdvkzW2-buy0BPpy2r5qUGrRFW2Ay6k-5rSeHb0gQ3o,3352
|
136
130
|
orionis/console/output/progress_bar.py,sha256=vFy582z6VJS46LV6tuyrmr9qvdVeTEtw3hyNcEHezeg,3088
|
137
131
|
orionis/console/output/styles.py,sha256=6a4oQCOBOKMh2ARdeq5GlIskJ3wjiylYmh66tUKKmpQ,4053
|
138
|
-
orionis/container/container.py,sha256=
|
139
|
-
orionis/container/resolver.py,sha256=
|
140
|
-
orionis/container/
|
132
|
+
orionis/container/container.py,sha256=dIfHPCC28_Eho7kgp0AUFS1S5iaCD4ws7udCy_jQ6sE,23112
|
133
|
+
orionis/container/resolver.py,sha256=bR26v1bAtg9FgDtfs6zP30-g75aWrSO43UJPweJ7jfo,18014
|
134
|
+
orionis/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
135
|
+
orionis/container/context/manager.py,sha256=9yODWkHBoJ2kgJZ5ONLqcEcex50vaWuMcxsvmDgnQo4,2437
|
136
|
+
orionis/container/context/scope.py,sha256=CWFiLLTAC_IdmeFKWX-jrphdxB0_TMEVBlz6lQVMPC8,937
|
137
|
+
orionis/container/contracts/container.py,sha256=hOO3w2yqVhp2nPTeS1uJEYgXSTbM3xwezDCOSNMC_a0,7603
|
141
138
|
orionis/container/entities/binding.py,sha256=Qp6Lf4XUDp2NjqXDAC2lzvhOFQWiBDKiGFcKfwb4axw,4342
|
142
139
|
orionis/container/enums/lifetimes.py,sha256=RqQmugMIB1Ev_j_vFLcWorndm-to7xg4stQ7yKFDdDw,190
|
143
140
|
orionis/container/exceptions/container_exception.py,sha256=goTDEwC70xTMD2qppN8KV-xyR0Nps218OD4D1LZ2-3s,470
|
@@ -244,7 +241,7 @@ orionis/foundation/contracts/config.py,sha256=Rpz6U6t8OXHO9JJKSTnCimytXE-tfCB-1i
|
|
244
241
|
orionis/foundation/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
245
242
|
orionis/foundation/exceptions/integrity.py,sha256=mc4pL1UMoYRHEmphnpW2oGk5URhu7DJRREyzHaV-cs8,472
|
246
243
|
orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
247
|
-
orionis/metadata/framework.py,sha256
|
244
|
+
orionis/metadata/framework.py,sha256=-G5Ox-3m6R7JXyXZxf3c-6gmJ37J674tYlWjc8-Ar6k,4960
|
248
245
|
orionis/metadata/package.py,sha256=tqLfBRo-w1j_GN4xvzUNFyweWYFS-qhSgAEc-AmCH1M,5452
|
249
246
|
orionis/patterns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
250
247
|
orionis/patterns/singleton/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -356,7 +353,7 @@ orionis/test/suite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
356
353
|
orionis/test/suite/test_unit.py,sha256=MWgW8dRCRyT1XZ5LsbXQ7-KVPReasoXwzEEL1EWWfE4,52190
|
357
354
|
orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
358
355
|
orionis/test/view/render.py,sha256=jXZkbITBknbUwm_mD8bcTiwLDvsFkrO9qrf0ZgPwqxc,4903
|
359
|
-
orionis-0.
|
356
|
+
orionis-0.322.0.dist-info/licenses/LICENCE,sha256=-_4cF2EBKuYVS_SQpy1uapq0oJPUU1vl_RUWSy2jJTo,1111
|
360
357
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
361
358
|
tests/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
362
359
|
tests/example/test_example.py,sha256=kvWgiW3ADEZf718dGsMPtDh_rmOSx1ypEInKm7_6ZPQ,601
|
@@ -457,8 +454,8 @@ tests/support/wrapper/test_services_wrapper_docdict.py,sha256=yeVwl-VcwkWSQYyxZu
|
|
457
454
|
tests/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
458
455
|
tests/testing/test_testing_result.py,sha256=MrGK3ZimedL0b5Ydu69Dg8Iul017AzLTm7VPxpXlpfU,4315
|
459
456
|
tests/testing/test_testing_unit.py,sha256=DjLBtvVn8B1KlVJNNkstBT8_csA1yeaMqnGrbanN_J4,7438
|
460
|
-
orionis-0.
|
461
|
-
orionis-0.
|
462
|
-
orionis-0.
|
463
|
-
orionis-0.
|
464
|
-
orionis-0.
|
457
|
+
orionis-0.322.0.dist-info/METADATA,sha256=AU8sCGe16NBNt6PHdMNm_gcUrv3R8mwDPArdt-WH0qU,4772
|
458
|
+
orionis-0.322.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
459
|
+
orionis-0.322.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
|
460
|
+
orionis-0.322.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
461
|
+
orionis-0.322.0.dist-info/RECORD,,
|
orionis/_container/container.py
DELETED
@@ -1,543 +0,0 @@
|
|
1
|
-
import inspect
|
2
|
-
from threading import Lock
|
3
|
-
from typing import Callable, Any, Dict, Deque, Optional, Type, get_origin, get_args
|
4
|
-
from collections import deque
|
5
|
-
from orionis._container.container_integrity import ContainerIntegrity
|
6
|
-
from orionis._container.lifetimes import Lifetime
|
7
|
-
from orionis._container.exception import OrionisContainerException, OrionisContainerValueError, OrionisContainerTypeError
|
8
|
-
from orionis._contracts.container.container import IContainer
|
9
|
-
|
10
|
-
class Container(IContainer):
|
11
|
-
"""
|
12
|
-
Service container and dependency injection manager.
|
13
|
-
|
14
|
-
This class follows the singleton pattern to manage service bindings, instances,
|
15
|
-
and different lifecycle types such as transient, singleton, and scoped.
|
16
|
-
"""
|
17
|
-
|
18
|
-
_instance = None
|
19
|
-
_lock = Lock()
|
20
|
-
|
21
|
-
@classmethod
|
22
|
-
def destroy(cls):
|
23
|
-
"""
|
24
|
-
Destroys the container instance.
|
25
|
-
"""
|
26
|
-
cls._instance = None
|
27
|
-
|
28
|
-
def __new__(cls):
|
29
|
-
"""
|
30
|
-
Create a new instance of the container.
|
31
|
-
"""
|
32
|
-
if cls._instance is None:
|
33
|
-
with cls._lock:
|
34
|
-
if cls._instance is None:
|
35
|
-
cls._instance = super().__new__(cls)
|
36
|
-
cls._instance._scoped_instances = {}
|
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)
|
44
|
-
return cls._instance
|
45
|
-
|
46
|
-
def bind(self, abstract: Callable[..., Any], concrete: Callable[..., Any], lifetime: str = Lifetime.TRANSIENT.value) -> None:
|
47
|
-
"""
|
48
|
-
Binds an abstract type to a concrete implementation with a specified lifetime.
|
49
|
-
|
50
|
-
Parameters
|
51
|
-
----------
|
52
|
-
abstract : Callable[..., Any]
|
53
|
-
The abstract base type or alias to be bound.
|
54
|
-
concrete : Callable[..., Any]
|
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'.
|
58
|
-
|
59
|
-
Raises
|
60
|
-
------
|
61
|
-
OrionisContainerValueError
|
62
|
-
If an invalid lifetime is provided or the concrete implementation is None.
|
63
|
-
|
64
|
-
Examples
|
65
|
-
--------
|
66
|
-
>>> container.bind(MyService, MyServiceImplementation, "singleton")
|
67
|
-
"""
|
68
|
-
if lifetime not in [member.value for member in Lifetime]:
|
69
|
-
raise OrionisContainerValueError(f"Invalid lifetime type '{lifetime}'.")
|
70
|
-
|
71
|
-
if concrete is None:
|
72
|
-
raise OrionisContainerValueError("Concrete implementation cannot be None when binding a service.")
|
73
|
-
|
74
|
-
abstract = abstract or concrete
|
75
|
-
ContainerIntegrity.ensureIsCallable(concrete)
|
76
|
-
ContainerIntegrity.ensureNotMain(concrete)
|
77
|
-
|
78
|
-
service_entry = {
|
79
|
-
"concrete": concrete,
|
80
|
-
"async": inspect.iscoroutinefunction(concrete)
|
81
|
-
}
|
82
|
-
|
83
|
-
service_registry = {
|
84
|
-
Lifetime.TRANSIENT.value: self._transient_services,
|
85
|
-
Lifetime.SCOPED.value: self._scoped_services,
|
86
|
-
Lifetime.SINGLETON.value: self._singleton_services
|
87
|
-
}
|
88
|
-
|
89
|
-
if ContainerIntegrity.isAbstract(abstract):
|
90
|
-
ContainerIntegrity.ensureImplementation(abstract, concrete)
|
91
|
-
service_registry[lifetime][abstract] = service_entry
|
92
|
-
return
|
93
|
-
|
94
|
-
if ContainerIntegrity.isAlias(abstract):
|
95
|
-
service_registry[lifetime][abstract] = service_entry
|
96
|
-
return
|
97
|
-
|
98
|
-
raise OrionisContainerValueError(f"Invalid abstract type '{abstract}'. It must be a valid alias or an abstract class.")
|
99
|
-
|
100
|
-
def transient(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
|
101
|
-
"""
|
102
|
-
Registers a service with a transient lifetime.
|
103
|
-
|
104
|
-
Parameters
|
105
|
-
----------
|
106
|
-
abstract : Callable[..., Any]
|
107
|
-
The abstract base type or alias to be bound.
|
108
|
-
concrete : Callable[..., Any]
|
109
|
-
The concrete implementation to associate with the abstract type.
|
110
|
-
|
111
|
-
Examples
|
112
|
-
--------
|
113
|
-
>>> container.transient(MyService, MyServiceImplementation)
|
114
|
-
"""
|
115
|
-
|
116
|
-
self.bind(abstract, concrete, Lifetime.TRANSIENT.value)
|
117
|
-
|
118
|
-
def scoped(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
|
119
|
-
"""
|
120
|
-
Registers a service with a scoped lifetime.
|
121
|
-
|
122
|
-
Parameters
|
123
|
-
----------
|
124
|
-
abstract : Callable[..., Any]
|
125
|
-
The abstract base type or alias to be bound.
|
126
|
-
concrete : Callable[..., Any]
|
127
|
-
The concrete implementation to associate with the abstract type.
|
128
|
-
|
129
|
-
Examples
|
130
|
-
--------
|
131
|
-
>>> container.scoped(MyService, MyServiceImplementation)
|
132
|
-
"""
|
133
|
-
|
134
|
-
self.bind(abstract, concrete, Lifetime.SCOPED.value)
|
135
|
-
|
136
|
-
def singleton(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
|
137
|
-
"""
|
138
|
-
Registers a service with a singleton lifetime.
|
139
|
-
|
140
|
-
Parameters
|
141
|
-
----------
|
142
|
-
abstract : Callable[..., Any]
|
143
|
-
The abstract base type or alias to be bound.
|
144
|
-
concrete : Callable[..., Any]
|
145
|
-
The concrete implementation to associate with the abstract type.
|
146
|
-
|
147
|
-
Examples
|
148
|
-
--------
|
149
|
-
>>> container.singleton(MyService, MyServiceImplementation)
|
150
|
-
"""
|
151
|
-
|
152
|
-
self.bind(abstract, concrete, Lifetime.SINGLETON.value)
|
153
|
-
|
154
|
-
def instance(self, abstract: Callable[..., Any], instance: Any) -> None:
|
155
|
-
"""
|
156
|
-
Registers an already instantiated object in the container.
|
157
|
-
|
158
|
-
Parameters
|
159
|
-
----------
|
160
|
-
abstract : Callable[..., Any]
|
161
|
-
The abstract base type or alias to be bound.
|
162
|
-
instance : Any
|
163
|
-
The instance to be stored.
|
164
|
-
|
165
|
-
Raises
|
166
|
-
------
|
167
|
-
OrionisContainerValueError
|
168
|
-
If the instance is None.
|
169
|
-
|
170
|
-
Examples
|
171
|
-
--------
|
172
|
-
>>> container.instance(MyService, my_service_instance)
|
173
|
-
"""
|
174
|
-
|
175
|
-
if instance is None:
|
176
|
-
raise OrionisContainerValueError("The provided instance cannot be None.")
|
177
|
-
|
178
|
-
ContainerIntegrity.ensureIsInstance(instance)
|
179
|
-
|
180
|
-
if ContainerIntegrity.isAbstract(abstract):
|
181
|
-
ContainerIntegrity.ensureImplementation(abstract, instance.__class__)
|
182
|
-
self._instances_services[abstract] = instance
|
183
|
-
return
|
184
|
-
|
185
|
-
if ContainerIntegrity.isAlias(abstract):
|
186
|
-
self._instances_services[abstract] = instance
|
187
|
-
return
|
188
|
-
|
189
|
-
raise OrionisContainerValueError(f"Invalid abstract type '{abstract}'. It must be a valid alias or an abstract class.")
|
190
|
-
|
191
|
-
def bound(self, abstract_or_alias: Callable[..., Any]) -> bool:
|
192
|
-
"""
|
193
|
-
Checks if a service or alias is bound in the container.
|
194
|
-
|
195
|
-
Parameters
|
196
|
-
----------
|
197
|
-
abstract_or_alias : Callable[..., Any]
|
198
|
-
The abstract type or alias to check.
|
199
|
-
|
200
|
-
Returns
|
201
|
-
-------
|
202
|
-
bool
|
203
|
-
True if the service is bound, False otherwise.
|
204
|
-
|
205
|
-
Examples
|
206
|
-
--------
|
207
|
-
>>> container.bound(MyService)
|
208
|
-
True
|
209
|
-
"""
|
210
|
-
|
211
|
-
service_dicts = [
|
212
|
-
self._instances_services,
|
213
|
-
self._transient_services,
|
214
|
-
self._scoped_services,
|
215
|
-
self._singleton_services,
|
216
|
-
self._aliases_services
|
217
|
-
]
|
218
|
-
return any(abstract_or_alias in service_dict for service_dict in service_dicts)
|
219
|
-
|
220
|
-
def has(self, abstract_or_alias: Callable[..., Any]) -> bool:
|
221
|
-
"""
|
222
|
-
Alias for `bound()` method.
|
223
|
-
|
224
|
-
Parameters
|
225
|
-
----------
|
226
|
-
abstract_or_alias : Callable[..., Any]
|
227
|
-
The abstract type or alias to check.
|
228
|
-
|
229
|
-
Returns
|
230
|
-
-------
|
231
|
-
bool
|
232
|
-
True if the service is bound, False otherwise.
|
233
|
-
|
234
|
-
Examples
|
235
|
-
--------
|
236
|
-
>>> container.has(MyService)
|
237
|
-
True
|
238
|
-
"""
|
239
|
-
|
240
|
-
return self.bound(abstract_or_alias)
|
241
|
-
|
242
|
-
def alias(self, alias: Callable[..., Any], abstract: Callable[..., Any]) -> None:
|
243
|
-
"""
|
244
|
-
Creates an alias for an existing abstract binding.
|
245
|
-
|
246
|
-
Parameters
|
247
|
-
----------
|
248
|
-
alias : Callable[..., Any]
|
249
|
-
The alias name.
|
250
|
-
abstract : Callable[..., Any]
|
251
|
-
The existing abstract type to alias.
|
252
|
-
|
253
|
-
Raises
|
254
|
-
------
|
255
|
-
OrionisContainerValueError
|
256
|
-
If the abstract type is not registered or the alias is already in use.
|
257
|
-
|
258
|
-
Examples
|
259
|
-
--------
|
260
|
-
>>> container.alias("DatabaseService", MyDatabaseService)
|
261
|
-
"""
|
262
|
-
|
263
|
-
if not self.has(abstract):
|
264
|
-
raise OrionisContainerValueError(f"Abstract '{abstract}' is not registered in the container.")
|
265
|
-
|
266
|
-
if alias in self._aliases_services:
|
267
|
-
raise OrionisContainerValueError(f"Alias '{alias}' is already in use.")
|
268
|
-
|
269
|
-
if not ContainerIntegrity.isAlias(abstract):
|
270
|
-
raise OrionisContainerValueError(f"Invalid target abstract type: {abstract}. It must be an alias.")
|
271
|
-
|
272
|
-
self._aliases_services[alias] = abstract
|
273
|
-
|
274
|
-
def isAlias(self, name: str) -> bool:
|
275
|
-
"""
|
276
|
-
Checks if a given name is an alias.
|
277
|
-
|
278
|
-
Parameters
|
279
|
-
----------
|
280
|
-
name : str
|
281
|
-
The name to check.
|
282
|
-
|
283
|
-
Returns
|
284
|
-
-------
|
285
|
-
bool
|
286
|
-
True if the name is an alias, False otherwise.
|
287
|
-
|
288
|
-
Raises
|
289
|
-
------
|
290
|
-
OrionisContainerTypeError
|
291
|
-
If the name is not a string.
|
292
|
-
|
293
|
-
Examples
|
294
|
-
--------
|
295
|
-
>>> container.isAlias("DatabaseService")
|
296
|
-
True
|
297
|
-
"""
|
298
|
-
|
299
|
-
if not isinstance(name, str):
|
300
|
-
raise OrionisContainerTypeError("The name must be a valid string.")
|
301
|
-
return name in self._aliases_services
|
302
|
-
|
303
|
-
def getBindings(self) -> Dict[str, Any]:
|
304
|
-
"""
|
305
|
-
Retrieves all registered service bindings.
|
306
|
-
|
307
|
-
Returns
|
308
|
-
-------
|
309
|
-
dict
|
310
|
-
A dictionary containing all instances, transient, scoped, singleton, and alias services.
|
311
|
-
|
312
|
-
Examples
|
313
|
-
--------
|
314
|
-
>>> container.getBindings()
|
315
|
-
"""
|
316
|
-
|
317
|
-
return {
|
318
|
-
"instances": self._instances_services,
|
319
|
-
"transient": self._transient_services,
|
320
|
-
"scoped": self._scoped_services,
|
321
|
-
"singleton": self._singleton_services,
|
322
|
-
"aliases": self._aliases_services
|
323
|
-
}
|
324
|
-
|
325
|
-
def getAlias(self, name: str) -> Callable[..., Any]:
|
326
|
-
"""
|
327
|
-
Retrieves the abstract type associated with an alias.
|
328
|
-
|
329
|
-
Parameters
|
330
|
-
----------
|
331
|
-
name : str
|
332
|
-
The alias name.
|
333
|
-
|
334
|
-
Returns
|
335
|
-
-------
|
336
|
-
Callable[..., Any]
|
337
|
-
The abstract type associated with the alias.
|
338
|
-
|
339
|
-
Raises
|
340
|
-
------
|
341
|
-
OrionisContainerValueError
|
342
|
-
If the alias is not registered.
|
343
|
-
|
344
|
-
Examples
|
345
|
-
--------
|
346
|
-
>>> container.getAlias("DatabaseService")
|
347
|
-
<class 'MyDatabaseService'>
|
348
|
-
"""
|
349
|
-
|
350
|
-
if not isinstance(name, str):
|
351
|
-
raise OrionisContainerValueError("The name must be a valid string.")
|
352
|
-
|
353
|
-
if name not in self._aliases_services:
|
354
|
-
raise OrionisContainerValueError(f"Alias '{name}' is not registered in the container.")
|
355
|
-
|
356
|
-
return self._aliases_services[name]
|
357
|
-
|
358
|
-
def forgetScopedInstances(self) -> None:
|
359
|
-
"""
|
360
|
-
Clears all scoped instances.
|
361
|
-
|
362
|
-
Examples
|
363
|
-
--------
|
364
|
-
>>> container.forgetScopedInstances()
|
365
|
-
"""
|
366
|
-
|
367
|
-
self._scoped_instances = {}
|
368
|
-
|
369
|
-
def newRequest(self) -> None:
|
370
|
-
"""
|
371
|
-
Resets scoped instances to handle a new request.
|
372
|
-
|
373
|
-
Examples
|
374
|
-
--------
|
375
|
-
>>> container.newRequest()
|
376
|
-
"""
|
377
|
-
|
378
|
-
self.forgetScopedInstances()
|
379
|
-
|
380
|
-
async def make(self, abstract_or_alias: Callable[..., Any]) -> Any:
|
381
|
-
"""
|
382
|
-
Resolves and instantiates a service from the container.
|
383
|
-
|
384
|
-
Parameters
|
385
|
-
----------
|
386
|
-
abstract_or_alias : Callable[..., Any]
|
387
|
-
The abstract type or alias to resolve.
|
388
|
-
|
389
|
-
Returns
|
390
|
-
-------
|
391
|
-
Any
|
392
|
-
The instantiated service.
|
393
|
-
|
394
|
-
Raises
|
395
|
-
------
|
396
|
-
OrionisContainerException
|
397
|
-
If the service is not found.
|
398
|
-
|
399
|
-
Examples
|
400
|
-
--------
|
401
|
-
>>> service = await container.make(MyService)
|
402
|
-
"""
|
403
|
-
if abstract_or_alias in self._aliases_services:
|
404
|
-
abstract_or_alias = self._aliases_services[abstract_or_alias]
|
405
|
-
|
406
|
-
if abstract_or_alias in self._instances_services:
|
407
|
-
return self._instances_services[abstract_or_alias]
|
408
|
-
|
409
|
-
if abstract_or_alias in self._singleton_services:
|
410
|
-
if abstract_or_alias not in self._singleton_instances:
|
411
|
-
service = self._singleton_services[abstract_or_alias]
|
412
|
-
self._singleton_instances[abstract_or_alias] = await self._resolve(service['concrete'])
|
413
|
-
return self._singleton_instances[abstract_or_alias]
|
414
|
-
|
415
|
-
if abstract_or_alias in self._scoped_services:
|
416
|
-
if abstract_or_alias not in self._scoped_instances:
|
417
|
-
service = self._scoped_services[abstract_or_alias]
|
418
|
-
self._scoped_instances[abstract_or_alias] = await self._resolve(service['concrete'])
|
419
|
-
return self._scoped_instances[abstract_or_alias]
|
420
|
-
|
421
|
-
if abstract_or_alias in self._transient_services:
|
422
|
-
service = self._transient_services[abstract_or_alias]
|
423
|
-
return await self._resolve(service['concrete'])
|
424
|
-
|
425
|
-
raise OrionisContainerException(f"No binding found for '{abstract_or_alias}' in the container.")
|
426
|
-
|
427
|
-
async def _resolve(self, concrete: Callable[..., Any], resolving: Optional[Deque[Type]] = None) -> Any:
|
428
|
-
"""
|
429
|
-
Asynchronous method to resolve dependencies recursively and instantiate a class.
|
430
|
-
|
431
|
-
Parameters
|
432
|
-
----------
|
433
|
-
concrete : Callable[..., Any]
|
434
|
-
The concrete implementation to instantiate.
|
435
|
-
resolving : Optional[Deque[Type]], optional
|
436
|
-
A queue to track resolving dependencies and prevent circular dependencies.
|
437
|
-
|
438
|
-
Returns
|
439
|
-
-------
|
440
|
-
Any
|
441
|
-
The instantiated object.
|
442
|
-
|
443
|
-
Raises
|
444
|
-
------
|
445
|
-
OrionisContainerException
|
446
|
-
If circular dependencies are detected or instantiation fails.
|
447
|
-
|
448
|
-
Examples
|
449
|
-
--------
|
450
|
-
>>> instance = await container._resolve(MyClass)
|
451
|
-
"""
|
452
|
-
if resolving is None:
|
453
|
-
resolving = deque()
|
454
|
-
|
455
|
-
if concrete in resolving:
|
456
|
-
raise OrionisContainerException(f"Circular dependency detected for {concrete}.")
|
457
|
-
|
458
|
-
resolving.append(concrete)
|
459
|
-
|
460
|
-
try:
|
461
|
-
signature = inspect.signature(concrete)
|
462
|
-
except ValueError as e:
|
463
|
-
raise OrionisContainerException(f"Unable to inspect signature of {concrete}: {str(e)}")
|
464
|
-
|
465
|
-
resolved_dependencies: Dict[str, Any] = {}
|
466
|
-
unresolved_dependencies = deque()
|
467
|
-
|
468
|
-
for param_name, param in signature.parameters.items():
|
469
|
-
if param_name == 'self':
|
470
|
-
continue
|
471
|
-
|
472
|
-
if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD):
|
473
|
-
continue
|
474
|
-
|
475
|
-
if param.annotation is param.empty and param.default is param.empty:
|
476
|
-
unresolved_dependencies.append(param_name)
|
477
|
-
continue
|
478
|
-
|
479
|
-
if param.default is not param.empty:
|
480
|
-
resolved_dependencies[param_name] = param.default
|
481
|
-
continue
|
482
|
-
|
483
|
-
if param.annotation is not param.empty:
|
484
|
-
param_type = param.annotation
|
485
|
-
|
486
|
-
if get_origin(param_type) is not None:
|
487
|
-
param_type = get_args(param_type)[0]
|
488
|
-
|
489
|
-
if isinstance(param_type, type) and not issubclass(param_type, (int, str, bool, float)):
|
490
|
-
if self.has(param_type):
|
491
|
-
resolved_dependencies[param_name] = await self.make(param_type)
|
492
|
-
else:
|
493
|
-
resolved_dependencies[param_name] = await self._resolve_dependency(param_type, resolving)
|
494
|
-
else:
|
495
|
-
resolved_dependencies[param_name] = param_type
|
496
|
-
|
497
|
-
while unresolved_dependencies:
|
498
|
-
dep_name = unresolved_dependencies.popleft()
|
499
|
-
if dep_name not in resolved_dependencies:
|
500
|
-
resolved_dependencies[dep_name] = await self._resolve_dependency(dep_name, resolving)
|
501
|
-
|
502
|
-
try:
|
503
|
-
instance = concrete(**resolved_dependencies)
|
504
|
-
resolving.pop()
|
505
|
-
return instance
|
506
|
-
except Exception as e:
|
507
|
-
raise OrionisContainerException(f"Failed to instantiate {concrete}: {str(e)}")
|
508
|
-
|
509
|
-
async def _resolve_dependency(self, dep_type: Any, resolving: Optional[Deque[Type]] = None) -> Any:
|
510
|
-
"""
|
511
|
-
Asynchronously resolves a dependency by instantiating or retrieving it from the container.
|
512
|
-
|
513
|
-
Parameters
|
514
|
-
----------
|
515
|
-
dep_type : Any
|
516
|
-
The dependency type to resolve.
|
517
|
-
resolving : Optional[Deque[Type]], optional
|
518
|
-
A queue to track resolving dependencies.
|
519
|
-
|
520
|
-
Returns
|
521
|
-
-------
|
522
|
-
Any
|
523
|
-
The resolved dependency.
|
524
|
-
|
525
|
-
Raises
|
526
|
-
------
|
527
|
-
OrionisContainerException
|
528
|
-
If the dependency cannot be resolved.
|
529
|
-
|
530
|
-
Examples
|
531
|
-
--------
|
532
|
-
>>> dependency = await container._resolve_dependency(MyDependency)
|
533
|
-
"""
|
534
|
-
if resolving is None:
|
535
|
-
resolving = deque()
|
536
|
-
|
537
|
-
if isinstance(dep_type, type):
|
538
|
-
if self.has(dep_type):
|
539
|
-
return await self.make(dep_type)
|
540
|
-
else:
|
541
|
-
return await self._resolve(dep_type, resolving)
|
542
|
-
|
543
|
-
raise OrionisContainerException(f"Cannot resolve dependency of type {dep_type}")
|
@@ -1,292 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
import inspect
|
3
|
-
from abc import ABC
|
4
|
-
from typing import Any, Callable, Set, Type
|
5
|
-
from orionis._container.exception import OrionisContainerValueError, OrionisContainerTypeError
|
6
|
-
from orionis._contracts.container.container_integrity import IContainerIntegrity
|
7
|
-
|
8
|
-
class ContainerIntegrity(IContainerIntegrity):
|
9
|
-
|
10
|
-
@staticmethod
|
11
|
-
def ensureImplementation(abstract: Type, concrete: Type, raise_exception: bool = True) -> bool:
|
12
|
-
"""
|
13
|
-
Strictly verify that 'concrete' implements all abstract methods of 'abstract'.
|
14
|
-
|
15
|
-
Args:
|
16
|
-
abstract: Abstract class or interface to verify against
|
17
|
-
concrete: Concrete class that should implement the abstract class
|
18
|
-
|
19
|
-
Raises:
|
20
|
-
OrionisContainerTypeError: If concrete doesn't properly implement abstract
|
21
|
-
"""
|
22
|
-
|
23
|
-
# Check if abstract is a class
|
24
|
-
if not inspect.isclass(abstract):
|
25
|
-
if raise_exception:
|
26
|
-
raise OrionisContainerTypeError(
|
27
|
-
f"Abstract must be a class, got {type(abstract).__name__}"
|
28
|
-
)
|
29
|
-
return False
|
30
|
-
|
31
|
-
# Check if concrete is a class
|
32
|
-
if not inspect.isclass(concrete):
|
33
|
-
if raise_exception:
|
34
|
-
raise OrionisContainerTypeError(
|
35
|
-
f"Concrete must be a class, got {type(concrete).__name__}"
|
36
|
-
)
|
37
|
-
return False
|
38
|
-
|
39
|
-
# Check if concrete inherits from abstract
|
40
|
-
if not issubclass(concrete, abstract):
|
41
|
-
if raise_exception:
|
42
|
-
raise OrionisContainerTypeError(
|
43
|
-
f"{concrete.__name__} must inherit from {abstract.__name__}"
|
44
|
-
)
|
45
|
-
return False
|
46
|
-
|
47
|
-
abstract_methods: Set[str] = set()
|
48
|
-
for base in abstract.__mro__:
|
49
|
-
if hasattr(base, '__abstractmethods__'):
|
50
|
-
abstract_methods.update(base.__abstractmethods__)
|
51
|
-
|
52
|
-
if not abstract_methods:
|
53
|
-
return # No abstract methods to implement
|
54
|
-
|
55
|
-
class_methods = {
|
56
|
-
name for name, member in inspect.getmembers(concrete)
|
57
|
-
if not name.startswith("_") and inspect.isfunction(member)
|
58
|
-
}
|
59
|
-
|
60
|
-
missing_methods = abstract_methods - class_methods
|
61
|
-
if missing_methods:
|
62
|
-
raise OrionisContainerTypeError(
|
63
|
-
f"{concrete.__name__} must implement: {sorted(missing_methods)}"
|
64
|
-
)
|
65
|
-
|
66
|
-
def ensureImplementation(abstract: Type, concrete: Type) -> None:
|
67
|
-
"""
|
68
|
-
Verify at runtime if `concrete` implements all methods of `abstract`.
|
69
|
-
|
70
|
-
:param abstract: Abstract class or interface.
|
71
|
-
:param concrete: Concrete class that should implement the abstract class.
|
72
|
-
:raises TypeError: If `concrete` does not implement all methods of `abstract`.
|
73
|
-
"""
|
74
|
-
# Get public methods of the interface (excluding magic methods)
|
75
|
-
interface_methods = {
|
76
|
-
name for name, func in inspect.getmembers(abstract, predicate=inspect.isfunction)
|
77
|
-
if not name.startswith("_")
|
78
|
-
}
|
79
|
-
|
80
|
-
# Get public methods of the concrete class
|
81
|
-
class_methods = {
|
82
|
-
name for name, func in inspect.getmembers(concrete, predicate=inspect.isfunction)
|
83
|
-
if not name.startswith("_")
|
84
|
-
}
|
85
|
-
|
86
|
-
# Verify that all interface methods are in the concrete class
|
87
|
-
if not interface_methods.issubset(class_methods):
|
88
|
-
missing_methods = interface_methods - class_methods
|
89
|
-
raise OrionisContainerTypeError(f"{concrete.__name__} does not implement the required methods of {abstract.__name__}: {missing_methods}")
|
90
|
-
|
91
|
-
|
92
|
-
@staticmethod
|
93
|
-
def ensureIsAbstract(abstract: Callable[..., Any]) -> None:
|
94
|
-
"""
|
95
|
-
Ensure that the given abstract is a valid abstract class.
|
96
|
-
|
97
|
-
:param abstract: Class to check
|
98
|
-
:raises OrionisContainerValueError: If the class is not a valid abstract interface
|
99
|
-
"""
|
100
|
-
if not isinstance(abstract, type) or not issubclass(abstract, ABC) or abstract is ABC:
|
101
|
-
raise OrionisContainerValueError("The provided class must inherit from ABC and not be ABC itself.")
|
102
|
-
|
103
|
-
if not any(getattr(attr, "__isabstractmethod__", False) for attr in abstract.__dict__.values()):
|
104
|
-
raise OrionisContainerValueError("The provided class must define at least one abstract method.")
|
105
|
-
|
106
|
-
@staticmethod
|
107
|
-
def ensureIsCallable(concrete: Callable[..., Any]) -> None:
|
108
|
-
"""
|
109
|
-
Ensure that the given implementation is callable or instantiable.
|
110
|
-
|
111
|
-
Parameters
|
112
|
-
----------
|
113
|
-
concrete : Callable[..., Any]
|
114
|
-
The implementation to check.
|
115
|
-
|
116
|
-
Raises
|
117
|
-
------
|
118
|
-
OrionisContainerTypeError
|
119
|
-
If the implementation is not callable.
|
120
|
-
"""
|
121
|
-
if not callable(concrete):
|
122
|
-
raise OrionisContainerTypeError(f"The implementation '{str(concrete)}' must be callable or an instantiable class.")
|
123
|
-
|
124
|
-
@staticmethod
|
125
|
-
def ensureIsInstance(instance: Any) -> None:
|
126
|
-
"""
|
127
|
-
Ensure that the given instance is a valid object.
|
128
|
-
|
129
|
-
Parameters
|
130
|
-
----------
|
131
|
-
instance : Any
|
132
|
-
The instance to check.
|
133
|
-
|
134
|
-
Raises
|
135
|
-
------
|
136
|
-
OrionisContainerValueError
|
137
|
-
If the instance is not a valid object.
|
138
|
-
"""
|
139
|
-
if not isinstance(instance, object):
|
140
|
-
raise OrionisContainerValueError(f"The instance '{str(instance)}' must be a valid object.")
|
141
|
-
|
142
|
-
module = type(instance).__module__
|
143
|
-
if module in ['builtins', 'abc']:
|
144
|
-
raise OrionisContainerValueError(f"The instance '{str(instance)}' is not a valid user-defined object. It belongs to the '{module}' module.")
|
145
|
-
|
146
|
-
@staticmethod
|
147
|
-
def ensureNotMain(concrete: Callable[..., Any]) -> str:
|
148
|
-
"""
|
149
|
-
Ensure that a class is not defined in the main script.
|
150
|
-
|
151
|
-
Parameters
|
152
|
-
----------
|
153
|
-
concrete : Callable[..., Any]
|
154
|
-
The class or function to check.
|
155
|
-
|
156
|
-
Returns
|
157
|
-
-------
|
158
|
-
str
|
159
|
-
The fully qualified name of the class.
|
160
|
-
|
161
|
-
Raises
|
162
|
-
------
|
163
|
-
OrionisContainerValueError
|
164
|
-
If the class is defined in the main module.
|
165
|
-
"""
|
166
|
-
if concrete.__module__ == "__main__":
|
167
|
-
raise OrionisContainerValueError("Cannot register a class from the (__main__) module in the container.")
|
168
|
-
|
169
|
-
@staticmethod
|
170
|
-
def ensureIsAlias(name: str) -> bool:
|
171
|
-
"""
|
172
|
-
Ensure that the given alias name is a valid string, with no special characters or spaces,
|
173
|
-
and it is not a primitive type.
|
174
|
-
|
175
|
-
Parameters
|
176
|
-
----------
|
177
|
-
name : str
|
178
|
-
The alias name to check.
|
179
|
-
|
180
|
-
Raises
|
181
|
-
------
|
182
|
-
OrionisContainerValueError
|
183
|
-
If the alias is invalid.
|
184
|
-
"""
|
185
|
-
if not isinstance(name, str):
|
186
|
-
raise OrionisContainerValueError(f"The alias '{name}' must be a string.")
|
187
|
-
|
188
|
-
if not re.match(r'^[a-zA-Z0-9_:]+$', name):
|
189
|
-
raise OrionisContainerValueError(
|
190
|
-
f"The alias '{name}' can only contain letters, numbers, underscores, and colons, without spaces or other special characters."
|
191
|
-
)
|
192
|
-
|
193
|
-
if name in {
|
194
|
-
int, "int",
|
195
|
-
float, "float",
|
196
|
-
str, "str",
|
197
|
-
bool, "bool",
|
198
|
-
bytes, "bytes",
|
199
|
-
type(None), "None",
|
200
|
-
complex, "complex",
|
201
|
-
list, "list",
|
202
|
-
tuple, "tuple",
|
203
|
-
dict, "dict",
|
204
|
-
set, "set",
|
205
|
-
frozenset, "frozenset"
|
206
|
-
}:
|
207
|
-
raise OrionisContainerValueError(f"The alias '{name}' cannot be a primitive type.")
|
208
|
-
|
209
|
-
@staticmethod
|
210
|
-
def isAlias(name: str) -> bool:
|
211
|
-
"""
|
212
|
-
Check if the given alias name is a valid string, with no special characters or spaces,
|
213
|
-
and it is not a primitive type.
|
214
|
-
|
215
|
-
Parameters
|
216
|
-
----------
|
217
|
-
name : str
|
218
|
-
The alias name to check.
|
219
|
-
|
220
|
-
Returns
|
221
|
-
-------
|
222
|
-
bool
|
223
|
-
True if the alias is valid, False otherwise.
|
224
|
-
"""
|
225
|
-
try:
|
226
|
-
ContainerIntegrity.ensureIsAlias(name)
|
227
|
-
return True
|
228
|
-
except OrionisContainerValueError:
|
229
|
-
return False
|
230
|
-
|
231
|
-
@staticmethod
|
232
|
-
def isCallable(concrete: Callable[..., Any]) -> bool:
|
233
|
-
"""
|
234
|
-
Check if the given implementation is callable or instantiable.
|
235
|
-
|
236
|
-
Parameters
|
237
|
-
----------
|
238
|
-
concrete : Callable[..., Any]
|
239
|
-
The implementation to check.
|
240
|
-
|
241
|
-
Returns
|
242
|
-
-------
|
243
|
-
bool
|
244
|
-
True if the implementation is callable, False otherwise.
|
245
|
-
"""
|
246
|
-
try:
|
247
|
-
ContainerIntegrity.ensureIsCallable(concrete)
|
248
|
-
return True
|
249
|
-
except OrionisContainerTypeError:
|
250
|
-
return False
|
251
|
-
|
252
|
-
@staticmethod
|
253
|
-
def isInstance(instance: Any) -> bool:
|
254
|
-
"""
|
255
|
-
Check if the given instance is a valid object.
|
256
|
-
|
257
|
-
Parameters
|
258
|
-
----------
|
259
|
-
instance : Any
|
260
|
-
The instance to check.
|
261
|
-
|
262
|
-
Returns
|
263
|
-
-------
|
264
|
-
bool
|
265
|
-
True if the instance is valid, False otherwise.
|
266
|
-
"""
|
267
|
-
try:
|
268
|
-
ContainerIntegrity.ensureIsInstance(instance)
|
269
|
-
return True
|
270
|
-
except OrionisContainerValueError:
|
271
|
-
return False
|
272
|
-
|
273
|
-
@staticmethod
|
274
|
-
def isAbstract(abstract: Callable[..., Any]) -> bool:
|
275
|
-
"""
|
276
|
-
Check if the given abstract is a valid abstract class.
|
277
|
-
|
278
|
-
Parameters
|
279
|
-
----------
|
280
|
-
abstract : Callable[..., Any]
|
281
|
-
The class to check.
|
282
|
-
|
283
|
-
Returns
|
284
|
-
-------
|
285
|
-
bool
|
286
|
-
True if the class is a valid abstract interface, False otherwise.
|
287
|
-
"""
|
288
|
-
try:
|
289
|
-
ContainerIntegrity.ensureIsAbstract(abstract)
|
290
|
-
return True
|
291
|
-
except OrionisContainerValueError:
|
292
|
-
return False
|
orionis/_container/exception.py
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
class OrionisContainerException(Exception):
|
2
|
-
"""
|
3
|
-
Excepción personalizada para errores relacionados con el contenedor de inyección de dependencias Orionis.
|
4
|
-
"""
|
5
|
-
|
6
|
-
def __init__(self, message: str) -> None:
|
7
|
-
"""
|
8
|
-
Inicializa la excepción con un mensaje de error.
|
9
|
-
|
10
|
-
Args:
|
11
|
-
message (str): Mensaje descriptivo del error.
|
12
|
-
"""
|
13
|
-
super().__init__(message)
|
14
|
-
|
15
|
-
def __str__(self) -> str:
|
16
|
-
"""Retorna una representación en cadena de la excepción."""
|
17
|
-
return f"[OrionisContainerException] {self.args[0]}"
|
18
|
-
|
19
|
-
|
20
|
-
class OrionisContainerValueError(ValueError):
|
21
|
-
"""
|
22
|
-
Excepción personalizada para errores de tipo ValueError en el contenedor Orionis.
|
23
|
-
"""
|
24
|
-
|
25
|
-
def __init__(self, message: str) -> None:
|
26
|
-
"""
|
27
|
-
Inicializa la excepción con un mensaje de error.
|
28
|
-
|
29
|
-
Args:
|
30
|
-
message (str): Mensaje descriptivo del error.
|
31
|
-
"""
|
32
|
-
super().__init__(message)
|
33
|
-
|
34
|
-
def __str__(self) -> str:
|
35
|
-
"""Retorna una representación en cadena de la excepción."""
|
36
|
-
return f"[OrionisContainerValueError] {self.args[0]}"
|
37
|
-
|
38
|
-
class OrionisContainerTypeError(TypeError):
|
39
|
-
"""
|
40
|
-
Custom exception for TypeError related to the Orionis container.
|
41
|
-
"""
|
42
|
-
|
43
|
-
def __init__(self, message: str) -> None:
|
44
|
-
"""
|
45
|
-
Initializes the exception with an error message.
|
46
|
-
|
47
|
-
Args:
|
48
|
-
message (str): Descriptive error message.
|
49
|
-
"""
|
50
|
-
super().__init__(message)
|
51
|
-
|
52
|
-
def __str__(self) -> str:
|
53
|
-
"""Returns a string representation of the exception."""
|
54
|
-
return f"[OrionisContainerTypeError] {self.args[0]}"
|
orionis/_container/lifetimes.py
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
from enum import Enum
|
2
|
-
|
3
|
-
class Lifetime(Enum):
|
4
|
-
"""Defines the lifecycle types for dependency injection."""
|
5
|
-
|
6
|
-
# Creates a new instance every time it is requested
|
7
|
-
TRANSIENT = "transient"
|
8
|
-
|
9
|
-
# A single instance is shared throughout the application
|
10
|
-
SINGLETON = "singleton"
|
11
|
-
|
12
|
-
# A new instance is created per request or context
|
13
|
-
SCOPED = "scoped"
|
orionis/_container/resolve.py
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
from typing import Any, Callable
|
2
|
-
from orionis._container.container import Container
|
3
|
-
from orionis._container.exception import OrionisContainerValueError
|
4
|
-
|
5
|
-
class Resolve:
|
6
|
-
"""
|
7
|
-
A class to resolve dependencies from the dependency injection container.
|
8
|
-
|
9
|
-
This class ensures that a given abstract class or alias exists in the container
|
10
|
-
and resolves the associated service when an instance is created.
|
11
|
-
|
12
|
-
Parameters
|
13
|
-
----------
|
14
|
-
abstract_or_alias : Callable[..., Any] or str
|
15
|
-
The abstract class, alias, or callable to resolve from the container.
|
16
|
-
|
17
|
-
Returns
|
18
|
-
-------
|
19
|
-
Any
|
20
|
-
The service associated with the abstract class or alias.
|
21
|
-
|
22
|
-
Raises
|
23
|
-
------
|
24
|
-
OrionisContainerValueError
|
25
|
-
If the abstract class or alias is not found in the container.
|
26
|
-
|
27
|
-
Examples
|
28
|
-
--------
|
29
|
-
>>> container = Container()
|
30
|
-
>>> container.bind("my_service", MyService)
|
31
|
-
>>> container.alias("my_alias", "my_service")
|
32
|
-
>>> service = Resolve("my_alias") # Returns the service associated with "my_alias"
|
33
|
-
>>> service = Resolve(MyService) # Returns the service associated with MyService
|
34
|
-
"""
|
35
|
-
|
36
|
-
def __new__(cls, abstract_or_alias: Callable[..., Any] | str):
|
37
|
-
"""
|
38
|
-
Create an instance of Resolve and return the resolved service.
|
39
|
-
|
40
|
-
Parameters
|
41
|
-
----------
|
42
|
-
abstract_or_alias : Callable[..., Any] or str
|
43
|
-
The abstract class, alias, or callable to resolve from the container.
|
44
|
-
|
45
|
-
Returns
|
46
|
-
-------
|
47
|
-
Any
|
48
|
-
The service associated with the abstract class or alias.
|
49
|
-
|
50
|
-
Raises
|
51
|
-
------
|
52
|
-
OrionisContainerValueError
|
53
|
-
If the abstract class or alias is not found in the container.
|
54
|
-
"""
|
55
|
-
|
56
|
-
# Validate that the abstract or alias exists in the container
|
57
|
-
container = Container()
|
58
|
-
if not container.bound(abstract_or_alias):
|
59
|
-
raise OrionisContainerValueError(
|
60
|
-
f"Service or alias '{abstract_or_alias}' not found in the container."
|
61
|
-
)
|
62
|
-
|
63
|
-
# Resolve and return the service associated with the abstract or alias
|
64
|
-
# return AsyncExecutor.run(container.make(abstract_or_alias))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|