orionis 0.449.0__py3-none-any.whl → 0.451.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/console/base/contracts/command.py +0 -2
- orionis/console/core/__init__.py +0 -0
- orionis/container/container.py +1620 -88
- orionis/container/context/scope.py +1 -1
- orionis/container/contracts/container.py +184 -33
- orionis/foundation/config/database/entities/mysql.py +0 -1
- orionis/metadata/framework.py +1 -1
- orionis/services/inspirational/contracts/__init__.py +0 -0
- orionis/services/introspection/abstract/contracts/reflection.py +5 -6
- orionis/services/introspection/abstract/reflection.py +5 -6
- orionis/services/introspection/callables/contracts/reflection.py +3 -2
- orionis/services/introspection/callables/reflection.py +4 -4
- orionis/services/introspection/concretes/contracts/reflection.py +5 -6
- orionis/services/introspection/concretes/reflection.py +5 -6
- orionis/services/introspection/dependencies/contracts/reflection.py +87 -23
- orionis/services/introspection/dependencies/entities/argument.py +95 -0
- orionis/services/introspection/dependencies/entities/resolve_argument.py +82 -0
- orionis/services/introspection/dependencies/reflection.py +176 -106
- orionis/services/introspection/instances/contracts/reflection.py +5 -6
- orionis/services/introspection/instances/reflection.py +5 -6
- orionis/test/core/unit_test.py +150 -48
- {orionis-0.449.0.dist-info → orionis-0.451.0.dist-info}/METADATA +1 -1
- {orionis-0.449.0.dist-info → orionis-0.451.0.dist-info}/RECORD +35 -38
- tests/container/mocks/mock_auto_resolution.py +192 -0
- tests/services/introspection/dependencies/test_reflect_dependencies.py +135 -58
- tests/services/introspection/reflection/test_reflection_abstract.py +5 -4
- tests/services/introspection/reflection/test_reflection_callable.py +3 -3
- tests/services/introspection/reflection/test_reflection_concrete.py +4 -4
- tests/services/introspection/reflection/test_reflection_instance.py +5 -5
- orionis/console/args/parser.py +0 -40
- orionis/container/contracts/resolver.py +0 -115
- orionis/container/resolver/resolver.py +0 -602
- orionis/services/introspection/dependencies/entities/callable_dependencies.py +0 -54
- orionis/services/introspection/dependencies/entities/class_dependencies.py +0 -61
- orionis/services/introspection/dependencies/entities/known_dependencies.py +0 -67
- orionis/services/introspection/dependencies/entities/method_dependencies.py +0 -61
- tests/container/resolver/test_resolver.py +0 -62
- /orionis/{container/resolver → console/commands}/__init__.py +0 -0
- {tests/container/resolver → orionis/console/contracts}/__init__.py +0 -0
- {orionis-0.449.0.dist-info → orionis-0.451.0.dist-info}/WHEEL +0 -0
- {orionis-0.449.0.dist-info → orionis-0.451.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.449.0.dist-info → orionis-0.451.0.dist-info}/top_level.txt +0 -0
- {orionis-0.449.0.dist-info → orionis-0.451.0.dist-info}/zip-safe +0 -0
|
@@ -1,602 +0,0 @@
|
|
|
1
|
-
from typing import Any, Callable
|
|
2
|
-
from orionis.container.context.scope import ScopedContext
|
|
3
|
-
from orionis.container.contracts.container import IContainer
|
|
4
|
-
from orionis.container.contracts.resolver import IResolver
|
|
5
|
-
from orionis.container.entities.binding import Binding
|
|
6
|
-
from orionis.container.enums.lifetimes import Lifetime
|
|
7
|
-
from orionis.container.exceptions import OrionisContainerException
|
|
8
|
-
from orionis.services.introspection.callables.reflection import ReflectionCallable
|
|
9
|
-
from orionis.services.introspection.concretes.reflection import ReflectionConcrete
|
|
10
|
-
from orionis.services.introspection.dependencies.entities.known_dependencies import KnownDependency
|
|
11
|
-
from orionis.services.introspection.dependencies.entities.method_dependencies import MethodDependency
|
|
12
|
-
|
|
13
|
-
class Resolver(IResolver):
|
|
14
|
-
|
|
15
|
-
def __init__(
|
|
16
|
-
self,
|
|
17
|
-
container: IContainer
|
|
18
|
-
):
|
|
19
|
-
"""
|
|
20
|
-
Parameters
|
|
21
|
-
----------
|
|
22
|
-
container : IContainer
|
|
23
|
-
The container instance used by the resolver to resolve dependencies.
|
|
24
|
-
|
|
25
|
-
Notes
|
|
26
|
-
-----
|
|
27
|
-
Initializes the Resolver with a reference to the dependency injection container.
|
|
28
|
-
This allows the resolver to access registered bindings and resolve dependencies
|
|
29
|
-
as required throughout the container's lifecycle.
|
|
30
|
-
"""
|
|
31
|
-
self.container = container
|
|
32
|
-
|
|
33
|
-
def resolve(
|
|
34
|
-
self,
|
|
35
|
-
binding: Binding,
|
|
36
|
-
*args,
|
|
37
|
-
**kwargs
|
|
38
|
-
):
|
|
39
|
-
"""
|
|
40
|
-
Resolves an instance from a binding according to its lifetime.
|
|
41
|
-
|
|
42
|
-
Parameters
|
|
43
|
-
----------
|
|
44
|
-
binding : Binding
|
|
45
|
-
The binding to resolve.
|
|
46
|
-
*args : tuple
|
|
47
|
-
Additional positional arguments to pass to the constructor.
|
|
48
|
-
**kwargs : dict
|
|
49
|
-
Additional keyword arguments to pass to the constructor.
|
|
50
|
-
|
|
51
|
-
Returns
|
|
52
|
-
-------
|
|
53
|
-
Any
|
|
54
|
-
The resolved instance.
|
|
55
|
-
|
|
56
|
-
Raises
|
|
57
|
-
------
|
|
58
|
-
OrionisContainerException
|
|
59
|
-
If the binding is not an instance of Binding or if the lifetime is not supported.
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
# Ensure the binding is an instance of Binding
|
|
63
|
-
if not isinstance(binding, Binding):
|
|
64
|
-
raise OrionisContainerException(
|
|
65
|
-
"The binding must be an instance of Binding."
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
# Handle based on binding type and lifetime
|
|
69
|
-
if binding.lifetime == Lifetime.TRANSIENT:
|
|
70
|
-
return self.__resolveTransient(binding, *args, **kwargs)
|
|
71
|
-
elif binding.lifetime == Lifetime.SINGLETON:
|
|
72
|
-
return self.__resolveSingleton(binding, *args, **kwargs)
|
|
73
|
-
elif binding.lifetime == Lifetime.SCOPED:
|
|
74
|
-
return self.__resolveScoped(binding, *args, **kwargs)
|
|
75
|
-
|
|
76
|
-
def resolveType(
|
|
77
|
-
self,
|
|
78
|
-
type_: Callable[..., Any],
|
|
79
|
-
*args,
|
|
80
|
-
**kwargs
|
|
81
|
-
) -> Any:
|
|
82
|
-
"""
|
|
83
|
-
Forces resolution of a type whether it's registered in the container or not.
|
|
84
|
-
|
|
85
|
-
Parameters
|
|
86
|
-
----------
|
|
87
|
-
type_ : Callable[..., Any]
|
|
88
|
-
The type or callable to resolve.
|
|
89
|
-
*args : tuple
|
|
90
|
-
Positional arguments to pass to the constructor/callable.
|
|
91
|
-
**kwargs : dict
|
|
92
|
-
Keyword arguments to pass to the constructor/callable.
|
|
93
|
-
|
|
94
|
-
Returns
|
|
95
|
-
-------
|
|
96
|
-
Any
|
|
97
|
-
The resolved instance.
|
|
98
|
-
|
|
99
|
-
Raises
|
|
100
|
-
------
|
|
101
|
-
OrionisContainerException
|
|
102
|
-
If the type cannot be resolved.
|
|
103
|
-
"""
|
|
104
|
-
|
|
105
|
-
# Try to resolve the type with the provided arguments
|
|
106
|
-
# If the type is already bound in the container, resolve it directly
|
|
107
|
-
# or if args or kwargs are provided, instantiate it directly
|
|
108
|
-
# If the type is a concrete class, instantiate it with resolved dependencies
|
|
109
|
-
try:
|
|
110
|
-
|
|
111
|
-
# Check if the type is already bound in the container
|
|
112
|
-
if self.container.bound(type_):
|
|
113
|
-
binding = self.container.getBinding(type_)
|
|
114
|
-
return self.resolve(binding, *args, **kwargs)
|
|
115
|
-
|
|
116
|
-
# If args or kwargs are provided, use them directly
|
|
117
|
-
if args or kwargs:
|
|
118
|
-
|
|
119
|
-
# For classes
|
|
120
|
-
if isinstance(type_, type):
|
|
121
|
-
return type_(*args, **kwargs)
|
|
122
|
-
|
|
123
|
-
# For callables
|
|
124
|
-
elif callable(type_):
|
|
125
|
-
return type_(*args, **kwargs)
|
|
126
|
-
|
|
127
|
-
# Otherwise use reflection to resolve dependencies
|
|
128
|
-
# If the type is a concrete class, instantiate it with resolved dependencies
|
|
129
|
-
elif ReflectionConcrete.isConcreteClass(type_):
|
|
130
|
-
return type_(**self.__resolveDependencies(type_, is_class=True))
|
|
131
|
-
|
|
132
|
-
# Try to call directly if it's a callable
|
|
133
|
-
elif callable(type_) and not isinstance(type_, type):
|
|
134
|
-
return type_(**self.__resolveDependencies(type_, is_class=False))
|
|
135
|
-
|
|
136
|
-
# If the type is neither a concrete class nor a callable, raise an exception
|
|
137
|
-
raise OrionisContainerException(
|
|
138
|
-
f"Cannot force resolve: {getattr(type_, '__name__', str(type_))} is neither a concrete class nor a callable."
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
except Exception as e:
|
|
142
|
-
|
|
143
|
-
# Get the type name safely to avoid AttributeError
|
|
144
|
-
type_name = getattr(type_, '__name__', str(type_))
|
|
145
|
-
module_name = getattr(type_, '__module__', "unknown module")
|
|
146
|
-
|
|
147
|
-
# Provide more detailed error message
|
|
148
|
-
error_msg = f"Error while force-resolving '{type_name}' from '{module_name}':\n{str(e)}"
|
|
149
|
-
|
|
150
|
-
# If it's already an OrionisContainerException, just re-raise it with the context
|
|
151
|
-
if isinstance(e, OrionisContainerException):
|
|
152
|
-
raise e from None
|
|
153
|
-
|
|
154
|
-
# Raise a new OrionisContainerException with the original exception as context
|
|
155
|
-
else:
|
|
156
|
-
raise OrionisContainerException(error_msg) from e
|
|
157
|
-
|
|
158
|
-
def resolveSignature(
|
|
159
|
-
self,
|
|
160
|
-
signature: MethodDependency
|
|
161
|
-
) -> dict:
|
|
162
|
-
"""
|
|
163
|
-
Resolves dependencies defined in a method signature.
|
|
164
|
-
|
|
165
|
-
Parameters
|
|
166
|
-
----------
|
|
167
|
-
signature : MethodDependency
|
|
168
|
-
The method dependency information to resolve.
|
|
169
|
-
|
|
170
|
-
Returns
|
|
171
|
-
-------
|
|
172
|
-
dict
|
|
173
|
-
A dictionary of resolved parameter values.
|
|
174
|
-
|
|
175
|
-
Raises
|
|
176
|
-
------
|
|
177
|
-
OrionisContainerException
|
|
178
|
-
If any dependencies cannot be resolved.
|
|
179
|
-
"""
|
|
180
|
-
# If no dependencies to resolve, return empty dict
|
|
181
|
-
if not signature.resolved and not signature.unresolved:
|
|
182
|
-
return {}
|
|
183
|
-
|
|
184
|
-
# If there are unresolved dependencies, raise an error
|
|
185
|
-
if signature.unresolved:
|
|
186
|
-
raise OrionisContainerException(
|
|
187
|
-
f"Cannot resolve method dependencies because the following parameters are unresolved: {', '.join(signature.unresolved)}."
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
# Create a dict of resolved dependencies
|
|
191
|
-
params = {}
|
|
192
|
-
for param_name, dep in signature.resolved.items():
|
|
193
|
-
if isinstance(dep, KnownDependency):
|
|
194
|
-
# Try to resolve the dependency using the container
|
|
195
|
-
if self.container.bound(dep.type):
|
|
196
|
-
params[param_name] = self.resolve(
|
|
197
|
-
self.container.getBinding(dep.type)
|
|
198
|
-
)
|
|
199
|
-
elif self.container.bound(dep.full_class_path):
|
|
200
|
-
params[param_name] = self.resolve(
|
|
201
|
-
self.container.getBinding(dep.full_class_path)
|
|
202
|
-
)
|
|
203
|
-
# Try to resolve directly if it's a concrete type
|
|
204
|
-
elif ReflectionConcrete.isConcreteClass(dep.type):
|
|
205
|
-
params[param_name] = self.resolveType(dep.type)
|
|
206
|
-
else:
|
|
207
|
-
raise OrionisContainerException(
|
|
208
|
-
f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}'."
|
|
209
|
-
)
|
|
210
|
-
else:
|
|
211
|
-
# Use default value
|
|
212
|
-
params[param_name] = dep
|
|
213
|
-
|
|
214
|
-
return params
|
|
215
|
-
|
|
216
|
-
def __resolveTransient(self, binding: Binding, *args, **kwargs) -> Any:
|
|
217
|
-
"""
|
|
218
|
-
Resolves a service with transient lifetime.
|
|
219
|
-
|
|
220
|
-
Parameters
|
|
221
|
-
----------
|
|
222
|
-
binding : Binding
|
|
223
|
-
The binding to resolve.
|
|
224
|
-
*args : tuple
|
|
225
|
-
Positional arguments to pass to the constructor.
|
|
226
|
-
**kwargs : dict
|
|
227
|
-
Keyword arguments to pass to the constructor.
|
|
228
|
-
|
|
229
|
-
Returns
|
|
230
|
-
-------
|
|
231
|
-
Any
|
|
232
|
-
A new instance of the requested service.
|
|
233
|
-
"""
|
|
234
|
-
|
|
235
|
-
# Check if the binding has a concrete class or function defined
|
|
236
|
-
if binding.concrete:
|
|
237
|
-
if args or kwargs:
|
|
238
|
-
return self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
|
239
|
-
else:
|
|
240
|
-
return self.__instantiateConcreteReflective(binding.concrete)
|
|
241
|
-
|
|
242
|
-
# If the binding has a function defined
|
|
243
|
-
elif binding.function:
|
|
244
|
-
if args or kwargs:
|
|
245
|
-
return self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
|
246
|
-
else:
|
|
247
|
-
return self.__instantiateCallableReflective(binding.function)
|
|
248
|
-
|
|
249
|
-
# If neither concrete class nor function is defined
|
|
250
|
-
else:
|
|
251
|
-
raise OrionisContainerException(
|
|
252
|
-
"Cannot resolve transient binding: neither a concrete class nor a function is defined."
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
def __resolveSingleton(self, binding: Binding, *args, **kwargs) -> Any:
|
|
256
|
-
"""
|
|
257
|
-
Resolves a service with singleton lifetime.
|
|
258
|
-
|
|
259
|
-
Parameters
|
|
260
|
-
----------
|
|
261
|
-
binding : Binding
|
|
262
|
-
The binding to resolve.
|
|
263
|
-
*args : tuple
|
|
264
|
-
Positional arguments to pass to the constructor (only used if instance doesn't exist yet).
|
|
265
|
-
**kwargs : dict
|
|
266
|
-
Keyword arguments to pass to the constructor (only used if instance doesn't exist yet).
|
|
267
|
-
|
|
268
|
-
Returns
|
|
269
|
-
-------
|
|
270
|
-
Any
|
|
271
|
-
The singleton instance of the requested service.
|
|
272
|
-
"""
|
|
273
|
-
# Return existing instance if available
|
|
274
|
-
if binding.instance:
|
|
275
|
-
return binding.instance
|
|
276
|
-
|
|
277
|
-
# Create instance if needed
|
|
278
|
-
if binding.concrete:
|
|
279
|
-
if args or kwargs:
|
|
280
|
-
binding.instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
|
281
|
-
else:
|
|
282
|
-
binding.instance = self.__instantiateConcreteReflective(binding.concrete)
|
|
283
|
-
return binding.instance
|
|
284
|
-
|
|
285
|
-
# If the binding has a function defined
|
|
286
|
-
elif binding.function:
|
|
287
|
-
if args or kwargs:
|
|
288
|
-
result = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
|
289
|
-
else:
|
|
290
|
-
result = self.__instantiateCallableReflective(binding.function)
|
|
291
|
-
|
|
292
|
-
# Store the result directly as the singleton instance
|
|
293
|
-
# We don't automatically invoke factory function results anymore
|
|
294
|
-
binding.instance = result
|
|
295
|
-
return binding.instance
|
|
296
|
-
|
|
297
|
-
# If neither concrete class nor function is defined
|
|
298
|
-
else:
|
|
299
|
-
raise OrionisContainerException(
|
|
300
|
-
"Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
def __resolveScoped(self, binding: Binding, *args, **kwargs) -> Any:
|
|
304
|
-
"""
|
|
305
|
-
Resolves a service with scoped lifetime.
|
|
306
|
-
|
|
307
|
-
Parameters
|
|
308
|
-
----------
|
|
309
|
-
binding : Binding
|
|
310
|
-
The binding to resolve.
|
|
311
|
-
*args : tuple
|
|
312
|
-
Positional arguments to pass to the constructor.
|
|
313
|
-
**kwargs : dict
|
|
314
|
-
Keyword arguments to pass to the constructor.
|
|
315
|
-
|
|
316
|
-
Returns
|
|
317
|
-
-------
|
|
318
|
-
Any
|
|
319
|
-
The scoped instance of the requested service.
|
|
320
|
-
|
|
321
|
-
Raises
|
|
322
|
-
------
|
|
323
|
-
OrionisContainerException
|
|
324
|
-
If no scope is active or service can't be resolved.
|
|
325
|
-
"""
|
|
326
|
-
scope = ScopedContext.getCurrentScope()
|
|
327
|
-
if scope is None:
|
|
328
|
-
raise OrionisContainerException(
|
|
329
|
-
f"No active scope found while resolving scoped service '{binding.alias}'. "
|
|
330
|
-
f"Use 'with container.createContext():' to create a scope context."
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
if binding.alias in scope:
|
|
334
|
-
return scope[binding.alias]
|
|
335
|
-
|
|
336
|
-
# Create a new instance
|
|
337
|
-
if binding.concrete:
|
|
338
|
-
if args or kwargs:
|
|
339
|
-
instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
|
340
|
-
else:
|
|
341
|
-
instance = self.__instantiateConcreteReflective(binding.concrete)
|
|
342
|
-
elif binding.function:
|
|
343
|
-
if args or kwargs:
|
|
344
|
-
instance = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
|
345
|
-
else:
|
|
346
|
-
instance = self.__instantiateCallableReflective(binding.function)
|
|
347
|
-
else:
|
|
348
|
-
raise OrionisContainerException(
|
|
349
|
-
"Cannot resolve scoped binding: neither a concrete class nor a function is defined."
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
scope[binding.alias] = instance
|
|
353
|
-
return instance
|
|
354
|
-
|
|
355
|
-
def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
|
|
356
|
-
"""
|
|
357
|
-
Instantiates a concrete class with the provided arguments.
|
|
358
|
-
|
|
359
|
-
Parameters
|
|
360
|
-
----------
|
|
361
|
-
concrete : Callable[..., Any]
|
|
362
|
-
Class to instantiate.
|
|
363
|
-
*args : tuple
|
|
364
|
-
Positional arguments to pass to the constructor.
|
|
365
|
-
**kwargs : dict
|
|
366
|
-
Keyword arguments to pass to the constructor.
|
|
367
|
-
|
|
368
|
-
Returns
|
|
369
|
-
-------
|
|
370
|
-
object
|
|
371
|
-
A new instance of the specified concrete class.
|
|
372
|
-
"""
|
|
373
|
-
|
|
374
|
-
# try to instantiate the concrete class with the provided arguments
|
|
375
|
-
try:
|
|
376
|
-
|
|
377
|
-
# If the concrete is a class, instantiate it directly
|
|
378
|
-
return concrete(*args, **kwargs)
|
|
379
|
-
|
|
380
|
-
except TypeError as e:
|
|
381
|
-
|
|
382
|
-
# If instantiation fails, use ReflectionConcrete to get class name and constructor signature
|
|
383
|
-
rf_concrete = ReflectionConcrete(concrete)
|
|
384
|
-
class_name = rf_concrete.getClassName()
|
|
385
|
-
signature = rf_concrete.getConstructorSignature()
|
|
386
|
-
|
|
387
|
-
# Raise an exception with detailed information about the failure
|
|
388
|
-
raise OrionisContainerException(
|
|
389
|
-
f"Failed to instantiate [{class_name}] with the provided arguments: {e}\n"
|
|
390
|
-
f"Expected constructor signature: [{signature}]"
|
|
391
|
-
) from e
|
|
392
|
-
|
|
393
|
-
def __instantiateCallableWithArgs(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
|
|
394
|
-
"""
|
|
395
|
-
Invokes a callable with the provided arguments.
|
|
396
|
-
|
|
397
|
-
Parameters
|
|
398
|
-
----------
|
|
399
|
-
fn : Callable[..., Any]
|
|
400
|
-
The callable to invoke.
|
|
401
|
-
*args : tuple
|
|
402
|
-
Positional arguments to pass to the callable.
|
|
403
|
-
**kwargs : dict
|
|
404
|
-
Keyword arguments to pass to the callable.
|
|
405
|
-
|
|
406
|
-
Returns
|
|
407
|
-
-------
|
|
408
|
-
Any
|
|
409
|
-
The result of the callable.
|
|
410
|
-
"""
|
|
411
|
-
|
|
412
|
-
# Try to invoke the callable with the provided arguments
|
|
413
|
-
try:
|
|
414
|
-
|
|
415
|
-
# If the callable is a function, invoke it directly
|
|
416
|
-
return fn(*args, **kwargs)
|
|
417
|
-
|
|
418
|
-
except TypeError as e:
|
|
419
|
-
|
|
420
|
-
# If invocation fails, use ReflectionCallable to get function name and signature
|
|
421
|
-
rf_callable = ReflectionCallable(fn)
|
|
422
|
-
function_name = rf_callable.getName()
|
|
423
|
-
signature = rf_callable.getSignature()
|
|
424
|
-
|
|
425
|
-
# Raise an exception with detailed information about the failure
|
|
426
|
-
raise OrionisContainerException(
|
|
427
|
-
f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
|
|
428
|
-
f"Expected function signature: [{signature}]"
|
|
429
|
-
) from e
|
|
430
|
-
|
|
431
|
-
def __instantiateConcreteReflective(self, concrete: Callable[..., Any]) -> Any:
|
|
432
|
-
"""
|
|
433
|
-
Instantiates a concrete class reflectively, resolving its dependencies from the container.
|
|
434
|
-
|
|
435
|
-
Parameters
|
|
436
|
-
----------
|
|
437
|
-
concrete : Callable[..., Any]
|
|
438
|
-
The concrete class to instantiate.
|
|
439
|
-
|
|
440
|
-
Returns
|
|
441
|
-
-------
|
|
442
|
-
Any
|
|
443
|
-
A new instance of the concrete class.
|
|
444
|
-
"""
|
|
445
|
-
# Resolve dependencies for the concrete class
|
|
446
|
-
params = self.__resolveDependencies(concrete, is_class=True)
|
|
447
|
-
|
|
448
|
-
# Instantiate the concrete class with resolved dependencies
|
|
449
|
-
return concrete(**params)
|
|
450
|
-
|
|
451
|
-
def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
|
|
452
|
-
"""
|
|
453
|
-
Invokes a callable reflectively, resolving its dependencies from the container.
|
|
454
|
-
|
|
455
|
-
Parameters
|
|
456
|
-
----------
|
|
457
|
-
fn : Callable[..., Any]
|
|
458
|
-
The callable to invoke.
|
|
459
|
-
|
|
460
|
-
Returns
|
|
461
|
-
-------
|
|
462
|
-
Any
|
|
463
|
-
The result of the callable.
|
|
464
|
-
"""
|
|
465
|
-
|
|
466
|
-
# Resolve dependencies for the callable
|
|
467
|
-
params = self.__resolveDependencies(fn, is_class=False)
|
|
468
|
-
|
|
469
|
-
# Invoke the callable with resolved dependencies
|
|
470
|
-
return fn(**params)
|
|
471
|
-
|
|
472
|
-
def __resolveDependencies(
|
|
473
|
-
self,
|
|
474
|
-
target: Callable[..., Any],
|
|
475
|
-
*,
|
|
476
|
-
is_class: bool = False
|
|
477
|
-
) -> dict:
|
|
478
|
-
"""
|
|
479
|
-
Resolves dependencies for a target callable or class.
|
|
480
|
-
|
|
481
|
-
Parameters
|
|
482
|
-
----------
|
|
483
|
-
target : Callable[..., Any]
|
|
484
|
-
The target callable or class whose dependencies to resolve.
|
|
485
|
-
is_class : bool, optional
|
|
486
|
-
Whether the target is a class (True) or a callable (False).
|
|
487
|
-
|
|
488
|
-
Returns
|
|
489
|
-
-------
|
|
490
|
-
dict
|
|
491
|
-
A dictionary of resolved dependencies.
|
|
492
|
-
"""
|
|
493
|
-
try:
|
|
494
|
-
|
|
495
|
-
# Use ReflectionConcrete for classes and ReflectionCallable for callables
|
|
496
|
-
if is_class:
|
|
497
|
-
reflection = ReflectionConcrete(target)
|
|
498
|
-
dependencies = reflection.getConstructorDependencies()
|
|
499
|
-
name = reflection.getClassName()
|
|
500
|
-
|
|
501
|
-
# If the target is a callable, use ReflectionCallable
|
|
502
|
-
else:
|
|
503
|
-
reflection = ReflectionCallable(target)
|
|
504
|
-
dependencies = reflection.getDependencies()
|
|
505
|
-
name = reflection.getName()
|
|
506
|
-
|
|
507
|
-
# Check for unresolved dependencies
|
|
508
|
-
if dependencies.unresolved:
|
|
509
|
-
unresolved_args = ', '.join(dependencies.unresolved)
|
|
510
|
-
raise OrionisContainerException(
|
|
511
|
-
f"Cannot resolve '{name}' because the following required arguments are missing: [{unresolved_args}]."
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
# Resolve dependencies
|
|
515
|
-
params = {}
|
|
516
|
-
for param_name, dep in dependencies.resolved.items():
|
|
517
|
-
|
|
518
|
-
# If the dependency is a KnownDependency, resolve it
|
|
519
|
-
if isinstance(dep, KnownDependency):
|
|
520
|
-
|
|
521
|
-
# If the dependency is a built-in type, raise an exception
|
|
522
|
-
if dep.module_name == 'builtins':
|
|
523
|
-
raise OrionisContainerException(
|
|
524
|
-
f"Cannot resolve '{name}' because parameter '{param_name}' depends on built-in type '{dep.type.__name__}'."
|
|
525
|
-
)
|
|
526
|
-
|
|
527
|
-
# Try to resolve from container using type (Abstract or Interface)
|
|
528
|
-
if self.container.bound(dep.type):
|
|
529
|
-
params[param_name] = self.resolve(
|
|
530
|
-
self.container.getBinding(dep.type)
|
|
531
|
-
)
|
|
532
|
-
|
|
533
|
-
# Try to resolve from container using full class path
|
|
534
|
-
elif self.container.bound(dep.full_class_path):
|
|
535
|
-
params[param_name] = self.resolve(
|
|
536
|
-
self.container.getBinding(dep.full_class_path)
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
# Try to instantiate directly if it's a concrete class
|
|
540
|
-
elif ReflectionConcrete.isConcreteClass(dep.type):
|
|
541
|
-
params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=True))
|
|
542
|
-
|
|
543
|
-
# Try to call directly if it's a callable
|
|
544
|
-
elif callable(dep.type) and not isinstance(dep.type, type):
|
|
545
|
-
params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=False))
|
|
546
|
-
|
|
547
|
-
# If the dependency cannot be resolved, raise an exception
|
|
548
|
-
else:
|
|
549
|
-
raise OrionisContainerException(
|
|
550
|
-
f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}' for '{name}'."
|
|
551
|
-
)
|
|
552
|
-
else:
|
|
553
|
-
# Use default value
|
|
554
|
-
params[param_name] = dep
|
|
555
|
-
|
|
556
|
-
# Return the resolved parameters
|
|
557
|
-
return params
|
|
558
|
-
|
|
559
|
-
except ImportError as e:
|
|
560
|
-
|
|
561
|
-
# Get target name safely
|
|
562
|
-
target_name = getattr(target, '__name__', str(target))
|
|
563
|
-
module_name = getattr(target, '__module__', "unknown module")
|
|
564
|
-
|
|
565
|
-
# Improved circular import detection with more helpful guidance
|
|
566
|
-
if "circular import" in str(e).lower() or "cannot import name" in str(e).lower():
|
|
567
|
-
raise OrionisContainerException(
|
|
568
|
-
f"Circular import detected while resolving dependencies for '{target_name}' in module '{module_name}'.\n"
|
|
569
|
-
f"This typically happens when two modules import each other. Consider:\n"
|
|
570
|
-
f"1. Restructuring your code to avoid circular dependencies\n"
|
|
571
|
-
f"2. Using delayed imports inside methods rather than at module level\n"
|
|
572
|
-
f"3. Using dependency injection to break the cycle\n"
|
|
573
|
-
f"Original error: {str(e)}"
|
|
574
|
-
) from e
|
|
575
|
-
else:
|
|
576
|
-
raise OrionisContainerException(
|
|
577
|
-
f"Import error while resolving dependencies for '{target_name}' in module '{module_name}':\n"
|
|
578
|
-
f"{str(e)}"
|
|
579
|
-
) from e
|
|
580
|
-
|
|
581
|
-
except Exception as e:
|
|
582
|
-
|
|
583
|
-
# More robust attribute extraction with fallbacks
|
|
584
|
-
target_type = "class" if isinstance(target, type) else "function"
|
|
585
|
-
target_name = getattr(target, '__name__', str(target))
|
|
586
|
-
module_name = getattr(target, '__module__', "unknown module")
|
|
587
|
-
|
|
588
|
-
# More detailed error message with context about the failure
|
|
589
|
-
error_msg = (
|
|
590
|
-
f"Error resolving dependencies for {target_type} '{target_name}' in '{module_name}':\n"
|
|
591
|
-
f"{str(e)}\n"
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
# Add specific guidance based on error type
|
|
595
|
-
if isinstance(e, TypeError):
|
|
596
|
-
error_msg += "This may be caused by incompatible argument types or missing required parameters."
|
|
597
|
-
elif isinstance(e, AttributeError):
|
|
598
|
-
error_msg += "This may be caused by accessing undefined attributes in the dependency chain."
|
|
599
|
-
error_msg += "\nCheck that all dependencies are properly registered in the container."
|
|
600
|
-
|
|
601
|
-
# Raise a more informative exception
|
|
602
|
-
raise OrionisContainerException(error_msg) from e
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Any, Dict, List
|
|
3
|
-
from orionis.services.introspection.dependencies.entities.known_dependencies import KnownDependency
|
|
4
|
-
from orionis.services.introspection.exceptions import ReflectionTypeError
|
|
5
|
-
|
|
6
|
-
@dataclass(frozen=True, kw_only=True)
|
|
7
|
-
class CallableDependency:
|
|
8
|
-
"""
|
|
9
|
-
Represents the resolved and unresolved dependencies of a callable.
|
|
10
|
-
|
|
11
|
-
Attributes
|
|
12
|
-
----------
|
|
13
|
-
resolved : Dict[KnownDependency, Any]
|
|
14
|
-
Dictionary mapping KnownDependency instances to their resolved values.
|
|
15
|
-
unresolved : List[str]
|
|
16
|
-
List of parameter names or dependency identifiers that could not be resolved.
|
|
17
|
-
|
|
18
|
-
Raises
|
|
19
|
-
------
|
|
20
|
-
ReflectionTypeError
|
|
21
|
-
If `resolved` is not a dictionary or `unresolved` is not a list.
|
|
22
|
-
ValueError
|
|
23
|
-
If `resolved` contains None keys or `unresolved` contains empty strings.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
# Resolved dependencies mapped to their values
|
|
27
|
-
resolved: Dict[KnownDependency, Any]
|
|
28
|
-
|
|
29
|
-
# Unresolved dependencies as a list of parameter names
|
|
30
|
-
unresolved: List[str]
|
|
31
|
-
|
|
32
|
-
def __post_init__(self):
|
|
33
|
-
"""
|
|
34
|
-
Validates the types and values of the attributes after initialization.
|
|
35
|
-
|
|
36
|
-
Raises
|
|
37
|
-
------
|
|
38
|
-
ReflectionTypeError
|
|
39
|
-
If `resolved` is not a dictionary or `unresolved` is not a list.
|
|
40
|
-
ValueError
|
|
41
|
-
If `resolved` contains None keys or `unresolved` contains empty strings.
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
# Validate 'resolved' is a dict with proper key types
|
|
45
|
-
if not isinstance(self.resolved, dict):
|
|
46
|
-
raise ReflectionTypeError(
|
|
47
|
-
f"'resolved' must be a dict, got {type(self.resolved).__name__}"
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
# Validate 'unresolved' is a list of valid parameter names
|
|
51
|
-
if not isinstance(self.unresolved, list):
|
|
52
|
-
raise ReflectionTypeError(
|
|
53
|
-
f"'unresolved' must be a list, got {type(self.unresolved).__name__}"
|
|
54
|
-
)
|