orionis 0.320.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 +31 -377
- orionis/container/context/manager.py +94 -0
- orionis/container/context/scope.py +40 -0
- orionis/container/contracts/container.py +62 -16
- orionis/container/resolver.py +432 -7
- orionis/container/validators/lifetime.py +53 -0
- orionis/metadata/framework.py +1 -1
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/METADATA +1 -1
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/RECORD +14 -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.320.0.dist-info → orionis-0.322.0.dist-info}/WHEEL +0 -0
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/top_level.txt +0 -0
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/zip-safe +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from typing import Any, Callable
|
3
3
|
from orionis.container.enums.lifetimes import Lifetime
|
4
|
+
from orionis.container.entities.binding import Binding
|
4
5
|
|
5
6
|
class IContainer(ABC):
|
6
7
|
"""
|
@@ -18,7 +19,7 @@ class IContainer(ABC):
|
|
18
19
|
enforce_decoupling: bool = False
|
19
20
|
) -> bool:
|
20
21
|
"""
|
21
|
-
|
22
|
+
Registers a service with a singleton lifetime.
|
22
23
|
|
23
24
|
Parameters
|
24
25
|
----------
|
@@ -27,7 +28,7 @@ class IContainer(ABC):
|
|
27
28
|
concrete : Callable[..., Any]
|
28
29
|
The concrete implementation to associate with the abstract type.
|
29
30
|
alias : str, optional
|
30
|
-
An alternative name to register the service under.
|
31
|
+
An alternative name to register the service under. If not provided, the abstract's class name is used.
|
31
32
|
enforce_decoupling : bool, optional
|
32
33
|
Whether to enforce that concrete is not a subclass of abstract.
|
33
34
|
|
@@ -48,7 +49,7 @@ class IContainer(ABC):
|
|
48
49
|
enforce_decoupling: bool = False
|
49
50
|
) -> bool:
|
50
51
|
"""
|
51
|
-
|
52
|
+
Registers a service with a transient lifetime.
|
52
53
|
|
53
54
|
Parameters
|
54
55
|
----------
|
@@ -57,7 +58,7 @@ class IContainer(ABC):
|
|
57
58
|
concrete : Callable[..., Any]
|
58
59
|
The concrete implementation to associate with the abstract type.
|
59
60
|
alias : str, optional
|
60
|
-
An alternative name to register the service under.
|
61
|
+
An alternative name to register the service under. If not provided, the abstract's class name is used.
|
61
62
|
enforce_decoupling : bool, optional
|
62
63
|
Whether to enforce that concrete is not a subclass of abstract.
|
63
64
|
|
@@ -78,7 +79,7 @@ class IContainer(ABC):
|
|
78
79
|
enforce_decoupling: bool = False
|
79
80
|
) -> bool:
|
80
81
|
"""
|
81
|
-
|
82
|
+
Registers a service with a scoped lifetime.
|
82
83
|
|
83
84
|
Parameters
|
84
85
|
----------
|
@@ -87,7 +88,7 @@ class IContainer(ABC):
|
|
87
88
|
concrete : Callable[..., Any]
|
88
89
|
The concrete implementation to associate with the abstract type.
|
89
90
|
alias : str, optional
|
90
|
-
An alternative name to register the service under.
|
91
|
+
An alternative name to register the service under. If not provided, the abstract's class name is used.
|
91
92
|
enforce_decoupling : bool, optional
|
92
93
|
Whether to enforce that concrete is not a subclass of abstract.
|
93
94
|
|
@@ -108,7 +109,7 @@ class IContainer(ABC):
|
|
108
109
|
enforce_decoupling: bool = False
|
109
110
|
) -> bool:
|
110
111
|
"""
|
111
|
-
|
112
|
+
Registers an instance of a class or interface in the container.
|
112
113
|
|
113
114
|
Parameters
|
114
115
|
----------
|
@@ -117,7 +118,8 @@ class IContainer(ABC):
|
|
117
118
|
instance : Any
|
118
119
|
The concrete instance to register.
|
119
120
|
alias : str, optional
|
120
|
-
An optional alias to register the instance under.
|
121
|
+
An optional alias to register the instance under. If not provided,
|
122
|
+
the abstract's `__name__` attribute will be used as the alias if available.
|
121
123
|
enforce_decoupling : bool, optional
|
122
124
|
Whether to enforce that instance's class is not a subclass of abstract.
|
123
125
|
|
@@ -129,7 +131,7 @@ class IContainer(ABC):
|
|
129
131
|
pass
|
130
132
|
|
131
133
|
@abstractmethod
|
132
|
-
def
|
134
|
+
def callable(
|
133
135
|
self,
|
134
136
|
alias: str,
|
135
137
|
fn: Callable[..., Any],
|
@@ -137,7 +139,7 @@ class IContainer(ABC):
|
|
137
139
|
lifetime: Lifetime = Lifetime.TRANSIENT
|
138
140
|
) -> bool:
|
139
141
|
"""
|
140
|
-
|
142
|
+
Registers a function or factory under a given alias.
|
141
143
|
|
142
144
|
Parameters
|
143
145
|
----------
|
@@ -163,16 +165,16 @@ class IContainer(ABC):
|
|
163
165
|
**kwargs: dict
|
164
166
|
) -> Any:
|
165
167
|
"""
|
166
|
-
|
168
|
+
Resolves and returns an instance of the requested service.
|
167
169
|
|
168
170
|
Parameters
|
169
171
|
----------
|
170
172
|
abstract_or_alias : Any
|
171
173
|
The abstract class, interface, or alias (str) to resolve.
|
172
174
|
*args : tuple
|
173
|
-
Positional arguments to pass to the constructor.
|
175
|
+
Positional arguments to pass to the constructor of the resolved service.
|
174
176
|
**kwargs : dict
|
175
|
-
Keyword arguments to pass to the constructor.
|
177
|
+
Keyword arguments to pass to the constructor of the resolved service.
|
176
178
|
|
177
179
|
Returns
|
178
180
|
-------
|
@@ -187,16 +189,60 @@ class IContainer(ABC):
|
|
187
189
|
abstract_or_alias: Any
|
188
190
|
) -> bool:
|
189
191
|
"""
|
190
|
-
|
192
|
+
Checks if a service (by abstract type or alias) is registered in the container.
|
191
193
|
|
192
194
|
Parameters
|
193
195
|
----------
|
194
196
|
abstract_or_alias : Any
|
195
|
-
The abstract class, interface, or alias (str) to check.
|
197
|
+
The abstract class, interface, or alias (str) to check for registration.
|
196
198
|
|
197
199
|
Returns
|
198
200
|
-------
|
199
201
|
bool
|
200
|
-
True if the service is registered, False otherwise.
|
202
|
+
True if the service is registered (either as an abstract type or alias), False otherwise.
|
203
|
+
"""
|
204
|
+
pass
|
205
|
+
|
206
|
+
@abstractmethod
|
207
|
+
def getBinding(
|
208
|
+
self,
|
209
|
+
abstract_or_alias: Any
|
210
|
+
) -> Binding:
|
211
|
+
"""
|
212
|
+
Retrieves the binding for the requested abstract type or alias.
|
213
|
+
|
214
|
+
Parameters
|
215
|
+
----------
|
216
|
+
abstract_or_alias : Any
|
217
|
+
The abstract class, interface, or alias (str) to retrieve.
|
218
|
+
|
219
|
+
Returns
|
220
|
+
-------
|
221
|
+
Binding
|
222
|
+
The binding associated with the requested abstract type or alias.
|
223
|
+
"""
|
224
|
+
pass
|
225
|
+
|
226
|
+
@abstractmethod
|
227
|
+
def drop(
|
228
|
+
self,
|
229
|
+
abstract: Callable[..., Any] = None,
|
230
|
+
alias: str = None
|
231
|
+
) -> None:
|
232
|
+
"""
|
233
|
+
Drops a service from the container by removing its bindings and aliases.
|
234
|
+
|
235
|
+
Warning
|
236
|
+
-------
|
237
|
+
Using this method irresponsibly can severely damage the system's logic.
|
238
|
+
Only use it when you are certain about the consequences, as removing
|
239
|
+
critical services may lead to system failures and unexpected behavior.
|
240
|
+
|
241
|
+
Parameters
|
242
|
+
----------
|
243
|
+
abstract : Callable[..., Any], optional
|
244
|
+
The abstract type or interface to be removed from the container.
|
245
|
+
alias : str, optional
|
246
|
+
The alias of the service to be removed.
|
201
247
|
"""
|
202
248
|
pass
|
orionis/container/resolver.py
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
from typing import Any, Callable
|
2
|
+
from orionis.container.context.scope import ScopedContext
|
1
3
|
from orionis.container.contracts.container import IContainer
|
2
4
|
from orionis.container.entities.binding import Binding
|
3
5
|
from orionis.container.enums.lifetimes import Lifetime
|
4
|
-
|
6
|
+
from orionis.container.exceptions.container_exception import OrionisContainerException
|
7
|
+
from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
|
8
|
+
from orionis.services.introspection.concretes.reflection_concrete import ReflectionConcrete
|
9
|
+
from orionis.services.introspection.dependencies.entities.resolved_dependencies import ResolvedDependency
|
5
10
|
|
6
11
|
class Resolver:
|
7
12
|
"""
|
@@ -10,19 +15,439 @@ class Resolver:
|
|
10
15
|
|
11
16
|
def __init__(
|
12
17
|
self,
|
13
|
-
container:IContainer
|
14
|
-
lifetime:Lifetime
|
18
|
+
container:IContainer
|
15
19
|
):
|
20
|
+
"""
|
21
|
+
Initialize the resolver.
|
22
|
+
|
23
|
+
This method initializes the resolver with a reference to the container.
|
24
|
+
|
25
|
+
Parameters
|
26
|
+
----------
|
27
|
+
container : IContainer
|
28
|
+
The container instance that this resolver will use to resolve dependencies.
|
29
|
+
"""
|
16
30
|
self.container = container
|
17
|
-
self.lifetime = lifetime
|
18
31
|
|
19
|
-
def
|
32
|
+
def resolve(
|
20
33
|
self,
|
21
34
|
binding:Binding,
|
22
35
|
*args,
|
23
36
|
**kwargs
|
24
37
|
):
|
25
38
|
"""
|
26
|
-
|
39
|
+
Resolves an instance from a binding.
|
40
|
+
This method resolves an instance based on the binding's lifetime and type.
|
41
|
+
It delegates to specific resolution methods based on the lifetime (transient, singleton, or scoped).
|
42
|
+
Args:
|
43
|
+
binding (Binding): The binding to resolve.
|
44
|
+
*args: Additional positional arguments to pass to the constructor.
|
45
|
+
**kwargs: Additional keyword arguments to pass to the constructor.
|
46
|
+
Returns:
|
47
|
+
Any: The resolved instance.
|
48
|
+
Raises:
|
49
|
+
OrionisContainerException: If the binding is not an instance of Binding
|
50
|
+
or if scoped lifetime resolution is attempted (not yet implemented).
|
51
|
+
"""
|
52
|
+
|
53
|
+
# Ensure the binding is an instance of Binding
|
54
|
+
if not isinstance(binding, Binding):
|
55
|
+
raise OrionisContainerException(
|
56
|
+
"The binding must be an instance of Binding."
|
57
|
+
)
|
58
|
+
|
59
|
+
# Handle based on binding type and lifetime
|
60
|
+
if binding.lifetime == Lifetime.TRANSIENT:
|
61
|
+
return self.__resolveTransient(binding, *args, **kwargs)
|
62
|
+
elif binding.lifetime == Lifetime.SINGLETON:
|
63
|
+
return self.__resolveSingleton(binding, *args, **kwargs)
|
64
|
+
elif binding.lifetime == Lifetime.SCOPED:
|
65
|
+
return self.__resolveScoped(binding, *args, **kwargs)
|
66
|
+
|
67
|
+
def __resolveTransient(self, binding: Binding, *args, **kwargs) -> Any:
|
68
|
+
"""
|
69
|
+
Resolves a service with transient lifetime.
|
70
|
+
|
71
|
+
Parameters
|
72
|
+
----------
|
73
|
+
binding : Binding
|
74
|
+
The binding to resolve.
|
75
|
+
*args : tuple
|
76
|
+
Positional arguments to pass to the constructor.
|
77
|
+
**kwargs : dict
|
78
|
+
Keyword arguments to pass to the constructor.
|
79
|
+
|
80
|
+
Returns
|
81
|
+
-------
|
82
|
+
Any
|
83
|
+
A new instance of the requested service.
|
84
|
+
"""
|
85
|
+
|
86
|
+
# Check if the binding has a concrete class or function defined
|
87
|
+
if binding.concrete:
|
88
|
+
if args or kwargs:
|
89
|
+
return self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
90
|
+
else:
|
91
|
+
return self.__instantiateConcreteReflective(binding.concrete)
|
92
|
+
|
93
|
+
# If the binding has a function defined
|
94
|
+
elif binding.function:
|
95
|
+
if args or kwargs:
|
96
|
+
return self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
97
|
+
else:
|
98
|
+
return self.__instantiateCallableReflective(binding.function)
|
99
|
+
|
100
|
+
# If neither concrete class nor function is defined
|
101
|
+
else:
|
102
|
+
raise OrionisContainerException(
|
103
|
+
"Cannot resolve transient binding: neither a concrete class nor a function is defined."
|
104
|
+
)
|
105
|
+
|
106
|
+
def __resolveSingleton(self, binding: Binding, *args, **kwargs) -> Any:
|
107
|
+
"""
|
108
|
+
Resolves a service with singleton lifetime.
|
109
|
+
|
110
|
+
Parameters
|
111
|
+
----------
|
112
|
+
binding : Binding
|
113
|
+
The binding to resolve.
|
114
|
+
*args : tuple
|
115
|
+
Positional arguments to pass to the constructor (only used if instance doesn't exist yet).
|
116
|
+
**kwargs : dict
|
117
|
+
Keyword arguments to pass to the constructor (only used if instance doesn't exist yet).
|
118
|
+
|
119
|
+
Returns
|
120
|
+
-------
|
121
|
+
Any
|
122
|
+
The singleton instance of the requested service.
|
123
|
+
"""
|
124
|
+
# Return existing instance if available
|
125
|
+
if binding.instance:
|
126
|
+
return binding.instance
|
127
|
+
|
128
|
+
# Create instance if needed
|
129
|
+
if binding.concrete:
|
130
|
+
if args or kwargs:
|
131
|
+
binding.instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
132
|
+
else:
|
133
|
+
binding.instance = self.__instantiateConcreteReflective(binding.concrete)
|
134
|
+
return binding.instance
|
135
|
+
|
136
|
+
# If the binding has a function defined
|
137
|
+
elif binding.function:
|
138
|
+
if args or kwargs:
|
139
|
+
result = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
140
|
+
else:
|
141
|
+
result = self.__instantiateCallableReflective(binding.function)
|
142
|
+
|
143
|
+
# Store the result directly as the singleton instance
|
144
|
+
# We don't automatically invoke factory function results anymore
|
145
|
+
binding.instance = result
|
146
|
+
return binding.instance
|
147
|
+
|
148
|
+
# If neither concrete class nor function is defined
|
149
|
+
else:
|
150
|
+
raise OrionisContainerException(
|
151
|
+
"Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
|
152
|
+
)
|
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
|
+
|
206
|
+
def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
|
207
|
+
"""
|
208
|
+
Instantiates a concrete class with the provided arguments.
|
209
|
+
|
210
|
+
Parameters
|
211
|
+
----------
|
212
|
+
concrete : Callable[..., Any]
|
213
|
+
Class to instantiate.
|
214
|
+
*args : tuple
|
215
|
+
Positional arguments to pass to the constructor.
|
216
|
+
**kwargs : dict
|
217
|
+
Keyword arguments to pass to the constructor.
|
218
|
+
|
219
|
+
Returns
|
220
|
+
-------
|
221
|
+
object
|
222
|
+
A new instance of the specified concrete class.
|
223
|
+
"""
|
224
|
+
|
225
|
+
# try to instantiate the concrete class with the provided arguments
|
226
|
+
try:
|
227
|
+
|
228
|
+
# If the concrete is a class, instantiate it directly
|
229
|
+
return concrete(*args, **kwargs)
|
230
|
+
|
231
|
+
except TypeError as e:
|
232
|
+
|
233
|
+
# If instantiation fails, use ReflectionConcrete to get class name and constructor signature
|
234
|
+
rf_concrete = ReflectionConcrete(concrete)
|
235
|
+
class_name = rf_concrete.getClassName()
|
236
|
+
signature = rf_concrete.getConstructorSignature()
|
237
|
+
|
238
|
+
# Raise an exception with detailed information about the failure
|
239
|
+
raise OrionisContainerException(
|
240
|
+
f"Failed to instantiate [{class_name}] with the provided arguments: {e}\n"
|
241
|
+
f"Expected constructor signature: [{signature}]"
|
242
|
+
) from e
|
243
|
+
|
244
|
+
def __instantiateCallableWithArgs(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
|
245
|
+
"""
|
246
|
+
Invokes a callable with the provided arguments.
|
247
|
+
|
248
|
+
Parameters
|
249
|
+
----------
|
250
|
+
fn : Callable[..., Any]
|
251
|
+
The callable to invoke.
|
252
|
+
*args : tuple
|
253
|
+
Positional arguments to pass to the callable.
|
254
|
+
**kwargs : dict
|
255
|
+
Keyword arguments to pass to the callable.
|
256
|
+
|
257
|
+
Returns
|
258
|
+
-------
|
259
|
+
Any
|
260
|
+
The result of the callable.
|
261
|
+
"""
|
262
|
+
|
263
|
+
# Try to invoke the callable with the provided arguments
|
264
|
+
try:
|
265
|
+
|
266
|
+
# If the callable is a function, invoke it directly
|
267
|
+
return fn(*args, **kwargs)
|
268
|
+
|
269
|
+
except TypeError as e:
|
270
|
+
|
271
|
+
# If invocation fails, use ReflectionCallable to get function name and signature
|
272
|
+
rf_callable = ReflectionCallable(fn)
|
273
|
+
function_name = rf_callable.getName()
|
274
|
+
signature = rf_callable.getSignature()
|
275
|
+
|
276
|
+
# Raise an exception with detailed information about the failure
|
277
|
+
raise OrionisContainerException(
|
278
|
+
f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
|
279
|
+
f"Expected function signature: [{signature}]"
|
280
|
+
) from e
|
281
|
+
|
282
|
+
def __instantiateConcreteReflective(self, concrete: Callable[..., Any]) -> Any:
|
283
|
+
"""
|
284
|
+
Instantiates a concrete class reflectively, resolving its dependencies from the container.
|
285
|
+
|
286
|
+
Parameters
|
287
|
+
----------
|
288
|
+
concrete : Callable[..., Any]
|
289
|
+
The concrete class to instantiate.
|
290
|
+
|
291
|
+
Returns
|
292
|
+
-------
|
293
|
+
Any
|
294
|
+
A new instance of the concrete class.
|
295
|
+
"""
|
296
|
+
# Resolve dependencies for the concrete class
|
297
|
+
params = self.__resolveDependencies(concrete, is_class=True)
|
298
|
+
|
299
|
+
# Instantiate the concrete class with resolved dependencies
|
300
|
+
return concrete(**params)
|
301
|
+
|
302
|
+
def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
|
303
|
+
"""
|
304
|
+
Invokes a callable reflectively, resolving its dependencies from the container.
|
305
|
+
|
306
|
+
Parameters
|
307
|
+
----------
|
308
|
+
fn : Callable[..., Any]
|
309
|
+
The callable to invoke.
|
310
|
+
|
311
|
+
Returns
|
312
|
+
-------
|
313
|
+
Any
|
314
|
+
The result of the callable.
|
315
|
+
"""
|
316
|
+
|
317
|
+
# Resolve dependencies for the callable
|
318
|
+
params = self.__resolveDependencies(fn, is_class=False)
|
319
|
+
|
320
|
+
# Invoke the callable with resolved dependencies
|
321
|
+
return fn(**params)
|
322
|
+
|
323
|
+
def __resolveDependencies(
|
324
|
+
self,
|
325
|
+
target: Callable[..., Any],
|
326
|
+
*,
|
327
|
+
is_class: bool = False
|
328
|
+
) -> dict:
|
27
329
|
"""
|
28
|
-
|
330
|
+
Resolves dependencies for a target callable or class.
|
331
|
+
|
332
|
+
Parameters
|
333
|
+
----------
|
334
|
+
target : Callable[..., Any]
|
335
|
+
The target callable or class whose dependencies to resolve.
|
336
|
+
is_class : bool, optional
|
337
|
+
Whether the target is a class (True) or a callable (False).
|
338
|
+
|
339
|
+
Returns
|
340
|
+
-------
|
341
|
+
dict
|
342
|
+
A dictionary of resolved dependencies.
|
343
|
+
"""
|
344
|
+
try:
|
345
|
+
|
346
|
+
# Use ReflectionConcrete for classes and ReflectionCallable for callables
|
347
|
+
if is_class:
|
348
|
+
reflection = ReflectionConcrete(target)
|
349
|
+
dependencies = reflection.getConstructorDependencies()
|
350
|
+
name = reflection.getClassName()
|
351
|
+
|
352
|
+
# If the target is a callable, use ReflectionCallable
|
353
|
+
else:
|
354
|
+
reflection = ReflectionCallable(target)
|
355
|
+
dependencies = reflection.getDependencies()
|
356
|
+
name = reflection.getName()
|
357
|
+
|
358
|
+
# Check for unresolved dependencies
|
359
|
+
if dependencies.unresolved:
|
360
|
+
unresolved_args = ', '.join(dependencies.unresolved)
|
361
|
+
raise OrionisContainerException(
|
362
|
+
f"Cannot resolve '{name}' because the following required arguments are missing: [{unresolved_args}]."
|
363
|
+
)
|
364
|
+
|
365
|
+
# Resolve dependencies
|
366
|
+
params = {}
|
367
|
+
for param_name, dep in dependencies.resolved.items():
|
368
|
+
|
369
|
+
# If the dependency is a ResolvedDependency, resolve it
|
370
|
+
if isinstance(dep, ResolvedDependency):
|
371
|
+
|
372
|
+
# If the dependency is a built-in type, raise an exception
|
373
|
+
if dep.module_name == 'builtins':
|
374
|
+
raise OrionisContainerException(
|
375
|
+
f"Cannot resolve '{name}' because parameter '{param_name}' depends on built-in type '{dep.type.__name__}'."
|
376
|
+
)
|
377
|
+
|
378
|
+
# Try to resolve from container using type (Abstract or Interface)
|
379
|
+
if self.container.bound(dep.type):
|
380
|
+
params[param_name] = self.resolve(
|
381
|
+
self.container.getBinding(dep.type)
|
382
|
+
)
|
383
|
+
|
384
|
+
# Try to resolve from container using full class path
|
385
|
+
elif self.container.bound(dep.full_class_path):
|
386
|
+
params[param_name] = self.resolve(
|
387
|
+
self.container.getBinding(dep.full_class_path)
|
388
|
+
)
|
389
|
+
|
390
|
+
# Try to instantiate directly if it's a concrete class
|
391
|
+
elif ReflectionConcrete.isConcreteClass(dep.type):
|
392
|
+
params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=True))
|
393
|
+
|
394
|
+
# Try to call directly if it's a callable
|
395
|
+
elif callable(dep.type) and not isinstance(dep.type, type):
|
396
|
+
params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=False))
|
397
|
+
|
398
|
+
# If the dependency cannot be resolved, raise an exception
|
399
|
+
else:
|
400
|
+
raise OrionisContainerException(
|
401
|
+
f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}' for '{name}'."
|
402
|
+
)
|
403
|
+
else:
|
404
|
+
# Use default value
|
405
|
+
params[param_name] = dep
|
406
|
+
|
407
|
+
# Return the resolved parameters
|
408
|
+
return params
|
409
|
+
|
410
|
+
except ImportError as e:
|
411
|
+
|
412
|
+
# Get target name safely
|
413
|
+
target_name = getattr(target, '__name__', str(target))
|
414
|
+
module_name = getattr(target, '__module__', "unknown module")
|
415
|
+
|
416
|
+
# Improved circular import detection with more helpful guidance
|
417
|
+
if "circular import" in str(e).lower() or "cannot import name" in str(e).lower():
|
418
|
+
raise OrionisContainerException(
|
419
|
+
f"Circular import detected while resolving dependencies for '{target_name}' in module '{module_name}'.\n"
|
420
|
+
f"This typically happens when two modules import each other. Consider:\n"
|
421
|
+
f"1. Restructuring your code to avoid circular dependencies\n"
|
422
|
+
f"2. Using delayed imports inside methods rather than at module level\n"
|
423
|
+
f"3. Using dependency injection to break the cycle\n"
|
424
|
+
f"Original error: {str(e)}"
|
425
|
+
) from e
|
426
|
+
else:
|
427
|
+
raise OrionisContainerException(
|
428
|
+
f"Import error while resolving dependencies for '{target_name}' in module '{module_name}':\n"
|
429
|
+
f"{str(e)}"
|
430
|
+
) from e
|
431
|
+
|
432
|
+
except Exception as e:
|
433
|
+
|
434
|
+
# More robust attribute extraction with fallbacks
|
435
|
+
target_type = "class" if isinstance(target, type) else "function"
|
436
|
+
target_name = getattr(target, '__name__', str(target))
|
437
|
+
module_name = getattr(target, '__module__', "unknown module")
|
438
|
+
|
439
|
+
# More detailed error message with context about the failure
|
440
|
+
error_msg = (
|
441
|
+
f"Error resolving dependencies for {target_type} '{target_name}' in '{module_name}':\n"
|
442
|
+
f"{str(e)}\n"
|
443
|
+
)
|
444
|
+
|
445
|
+
# Add specific guidance based on error type
|
446
|
+
if isinstance(e, TypeError):
|
447
|
+
error_msg += "This may be caused by incompatible argument types or missing required parameters."
|
448
|
+
elif isinstance(e, AttributeError):
|
449
|
+
error_msg += "This may be caused by accessing undefined attributes in the dependency chain."
|
450
|
+
error_msg += "\nCheck that all dependencies are properly registered in the container."
|
451
|
+
|
452
|
+
# Raise a more informative exception
|
453
|
+
raise OrionisContainerException(error_msg) from e
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from typing import Any, Union
|
2
|
+
from orionis.container.enums.lifetimes import Lifetime
|
3
|
+
from orionis.container.exceptions.type_error_exception import OrionisContainerTypeError
|
4
|
+
|
5
|
+
class __LifetimeValidator:
|
6
|
+
"""
|
7
|
+
Validator that checks if a value is a valid lifetime and converts string representations
|
8
|
+
to Lifetime enum values.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __call__(self, lifetime: Union[str, Lifetime, Any]) -> Lifetime:
|
12
|
+
"""
|
13
|
+
Validates and normalizes the provided lifetime value.
|
14
|
+
|
15
|
+
Parameters
|
16
|
+
----------
|
17
|
+
lifetime : Union[str, Lifetime, Any]
|
18
|
+
The lifetime value to validate. Can be a Lifetime enum or a string
|
19
|
+
representing a valid lifetime.
|
20
|
+
|
21
|
+
Returns
|
22
|
+
-------
|
23
|
+
Lifetime
|
24
|
+
The validated Lifetime enum value.
|
25
|
+
|
26
|
+
Raises
|
27
|
+
------
|
28
|
+
OrionisContainerTypeError
|
29
|
+
If the value is not a valid Lifetime enum or string representation,
|
30
|
+
or if the string doesn't match any valid Lifetime value.
|
31
|
+
"""
|
32
|
+
# Already a Lifetime enum
|
33
|
+
if isinstance(lifetime, Lifetime):
|
34
|
+
return lifetime
|
35
|
+
|
36
|
+
# String that might represent a Lifetime
|
37
|
+
if isinstance(lifetime, str):
|
38
|
+
lifetime_key = lifetime.strip().upper()
|
39
|
+
if lifetime_key in Lifetime.__members__:
|
40
|
+
return Lifetime[lifetime_key]
|
41
|
+
|
42
|
+
valid_options = ', '.join(Lifetime.__members__.keys())
|
43
|
+
raise OrionisContainerTypeError(
|
44
|
+
f"Invalid lifetime '{lifetime}'. Valid options are: {valid_options}."
|
45
|
+
)
|
46
|
+
|
47
|
+
# Invalid type
|
48
|
+
raise OrionisContainerTypeError(
|
49
|
+
f"Lifetime must be of type str or Lifetime enum, got {type(lifetime).__name__}."
|
50
|
+
)
|
51
|
+
|
52
|
+
# Exported singleton instance
|
53
|
+
LifetimeValidator = __LifetimeValidator()
|