orionis 0.315.0__py3-none-any.whl → 0.317.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 +408 -53
- orionis/container/entities/binding.py +124 -0
- orionis/metadata/framework.py +1 -1
- orionis/services/introspection/callables/__init__.py +0 -0
- orionis/services/introspection/callables/reflection_callable.py +147 -0
- orionis/services/introspection/dependencies/reflect_dependencies.py +57 -6
- {orionis-0.315.0.dist-info → orionis-0.317.0.dist-info}/METADATA +1 -1
- {orionis-0.315.0.dist-info → orionis-0.317.0.dist-info}/RECORD +14 -10
- tests/services/inspection/dependencies/test_reflect_dependencies.py +34 -1
- tests/services/inspection/reflection/test_reflection_callable.py +157 -0
- {orionis-0.315.0.dist-info → orionis-0.317.0.dist-info}/WHEEL +0 -0
- {orionis-0.315.0.dist-info → orionis-0.317.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.315.0.dist-info → orionis-0.317.0.dist-info}/top_level.txt +0 -0
- {orionis-0.315.0.dist-info → orionis-0.317.0.dist-info}/zip-safe +0 -0
orionis/container/container.py
CHANGED
@@ -1,21 +1,147 @@
|
|
1
1
|
from typing import Any, Callable
|
2
|
+
from orionis.container.entities.binding import Binding
|
2
3
|
from orionis.container.enums.lifetimes import Lifetime
|
3
4
|
from orionis.container.exceptions.container_exception import OrionisContainerException
|
4
5
|
from orionis.container.exceptions.type_error_exception import OrionisContainerTypeError
|
5
6
|
from orionis.services.introspection.abstract.reflection_abstract import ReflectionAbstract
|
7
|
+
from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
|
6
8
|
from orionis.services.introspection.concretes.reflection_concrete import ReflectionConcrete
|
7
9
|
from orionis.services.introspection.instances.reflection_instance import ReflectionInstance
|
8
10
|
from orionis.services.introspection.reflection import Reflection
|
11
|
+
import inspect
|
12
|
+
import threading
|
9
13
|
|
10
14
|
class Container:
|
11
15
|
|
16
|
+
# Singleton instance of the container.
|
17
|
+
# This is a class variable that holds the single instance of the Container class.
|
18
|
+
_instance = None
|
19
|
+
|
20
|
+
# Lock for thread-safe singleton instantiation.
|
21
|
+
# This lock ensures that only one thread can create the instance at a time,
|
22
|
+
# preventing
|
23
|
+
_lock = threading.Lock()
|
24
|
+
|
25
|
+
# Class variable to track if the container has been initialized.
|
26
|
+
# This is used to ensure that the initialization logic runs only once,
|
27
|
+
# regardless of how many times the class is instantiated.
|
28
|
+
_initialized = False
|
29
|
+
|
30
|
+
def __new__(cls, *args, **kwargs):
|
31
|
+
"""
|
32
|
+
Creates and returns a singleton instance of the class.
|
33
|
+
|
34
|
+
This method implements the singleton pattern, ensuring that only one instance
|
35
|
+
of the class exists. If an instance does not exist, it acquires a lock to
|
36
|
+
ensure thread safety and creates the instance. Subsequent calls return the
|
37
|
+
existing instance.
|
38
|
+
Parameters
|
39
|
+
----------
|
40
|
+
*args : tuple
|
41
|
+
Variable length argument list.
|
42
|
+
**kwargs : dict
|
43
|
+
Arbitrary keyword arguments.
|
44
|
+
Returns
|
45
|
+
-------
|
46
|
+
Container
|
47
|
+
The singleton instance of the class.
|
48
|
+
"""
|
49
|
+
if cls._instance is None:
|
50
|
+
with cls._lock:
|
51
|
+
if cls._instance is None:
|
52
|
+
cls._instance = super(Container, cls).__new__(cls)
|
53
|
+
return cls._instance
|
54
|
+
|
12
55
|
def __init__(self):
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
56
|
+
"""
|
57
|
+
Initializes a new instance of the container.
|
58
|
+
|
59
|
+
This constructor sets up the internal dictionaries for bindings and aliases,
|
60
|
+
ensuring that these are only initialized once per class. The initialization
|
61
|
+
is guarded by the `_initialized` class attribute to prevent redundant setup.
|
62
|
+
|
63
|
+
Notes
|
64
|
+
-----
|
65
|
+
- The `__bindings` dictionary is used to store service bindings.
|
66
|
+
- The `__aliasses` dictionary is used to store service aliases.
|
67
|
+
- Initialization occurs only once per class, regardless of the number of instances.
|
68
|
+
"""
|
69
|
+
if not self.__class__._initialized:
|
70
|
+
self.__bindings = {}
|
71
|
+
self.__aliasses = {}
|
72
|
+
self.__class__._initialized = True
|
73
|
+
|
74
|
+
def __dropService(
|
75
|
+
self,
|
76
|
+
abstract: Callable[..., Any] = None,
|
77
|
+
alias: str = None
|
78
|
+
) -> None:
|
79
|
+
"""
|
80
|
+
Drops a service from the container.
|
81
|
+
|
82
|
+
Parameters
|
83
|
+
----------
|
84
|
+
abstract : Callable[..., Any]
|
85
|
+
The abstract type or interface to be removed.
|
86
|
+
alias : str, optional
|
87
|
+
The alias of the service to be removed. If not provided, the service will be removed by its abstract type.
|
88
|
+
|
89
|
+
Raises
|
90
|
+
------
|
91
|
+
OrionisContainerException
|
92
|
+
If the service does not exist in the container.
|
93
|
+
"""
|
94
|
+
|
95
|
+
# If abstract is provided
|
96
|
+
if abstract:
|
97
|
+
|
98
|
+
# Remove the abstract service from the bindings if it exists
|
99
|
+
if abstract in self.__bindings:
|
100
|
+
del self.__bindings[abstract]
|
101
|
+
|
102
|
+
# Remove the default alias (module + class name) from aliases if it exists
|
103
|
+
abs_alias = ReflectionAbstract(abstract).getModuleWithClassName()
|
104
|
+
if abs_alias in self.__aliasses:
|
105
|
+
del self.__aliasses[abs_alias]
|
17
106
|
|
18
|
-
|
107
|
+
# If a custom alias is provided
|
108
|
+
if alias:
|
109
|
+
|
110
|
+
# Remove it from the aliases dictionary if it exists
|
111
|
+
if alias in self.__aliasses:
|
112
|
+
del self.__aliasses[alias]
|
113
|
+
|
114
|
+
# Remove the binding associated with the alias
|
115
|
+
if alias in self.__bindings:
|
116
|
+
del self.__bindings[alias]
|
117
|
+
|
118
|
+
def __ensureIsCallable(
|
119
|
+
self,
|
120
|
+
value: Any
|
121
|
+
) -> None:
|
122
|
+
"""
|
123
|
+
Ensures that the provided value is callable.
|
124
|
+
|
125
|
+
Parameters
|
126
|
+
----------
|
127
|
+
value : Any
|
128
|
+
The value to check.
|
129
|
+
|
130
|
+
Raises
|
131
|
+
------
|
132
|
+
OrionisContainerTypeError
|
133
|
+
If the value is not callable.
|
134
|
+
"""
|
135
|
+
|
136
|
+
if not callable(value):
|
137
|
+
raise OrionisContainerTypeError(
|
138
|
+
f"Expected a callable type, but got {type(value).__name__} instead."
|
139
|
+
)
|
140
|
+
|
141
|
+
def __ensureAliasType(
|
142
|
+
self,
|
143
|
+
value: Any
|
144
|
+
) -> None:
|
19
145
|
"""
|
20
146
|
Ensures that the provided value is a valid alias of type str and does not contain invalid characters.
|
21
147
|
|
@@ -49,7 +175,11 @@ class Container:
|
|
49
175
|
"Aliases must not contain whitespace or special symbols."
|
50
176
|
)
|
51
177
|
|
52
|
-
def __ensureAbstractClass(
|
178
|
+
def __ensureAbstractClass(
|
179
|
+
self,
|
180
|
+
abstract: Callable[..., Any],
|
181
|
+
lifetime: str
|
182
|
+
) -> None:
|
53
183
|
"""
|
54
184
|
Ensures that the provided abstract is an abstract class.
|
55
185
|
|
@@ -72,7 +202,11 @@ class Container:
|
|
72
202
|
f"Unexpected error registering {lifetime} service: {e}"
|
73
203
|
) from e
|
74
204
|
|
75
|
-
def __ensureConcreteClass(
|
205
|
+
def __ensureConcreteClass(
|
206
|
+
self,
|
207
|
+
concrete: Callable[..., Any],
|
208
|
+
lifetime: str
|
209
|
+
) -> None:
|
76
210
|
"""
|
77
211
|
Ensures that the provided concrete is a concrete (non-abstract) class.
|
78
212
|
|
@@ -95,7 +229,11 @@ class Container:
|
|
95
229
|
f"Unexpected error registering {lifetime} service: {e}"
|
96
230
|
) from e
|
97
231
|
|
98
|
-
def __ensureIsSubclass(
|
232
|
+
def __ensureIsSubclass(
|
233
|
+
self,
|
234
|
+
abstract: Callable[..., Any],
|
235
|
+
concrete: Callable[..., Any]
|
236
|
+
) -> None:
|
99
237
|
"""
|
100
238
|
Validates that the concrete class is a subclass of the provided abstract class.
|
101
239
|
|
@@ -122,7 +260,11 @@ class Container:
|
|
122
260
|
"Please ensure that the concrete class is a subclass of the specified abstract class."
|
123
261
|
)
|
124
262
|
|
125
|
-
def __ensureIsNotSubclass(
|
263
|
+
def __ensureIsNotSubclass(
|
264
|
+
self,
|
265
|
+
abstract: Callable[..., Any],
|
266
|
+
concrete: Callable[..., Any]
|
267
|
+
) -> None:
|
126
268
|
"""
|
127
269
|
Validates that the concrete class is NOT a subclass of the provided abstract class.
|
128
270
|
|
@@ -148,7 +290,10 @@ class Container:
|
|
148
290
|
"Please ensure that the concrete class is not a subclass of the specified abstract class."
|
149
291
|
)
|
150
292
|
|
151
|
-
def __ensureInstance(
|
293
|
+
def __ensureInstance(
|
294
|
+
self,
|
295
|
+
instance: Any
|
296
|
+
) -> None:
|
152
297
|
"""
|
153
298
|
Ensures that the provided object is a valid instance.
|
154
299
|
|
@@ -175,7 +320,13 @@ class Container:
|
|
175
320
|
f"Error registering instance: {e}"
|
176
321
|
) from e
|
177
322
|
|
178
|
-
def __ensureImplementation(
|
323
|
+
def __ensureImplementation(
|
324
|
+
self,
|
325
|
+
*,
|
326
|
+
abstract: Callable[..., Any] = None,
|
327
|
+
concrete: Callable[..., Any] = None,
|
328
|
+
instance: Any = None
|
329
|
+
) -> None:
|
179
330
|
"""
|
180
331
|
Ensures that a concrete class or instance implements all abstract methods defined in an abstract class.
|
181
332
|
|
@@ -229,7 +380,14 @@ class Container:
|
|
229
380
|
"Please ensure that all abstract methods are implemented."
|
230
381
|
)
|
231
382
|
|
232
|
-
def transient(
|
383
|
+
def transient(
|
384
|
+
self,
|
385
|
+
abstract: Callable[..., Any],
|
386
|
+
concrete: Callable[..., Any],
|
387
|
+
*,
|
388
|
+
alias: str = None,
|
389
|
+
enforce_decoupling: bool = False
|
390
|
+
) -> bool:
|
233
391
|
"""
|
234
392
|
Registers a service with a transient lifetime.
|
235
393
|
|
@@ -267,11 +425,12 @@ class Container:
|
|
267
425
|
# Ensure that concrete is a concrete class
|
268
426
|
self.__ensureConcreteClass(concrete, Lifetime.TRANSIENT)
|
269
427
|
|
428
|
+
# Ensure that concrete is NOT a subclass of abstract
|
270
429
|
if enforce_decoupling:
|
271
|
-
# Ensure that concrete is NOT a subclass of abstract
|
272
430
|
self.__ensureIsNotSubclass(abstract, concrete)
|
431
|
+
|
432
|
+
# Validate that concrete is a subclass of abstract
|
273
433
|
else:
|
274
|
-
# Validate that concrete is a subclass of abstract
|
275
434
|
self.__ensureIsSubclass(abstract, concrete)
|
276
435
|
|
277
436
|
# Ensure implementation
|
@@ -284,20 +443,37 @@ class Container:
|
|
284
443
|
if alias:
|
285
444
|
self.__ensureAliasType(alias)
|
286
445
|
|
446
|
+
# Extract the module and class name for the alias
|
447
|
+
else:
|
448
|
+
rf_asbtract = ReflectionAbstract(abstract)
|
449
|
+
alias = rf_asbtract.getModuleWithClassName()
|
450
|
+
|
451
|
+
# If the service is already registered, drop it
|
452
|
+
self.__dropService(abstract, alias)
|
453
|
+
|
287
454
|
# Register the service with transient lifetime
|
288
|
-
self.
|
455
|
+
self.__bindings[abstract] = Binding(
|
456
|
+
contract = abstract,
|
457
|
+
concrete = concrete,
|
458
|
+
lifetime = Lifetime.TRANSIENT,
|
459
|
+
enforce_decoupling = enforce_decoupling,
|
460
|
+
alias = alias
|
461
|
+
)
|
289
462
|
|
290
|
-
#
|
291
|
-
|
292
|
-
self.__transient[alias] = concrete
|
293
|
-
elif hasattr(abstract, '__name__'):
|
294
|
-
alias = abstract.__name__
|
295
|
-
self.__transient[alias] = concrete
|
463
|
+
# Register the alias
|
464
|
+
self.__aliasses[alias] = self.__bindings[abstract]
|
296
465
|
|
297
466
|
# Return True to indicate successful registration
|
298
467
|
return True
|
299
468
|
|
300
|
-
def singleton(
|
469
|
+
def singleton(
|
470
|
+
self,
|
471
|
+
abstract: Callable[..., Any],
|
472
|
+
concrete: Callable[..., Any],
|
473
|
+
*,
|
474
|
+
alias: str = None,
|
475
|
+
enforce_decoupling: bool = False
|
476
|
+
) -> bool:
|
301
477
|
"""
|
302
478
|
Registers a service with a singleton lifetime.
|
303
479
|
|
@@ -334,11 +510,12 @@ class Container:
|
|
334
510
|
# Ensure that concrete is a concrete class
|
335
511
|
self.__ensureConcreteClass(concrete, Lifetime.SINGLETON)
|
336
512
|
|
513
|
+
# Ensure that concrete is NOT a subclass of abstract
|
337
514
|
if enforce_decoupling:
|
338
|
-
# Ensure that concrete is NOT a subclass of abstract
|
339
515
|
self.__ensureIsNotSubclass(abstract, concrete)
|
516
|
+
|
517
|
+
# Validate that concrete is a subclass of abstract
|
340
518
|
else:
|
341
|
-
# Validate that concrete is a subclass of abstract
|
342
519
|
self.__ensureIsSubclass(abstract, concrete)
|
343
520
|
|
344
521
|
# Ensure implementation
|
@@ -350,21 +527,36 @@ class Container:
|
|
350
527
|
# Ensure that the alias is a valid string if provided
|
351
528
|
if alias:
|
352
529
|
self.__ensureAliasType(alias)
|
530
|
+
else:
|
531
|
+
rf_asbtract = ReflectionAbstract(abstract)
|
532
|
+
alias = rf_asbtract.getModuleWithClassName()
|
533
|
+
|
534
|
+
# If the service is already registered, drop it
|
535
|
+
self.__dropService(abstract, alias)
|
353
536
|
|
354
537
|
# Register the service with singleton lifetime
|
355
|
-
self.
|
538
|
+
self.__bindings[abstract] = Binding(
|
539
|
+
contract = abstract,
|
540
|
+
concrete = concrete,
|
541
|
+
lifetime = Lifetime.SINGLETON,
|
542
|
+
enforce_decoupling = enforce_decoupling,
|
543
|
+
alias = alias
|
544
|
+
)
|
356
545
|
|
357
|
-
#
|
358
|
-
|
359
|
-
self.__singleton[alias] = concrete
|
360
|
-
elif hasattr(abstract, '__name__'):
|
361
|
-
alias = abstract.__name__
|
362
|
-
self.__singleton[alias] = concrete
|
546
|
+
# Register the alias
|
547
|
+
self.__aliasses[alias] = self.__bindings[abstract]
|
363
548
|
|
364
549
|
# Return True to indicate successful registration
|
365
550
|
return True
|
366
551
|
|
367
|
-
def scoped(
|
552
|
+
def scoped(
|
553
|
+
self,
|
554
|
+
abstract: Callable[..., Any],
|
555
|
+
concrete: Callable[..., Any],
|
556
|
+
*,
|
557
|
+
alias: str = None,
|
558
|
+
enforce_decoupling: bool = False
|
559
|
+
) -> bool:
|
368
560
|
"""
|
369
561
|
Registers a service with a scoped lifetime.
|
370
562
|
|
@@ -401,11 +593,12 @@ class Container:
|
|
401
593
|
# Ensure that concrete is a concrete class
|
402
594
|
self.__ensureConcreteClass(concrete, Lifetime.SCOPED)
|
403
595
|
|
596
|
+
# Ensure that concrete is NOT a subclass of abstract
|
404
597
|
if enforce_decoupling:
|
405
|
-
# Ensure that concrete is NOT a subclass of abstract
|
406
598
|
self.__ensureIsNotSubclass(abstract, concrete)
|
599
|
+
|
600
|
+
# Validate that concrete is a subclass of abstract
|
407
601
|
else:
|
408
|
-
# Validate that concrete is a subclass of abstract
|
409
602
|
self.__ensureIsSubclass(abstract, concrete)
|
410
603
|
|
411
604
|
# Ensure implementation
|
@@ -417,21 +610,36 @@ class Container:
|
|
417
610
|
# Ensure that the alias is a valid string if provided
|
418
611
|
if alias:
|
419
612
|
self.__ensureAliasType(alias)
|
613
|
+
else:
|
614
|
+
rf_asbtract = ReflectionAbstract(abstract)
|
615
|
+
alias = rf_asbtract.getModuleWithClassName()
|
616
|
+
|
617
|
+
# If the service is already registered, drop it
|
618
|
+
self.__dropService(abstract, alias)
|
420
619
|
|
421
620
|
# Register the service with scoped lifetime
|
422
|
-
self.
|
621
|
+
self.__bindings[abstract] = Binding(
|
622
|
+
contract = abstract,
|
623
|
+
concrete = concrete,
|
624
|
+
lifetime = Lifetime.SCOPED,
|
625
|
+
enforce_decoupling = enforce_decoupling,
|
626
|
+
alias = alias
|
627
|
+
)
|
423
628
|
|
424
|
-
#
|
425
|
-
|
426
|
-
self.__scoped[alias] = concrete
|
427
|
-
elif hasattr(abstract, '__name__'):
|
428
|
-
alias = abstract.__name__
|
429
|
-
self.__scoped[alias] = concrete
|
629
|
+
# Register the alias
|
630
|
+
self.__aliasses[alias] = self.__bindings[abstract]
|
430
631
|
|
431
632
|
# Return True to indicate successful registration
|
432
633
|
return True
|
433
634
|
|
434
|
-
def instance(
|
635
|
+
def instance(
|
636
|
+
self,
|
637
|
+
abstract: Callable[..., Any],
|
638
|
+
instance: Any,
|
639
|
+
*,
|
640
|
+
alias: str = None,
|
641
|
+
enforce_decoupling: bool = False
|
642
|
+
) -> bool:
|
435
643
|
"""
|
436
644
|
Registers an instance of a class or interface in the container.
|
437
645
|
Parameters
|
@@ -466,11 +674,12 @@ class Container:
|
|
466
674
|
# Ensure that the instance is a valid instance
|
467
675
|
self.__ensureInstance(instance)
|
468
676
|
|
677
|
+
# Ensure that instance is NOT a subclass of abstract
|
469
678
|
if enforce_decoupling:
|
470
|
-
# Ensure that instance is NOT a subclass of abstract
|
471
679
|
self.__ensureIsNotSubclass(abstract, instance.__class__)
|
680
|
+
|
681
|
+
# Validate that instance is a subclass of abstract
|
472
682
|
else:
|
473
|
-
# Validate that instance is a subclass of abstract
|
474
683
|
self.__ensureIsSubclass(abstract, instance.__class__)
|
475
684
|
|
476
685
|
# Ensure implementation
|
@@ -482,19 +691,165 @@ class Container:
|
|
482
691
|
# Ensure that the alias is a valid string if provided
|
483
692
|
if alias:
|
484
693
|
self.__ensureAliasType(alias)
|
694
|
+
else:
|
695
|
+
rf_asbtract = ReflectionAbstract(abstract)
|
696
|
+
alias = rf_asbtract.getModuleWithClassName()
|
697
|
+
|
698
|
+
# If the service is already registered, drop it
|
699
|
+
self.__dropService(abstract, alias)
|
485
700
|
|
486
701
|
# Register the instance with the abstract type
|
487
|
-
self.
|
702
|
+
self.__bindings[abstract] = Binding(
|
703
|
+
contract = abstract,
|
704
|
+
instance = instance,
|
705
|
+
lifetime = Lifetime.SINGLETON,
|
706
|
+
enforce_decoupling = enforce_decoupling,
|
707
|
+
alias = alias
|
708
|
+
)
|
488
709
|
|
489
|
-
#
|
490
|
-
|
491
|
-
self.__instance[alias] = instance
|
492
|
-
elif hasattr(abstract, '__name__'):
|
493
|
-
alias = abstract.__name__
|
494
|
-
self.__instance[alias] = instance
|
710
|
+
# Register the alias
|
711
|
+
self.__aliasses[alias] = self.__bindings[abstract]
|
495
712
|
|
496
713
|
# Return True to indicate successful registration
|
497
714
|
return True
|
498
715
|
|
499
|
-
def
|
500
|
-
|
716
|
+
def function(
|
717
|
+
self,
|
718
|
+
alias: str,
|
719
|
+
function: Callable[..., Any],
|
720
|
+
*,
|
721
|
+
lifetime: Lifetime = Lifetime.TRANSIENT
|
722
|
+
) -> bool:
|
723
|
+
"""
|
724
|
+
Registers a function or factory under a given alias.
|
725
|
+
|
726
|
+
Parameters
|
727
|
+
----------
|
728
|
+
alias : str
|
729
|
+
The alias to register the function under.
|
730
|
+
function : Callable[..., Any]
|
731
|
+
The function or factory to register.
|
732
|
+
lifetime : Lifetime, optional
|
733
|
+
The lifetime of the function registration (default is TRANSIENT).
|
734
|
+
|
735
|
+
Returns
|
736
|
+
-------
|
737
|
+
bool
|
738
|
+
True if the function was registered successfully.
|
739
|
+
|
740
|
+
Raises
|
741
|
+
------
|
742
|
+
OrionisContainerTypeError
|
743
|
+
If the alias is invalid or the function is not callable.
|
744
|
+
OrionisContainerException
|
745
|
+
If the lifetime is not allowed for the function signature.
|
746
|
+
"""
|
747
|
+
# Normalize and validate the lifetime parameter
|
748
|
+
if not isinstance(lifetime, Lifetime):
|
749
|
+
if isinstance(lifetime, str):
|
750
|
+
lifetime_key = lifetime.strip().upper()
|
751
|
+
if lifetime_key in Lifetime.__members__:
|
752
|
+
lifetime = Lifetime[lifetime_key]
|
753
|
+
else:
|
754
|
+
valid = ', '.join(Lifetime.__members__.keys())
|
755
|
+
raise OrionisContainerTypeError(
|
756
|
+
f"Invalid lifetime '{lifetime}'. Valid options are: {valid}."
|
757
|
+
)
|
758
|
+
else:
|
759
|
+
raise OrionisContainerTypeError(
|
760
|
+
f"Lifetime must be of type str or Lifetime enum, got {type(lifetime).__name__}."
|
761
|
+
)
|
762
|
+
|
763
|
+
# Ensure that the alias is a valid string
|
764
|
+
self.__ensureAliasType(alias)
|
765
|
+
|
766
|
+
# Validate that the function is callable
|
767
|
+
self.__ensureIsCallable(function)
|
768
|
+
|
769
|
+
# Inspect the function signature
|
770
|
+
params = ReflectionCallable(function).getDependencies()
|
771
|
+
|
772
|
+
# If the function requires arguments, only allow TRANSIENT
|
773
|
+
if (len(params.resolved) + len(params.unresolved)) > 0 and lifetime != Lifetime.TRANSIENT:
|
774
|
+
raise OrionisContainerException(
|
775
|
+
"Functions that require arguments can only be registered with a TRANSIENT lifetime."
|
776
|
+
)
|
777
|
+
|
778
|
+
# If the service is already registered, drop it
|
779
|
+
self.__dropService(None, alias)
|
780
|
+
|
781
|
+
# Register the function with the specified alias and lifetime
|
782
|
+
self.__bindings[alias] = Binding(
|
783
|
+
function=function,
|
784
|
+
lifetime=lifetime,
|
785
|
+
alias=alias
|
786
|
+
)
|
787
|
+
|
788
|
+
# Register the function as a binding
|
789
|
+
self.__aliasses[alias] = self.__bindings[alias]
|
790
|
+
|
791
|
+
return True
|
792
|
+
|
793
|
+
def bound(
|
794
|
+
self,
|
795
|
+
abstract_or_alias: Any,
|
796
|
+
) -> bool:
|
797
|
+
"""
|
798
|
+
Checks if a service (by abstract type or alias) is registered in the container.
|
799
|
+
|
800
|
+
Parameters
|
801
|
+
----------
|
802
|
+
abstract_or_alias : Any
|
803
|
+
The abstract class, interface, or alias (str) to check for registration.
|
804
|
+
|
805
|
+
Returns
|
806
|
+
-------
|
807
|
+
bool
|
808
|
+
True if the service is registered (either as an abstract type or alias), False otherwise.
|
809
|
+
|
810
|
+
Notes
|
811
|
+
-----
|
812
|
+
This method allows you to verify whether a service has been registered in the container,
|
813
|
+
either by its abstract type or by its alias. It supports both class-based and string-based lookups.
|
814
|
+
"""
|
815
|
+
return (
|
816
|
+
abstract_or_alias in self.__bindings
|
817
|
+
or abstract_or_alias in self.__aliasses
|
818
|
+
)
|
819
|
+
|
820
|
+
def make(
|
821
|
+
self,
|
822
|
+
abstract_or_alias: Any,
|
823
|
+
*args,
|
824
|
+
**kwargs
|
825
|
+
) -> Any:
|
826
|
+
"""
|
827
|
+
Resolves and returns an instance of the requested service.
|
828
|
+
|
829
|
+
Parameters
|
830
|
+
----------
|
831
|
+
abstract_or_alias : Any
|
832
|
+
The abstract class, interface, or alias (str) to resolve.
|
833
|
+
*args : tuple
|
834
|
+
Positional arguments to pass to the constructor of the resolved service.
|
835
|
+
**kwargs : dict
|
836
|
+
Keyword arguments to pass to the constructor of the resolved service.
|
837
|
+
|
838
|
+
Returns
|
839
|
+
-------
|
840
|
+
Any
|
841
|
+
An instance of the requested service.
|
842
|
+
|
843
|
+
Raises
|
844
|
+
------
|
845
|
+
OrionisContainerException
|
846
|
+
If the requested service is not registered in the container.
|
847
|
+
"""
|
848
|
+
if not self.bound(abstract_or_alias):
|
849
|
+
raise OrionisContainerException(
|
850
|
+
f"The requested service '{abstract_or_alias}' is not registered in the container."
|
851
|
+
)
|
852
|
+
|
853
|
+
binding = self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
|
854
|
+
|
855
|
+
print(binding)
|
@@ -0,0 +1,124 @@
|
|
1
|
+
from dataclasses import asdict, dataclass, field, fields
|
2
|
+
from orionis.container.enums.lifetimes import Lifetime
|
3
|
+
from orionis.container.exceptions.type_error_exception import OrionisContainerTypeError
|
4
|
+
|
5
|
+
@dataclass(unsafe_hash=True, kw_only=True)
|
6
|
+
class Binding:
|
7
|
+
|
8
|
+
contract: type = field(
|
9
|
+
default=None,
|
10
|
+
metadata={
|
11
|
+
"description": "Contrato de la clase concreta a inyectar.",
|
12
|
+
"default": None
|
13
|
+
}
|
14
|
+
)
|
15
|
+
|
16
|
+
concrete: type = field(
|
17
|
+
default=None,
|
18
|
+
metadata={
|
19
|
+
"description": "Clase concreta que implementa el contrato.",
|
20
|
+
"default": None
|
21
|
+
}
|
22
|
+
)
|
23
|
+
|
24
|
+
instance: object = field(
|
25
|
+
default=None,
|
26
|
+
metadata={
|
27
|
+
"description": "Instancia concreta de la clase, si se proporciona.",
|
28
|
+
"default": None
|
29
|
+
}
|
30
|
+
)
|
31
|
+
|
32
|
+
function: callable = field(
|
33
|
+
default=None,
|
34
|
+
metadata={
|
35
|
+
"description": "Función que se invoca para crear la instancia.",
|
36
|
+
"default": None
|
37
|
+
}
|
38
|
+
)
|
39
|
+
|
40
|
+
lifetime: Lifetime = field(
|
41
|
+
default=Lifetime.TRANSIENT,
|
42
|
+
metadata={
|
43
|
+
"description": "Tiempo de vida de la instancia.",
|
44
|
+
"default": Lifetime.TRANSIENT
|
45
|
+
}
|
46
|
+
)
|
47
|
+
|
48
|
+
enforce_decoupling: bool = field(
|
49
|
+
default=False,
|
50
|
+
metadata={
|
51
|
+
"description": "Indica si se debe forzar el desacoplamiento entre contrato y concreta.",
|
52
|
+
"default": False
|
53
|
+
}
|
54
|
+
)
|
55
|
+
|
56
|
+
alias: str = field(
|
57
|
+
default=None,
|
58
|
+
metadata={
|
59
|
+
"description": "Alias para resolver la dependencia desde el contenedor.",
|
60
|
+
"default": None
|
61
|
+
}
|
62
|
+
)
|
63
|
+
|
64
|
+
def __post_init__(self):
|
65
|
+
"""
|
66
|
+
Performs type validation of instance attributes after initialization.
|
67
|
+
|
68
|
+
Parameters
|
69
|
+
----------
|
70
|
+
None
|
71
|
+
|
72
|
+
Raises
|
73
|
+
------
|
74
|
+
OrionisContainerTypeError
|
75
|
+
If 'lifetime' is not an instance of `Lifetime` (when not None).
|
76
|
+
OrionisContainerTypeError
|
77
|
+
If 'enforce_decoupling' is not of type `bool`.
|
78
|
+
OrionisContainerTypeError
|
79
|
+
If 'alias' is not of type `str` or `None`.
|
80
|
+
"""
|
81
|
+
if self.lifetime is not None and not isinstance(self.lifetime, Lifetime):
|
82
|
+
raise OrionisContainerTypeError(
|
83
|
+
f"The 'lifetime' attribute must be an instance of 'Lifetime', but received type '{type(self.lifetime).__name__}'."
|
84
|
+
)
|
85
|
+
|
86
|
+
if not isinstance(self.enforce_decoupling, bool):
|
87
|
+
raise OrionisContainerTypeError(
|
88
|
+
f"The 'enforce_decoupling' attribute must be of type 'bool', but received type '{type(self.enforce_decoupling).__name__}'."
|
89
|
+
)
|
90
|
+
|
91
|
+
if self.alias is not None and not isinstance(self.alias, str):
|
92
|
+
raise OrionisContainerTypeError(
|
93
|
+
f"The 'alias' attribute must be of type 'str' or 'None', but received type '{type(self.alias).__name__}'."
|
94
|
+
)
|
95
|
+
|
96
|
+
def toDict(self) -> dict:
|
97
|
+
"""
|
98
|
+
Convert the object to a dictionary representation.
|
99
|
+
Returns:
|
100
|
+
dict: A dictionary representation of the Dataclass object.
|
101
|
+
"""
|
102
|
+
return asdict(self)
|
103
|
+
|
104
|
+
def getFields(self):
|
105
|
+
"""
|
106
|
+
Retrieves a list of field information for the current dataclass instance.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
list: A list of dictionaries, each containing details about a field:
|
110
|
+
- name (str): The name of the field.
|
111
|
+
- type (type): The type of the field.
|
112
|
+
- default: The default value of the field, if specified; otherwise, the value from metadata or None.
|
113
|
+
- metadata (mapping): The metadata associated with the field.
|
114
|
+
"""
|
115
|
+
__fields = []
|
116
|
+
for field in fields(self):
|
117
|
+
__metadata = dict(field.metadata) or {}
|
118
|
+
__fields.append({
|
119
|
+
"name": field.name,
|
120
|
+
"type": field.type.__name__ if hasattr(field.type, '__name__') else str(field.type),
|
121
|
+
"default": field.default if (field.default is not None and '_MISSING_TYPE' not in str(field.default)) else __metadata.get('default', None),
|
122
|
+
"metadata": __metadata
|
123
|
+
})
|
124
|
+
return __fields
|
orionis/metadata/framework.py
CHANGED
File without changes
|
@@ -0,0 +1,147 @@
|
|
1
|
+
import inspect
|
2
|
+
from orionis.services.asynchrony.coroutines import Coroutine
|
3
|
+
from orionis.services.introspection.dependencies.entities.method_dependencies import MethodDependency as CallableDependency
|
4
|
+
from orionis.services.introspection.dependencies.reflect_dependencies import ReflectDependencies
|
5
|
+
from orionis.services.introspection.exceptions.reflection_attribute_error import ReflectionAttributeError
|
6
|
+
from orionis.services.introspection.exceptions.reflection_type_error import ReflectionTypeError
|
7
|
+
import asyncio
|
8
|
+
|
9
|
+
class ReflectionCallable:
|
10
|
+
|
11
|
+
def __init__(self, fn: callable) -> None:
|
12
|
+
"""
|
13
|
+
Parameters
|
14
|
+
----------
|
15
|
+
fn : callable
|
16
|
+
The function, method, or lambda to be wrapped.
|
17
|
+
Raises
|
18
|
+
------
|
19
|
+
ReflectionTypeError
|
20
|
+
If `fn` is not a function, method, or lambda.
|
21
|
+
Notes
|
22
|
+
-----
|
23
|
+
This constructor initializes the ReflectionCallable with the provided callable object.
|
24
|
+
It ensures that the input is a valid function, method, or lambda, and raises an error otherwise.
|
25
|
+
"""
|
26
|
+
if not (inspect.isfunction(fn) or inspect.ismethod(fn) or (callable(fn) and hasattr(fn, "__code__"))):
|
27
|
+
raise ReflectionTypeError(f"Expected a function, method, or lambda, got {type(fn).__name__}")
|
28
|
+
self.__function = fn
|
29
|
+
|
30
|
+
def getCallable(self) -> callable:
|
31
|
+
"""
|
32
|
+
Retrieve the callable function associated with this instance.
|
33
|
+
Returns
|
34
|
+
-------
|
35
|
+
callable
|
36
|
+
The function object encapsulated by this instance.
|
37
|
+
"""
|
38
|
+
return self.__function
|
39
|
+
|
40
|
+
def getName(self) -> str:
|
41
|
+
"""
|
42
|
+
Returns
|
43
|
+
-------
|
44
|
+
str
|
45
|
+
The name of the function.
|
46
|
+
"""
|
47
|
+
return self.__function.__name__
|
48
|
+
|
49
|
+
def getModuleName(self) -> str:
|
50
|
+
"""
|
51
|
+
Get the name of the module where the underlying function is defined.
|
52
|
+
Returns
|
53
|
+
-------
|
54
|
+
str
|
55
|
+
The name of the module in which the function was originally declared.
|
56
|
+
"""
|
57
|
+
return self.__function.__module__
|
58
|
+
|
59
|
+
def getModuleWithCallableName(self) -> str:
|
60
|
+
"""
|
61
|
+
Get the fully qualified name of the callable, including its module.
|
62
|
+
Returns
|
63
|
+
-------
|
64
|
+
str
|
65
|
+
A string consisting of the module name and the callable name, separated by a dot.
|
66
|
+
"""
|
67
|
+
return f"{self.getModuleName()}.{self.getName()}"
|
68
|
+
|
69
|
+
def getDocstring(self) -> str:
|
70
|
+
"""
|
71
|
+
Retrieve the docstring of the callable function.
|
72
|
+
Returns
|
73
|
+
-------
|
74
|
+
str
|
75
|
+
The docstring associated with the function. Returns an empty string if no docstring is present.
|
76
|
+
"""
|
77
|
+
return self.__function.__doc__ or ""
|
78
|
+
|
79
|
+
def getSourceCode(self) -> str:
|
80
|
+
"""
|
81
|
+
Retrieve the source code of the wrapped callable.
|
82
|
+
Returns
|
83
|
+
-------
|
84
|
+
str
|
85
|
+
The source code of the callable function as a string. If the source code
|
86
|
+
cannot be retrieved, a ReflectionAttributeError is raised.
|
87
|
+
Raises
|
88
|
+
------
|
89
|
+
ReflectionAttributeError
|
90
|
+
If the source code cannot be obtained due to an OSError.
|
91
|
+
"""
|
92
|
+
try:
|
93
|
+
return inspect.getsource(self.__function)
|
94
|
+
except OSError as e:
|
95
|
+
raise ReflectionAttributeError(f"Could not retrieve source code: {e}")
|
96
|
+
|
97
|
+
def getFile(self) -> str:
|
98
|
+
"""
|
99
|
+
Retrieve the filename where the underlying callable function is defined.
|
100
|
+
Returns
|
101
|
+
-------
|
102
|
+
str
|
103
|
+
The absolute path to the source file containing the callable.
|
104
|
+
Raises
|
105
|
+
------
|
106
|
+
TypeError
|
107
|
+
If the underlying object is a built-in function or method, or if its source file cannot be determined.
|
108
|
+
"""
|
109
|
+
return inspect.getfile(self.__function)
|
110
|
+
|
111
|
+
def call(self, *args, **kwargs):
|
112
|
+
"""
|
113
|
+
Call the wrapped function with the provided arguments.
|
114
|
+
If the wrapped function is asynchronous, it will be executed using `asyncio.run`.
|
115
|
+
Parameters
|
116
|
+
----------
|
117
|
+
*args : tuple
|
118
|
+
Positional arguments to pass to the function.
|
119
|
+
**kwargs : dict
|
120
|
+
Keyword arguments to pass to the function.
|
121
|
+
Returns
|
122
|
+
-------
|
123
|
+
Any
|
124
|
+
The result returned by the function call.
|
125
|
+
Raises
|
126
|
+
------
|
127
|
+
Exception
|
128
|
+
Propagates any exception raised by the called function.
|
129
|
+
"""
|
130
|
+
if inspect.iscoroutinefunction(self.__function):
|
131
|
+
return Coroutine(self.__function(*args, **kwargs)).run()
|
132
|
+
return self.__function(*args, **kwargs)
|
133
|
+
|
134
|
+
def getDependencies(self) -> CallableDependency:
|
135
|
+
"""
|
136
|
+
Analyzes the callable associated with this instance and retrieves its dependencies.
|
137
|
+
CallableDependency
|
138
|
+
An object containing information about the callable's dependencies, including:
|
139
|
+
- resolved: dict
|
140
|
+
A dictionary mapping parameter names to their resolved values (e.g., default values or injected dependencies).
|
141
|
+
- unresolved: list of str
|
142
|
+
A list of parameter names that could not be resolved (i.e., parameters without default values or missing annotations).
|
143
|
+
Notes
|
144
|
+
-----
|
145
|
+
This method leverages the `ReflectDependencies` utility to inspect the callable and determine which dependencies are satisfied and which remain unresolved.
|
146
|
+
"""
|
147
|
+
return ReflectDependencies().getCallableDependencies(self.__function)
|
@@ -11,16 +11,16 @@ class ReflectDependencies(IReflectDependencies):
|
|
11
11
|
This class is used to reflect dependencies of a given object.
|
12
12
|
"""
|
13
13
|
|
14
|
-
def __init__(self,
|
14
|
+
def __init__(self, target = None):
|
15
15
|
"""
|
16
16
|
Initializes the ReflectDependencies instance with the given object.
|
17
17
|
|
18
18
|
Parameters
|
19
19
|
----------
|
20
|
-
|
20
|
+
target : Any
|
21
21
|
The object whose dependencies are to be reflected.
|
22
22
|
"""
|
23
|
-
self.
|
23
|
+
self.__target = target
|
24
24
|
|
25
25
|
def __paramSkip(self, param_name: str, param: inspect.Parameter) -> bool:
|
26
26
|
"""
|
@@ -43,7 +43,7 @@ class ReflectDependencies(IReflectDependencies):
|
|
43
43
|
return True
|
44
44
|
|
45
45
|
# Skip 'self' in class methods or instance methods
|
46
|
-
if param_name == 'self' and isinstance(self.
|
46
|
+
if param_name == 'self' and isinstance(self.__target, type):
|
47
47
|
return True
|
48
48
|
|
49
49
|
# Skip special parameters like *args and **kwargs
|
@@ -90,7 +90,7 @@ class ReflectDependencies(IReflectDependencies):
|
|
90
90
|
- resolved: Dictionary of resolved dependencies with their names and values.
|
91
91
|
- unresolved: List of unresolved dependencies (parameter names without default values or annotations).
|
92
92
|
"""
|
93
|
-
signature = self.__inspectSignature(self.
|
93
|
+
signature = self.__inspectSignature(self.__target.__init__)
|
94
94
|
resolved_dependencies: Dict[str, Any] = {}
|
95
95
|
unresolved_dependencies: List[str] = []
|
96
96
|
|
@@ -141,7 +141,58 @@ class ReflectDependencies(IReflectDependencies):
|
|
141
141
|
- resolved: Dictionary of resolved dependencies with their names and values.
|
142
142
|
- unresolved: List of unresolved dependencies (parameter names without default values or annotations).
|
143
143
|
"""
|
144
|
-
signature = self.__inspectSignature(getattr(self.
|
144
|
+
signature = self.__inspectSignature(getattr(self.__target, method_name))
|
145
|
+
resolved_dependencies: Dict[str, Any] = {}
|
146
|
+
unresolved_dependencies: List[str] = []
|
147
|
+
|
148
|
+
for param_name, param in signature.parameters.items():
|
149
|
+
|
150
|
+
# Skip parameters that are not relevant for dependency resolution
|
151
|
+
if self.__paramSkip(param_name, param):
|
152
|
+
continue
|
153
|
+
|
154
|
+
# Add to the list of unresolved dependencies if it has no default value or annotation
|
155
|
+
if param.annotation is param.empty and param.default is param.empty:
|
156
|
+
unresolved_dependencies.append(param_name)
|
157
|
+
continue
|
158
|
+
|
159
|
+
# Parameters with default values
|
160
|
+
if param.default is not param.empty:
|
161
|
+
resolved_dependencies[param_name] = param.default
|
162
|
+
continue
|
163
|
+
|
164
|
+
# If the parameter has an annotation, it is added to the list of resolved dependencies
|
165
|
+
if param.annotation is not param.empty:
|
166
|
+
module_path = param.annotation.__module__
|
167
|
+
resolved_dependencies[param_name] = ResolvedDependency(
|
168
|
+
module_name=module_path,
|
169
|
+
class_name=param.annotation.__name__,
|
170
|
+
type=param.annotation,
|
171
|
+
full_class_path=f"{module_path}.{param.annotation.__name__}"
|
172
|
+
)
|
173
|
+
|
174
|
+
return MethodDependency(
|
175
|
+
resolved=resolved_dependencies,
|
176
|
+
unresolved=unresolved_dependencies
|
177
|
+
)
|
178
|
+
|
179
|
+
def getCallableDependencies(self, fn: callable) -> MethodDependency:
|
180
|
+
"""
|
181
|
+
Get the resolved and unresolved dependencies from a callable function.
|
182
|
+
|
183
|
+
Parameters
|
184
|
+
----------
|
185
|
+
fn : callable
|
186
|
+
The function to inspect.
|
187
|
+
|
188
|
+
Returns
|
189
|
+
-------
|
190
|
+
MethodDependency
|
191
|
+
A structured representation of the callable dependencies, containing:
|
192
|
+
- resolved: Dictionary of resolved dependencies with their names and values.
|
193
|
+
- unresolved: List of unresolved dependencies (parameter names without default values or annotations).
|
194
|
+
"""
|
195
|
+
signature = inspect.signature(fn)
|
145
196
|
resolved_dependencies: Dict[str, Any] = {}
|
146
197
|
unresolved_dependencies: List[str] = []
|
147
198
|
|
@@ -135,7 +135,8 @@ orionis/console/output/console.py,sha256=TE_Hl720ADd82dbERFSWhkoQRukDQZmETSw4nkw
|
|
135
135
|
orionis/console/output/executor.py,sha256=bdvkzW2-buy0BPpy2r5qUGrRFW2Ay6k-5rSeHb0gQ3o,3352
|
136
136
|
orionis/console/output/progress_bar.py,sha256=vFy582z6VJS46LV6tuyrmr9qvdVeTEtw3hyNcEHezeg,3088
|
137
137
|
orionis/console/output/styles.py,sha256=6a4oQCOBOKMh2ARdeq5GlIskJ3wjiylYmh66tUKKmpQ,4053
|
138
|
-
orionis/container/container.py,sha256=
|
138
|
+
orionis/container/container.py,sha256=BPBO0i1EgL-9g9sgDotrInM2UiuDMT7TNDRHDdrQCZo,30121
|
139
|
+
orionis/container/entities/binding.py,sha256=Qp6Lf4XUDp2NjqXDAC2lzvhOFQWiBDKiGFcKfwb4axw,4342
|
139
140
|
orionis/container/enums/lifetimes.py,sha256=RqQmugMIB1Ev_j_vFLcWorndm-to7xg4stQ7yKFDdDw,190
|
140
141
|
orionis/container/exceptions/container_exception.py,sha256=goTDEwC70xTMD2qppN8KV-xyR0Nps218OD4D1LZ2-3s,470
|
141
142
|
orionis/container/exceptions/type_error_exception.py,sha256=cYuvoXVOgRYj3tZPfK341aUERkf33-buOiI2eXxcrAw,470
|
@@ -231,7 +232,7 @@ orionis/foundation/contracts/config.py,sha256=Rpz6U6t8OXHO9JJKSTnCimytXE-tfCB-1i
|
|
231
232
|
orionis/foundation/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
232
233
|
orionis/foundation/exceptions/integrity.py,sha256=mc4pL1UMoYRHEmphnpW2oGk5URhu7DJRREyzHaV-cs8,472
|
233
234
|
orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
234
|
-
orionis/metadata/framework.py,sha256=
|
235
|
+
orionis/metadata/framework.py,sha256=IhaXribsZwgUQ1ZD4sXOCEanSM6ZfeiOASiDyOpzViA,4960
|
235
236
|
orionis/metadata/package.py,sha256=tqLfBRo-w1j_GN4xvzUNFyweWYFS-qhSgAEc-AmCH1M,5452
|
236
237
|
orionis/patterns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
237
238
|
orionis/patterns/singleton/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -257,6 +258,8 @@ orionis/services/introspection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
257
258
|
orionis/services/introspection/reflection.py,sha256=6z4VkDICohMIkm9jEd7nmFABwVuU7SBoTFaH3tq4PGk,10897
|
258
259
|
orionis/services/introspection/abstract/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
259
260
|
orionis/services/introspection/abstract/reflection_abstract.py,sha256=SPK2X11VvGORxxPOYloaD6hPAvky--obRU4CO1DE4zM,43865
|
261
|
+
orionis/services/introspection/callables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
262
|
+
orionis/services/introspection/callables/reflection_callable.py,sha256=AWTw9T9DGpCscwvrqYtYAYdTeoJqIHzdRDS29Sb5cSI,5667
|
260
263
|
orionis/services/introspection/concretes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
261
264
|
orionis/services/introspection/concretes/reflection_concrete.py,sha256=1GxD-y8LPGL6kI4Y3XbeLcFjR5Y8cOqbVEPCtPnsuYA,50982
|
262
265
|
orionis/services/introspection/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -266,7 +269,7 @@ orionis/services/introspection/contracts/reflection_concrete.py,sha256=9ZQjJpZwv
|
|
266
269
|
orionis/services/introspection/contracts/reflection_instance.py,sha256=D9sH-uOSZ_E7luAfbjI_ML6kfxuO5MtvLk6037iQJ7o,20936
|
267
270
|
orionis/services/introspection/contracts/reflection_module.py,sha256=YLqKg5EhaddUBrytMHX1-uz9mNsRISK1iVyG_iUiVYA,9666
|
268
271
|
orionis/services/introspection/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
269
|
-
orionis/services/introspection/dependencies/reflect_dependencies.py,sha256=
|
272
|
+
orionis/services/introspection/dependencies/reflect_dependencies.py,sha256=xjY9RRBbALfifoefUuHu2EU5XUNdV9zKFaJ9-x7TP2I,9303
|
270
273
|
orionis/services/introspection/dependencies/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
271
274
|
orionis/services/introspection/dependencies/entities/class_dependencies.py,sha256=pALvV_duAvDYmNp7PJYWkpIIQYmqWxuc_RGruEckfPA,2063
|
272
275
|
orionis/services/introspection/dependencies/entities/method_dependencies.py,sha256=FDwroILMPhqPxaxisPVEeKeLUg57GNQ4tQfWjGMh40E,2194
|
@@ -340,7 +343,7 @@ orionis/test/suite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
340
343
|
orionis/test/suite/test_unit.py,sha256=MWgW8dRCRyT1XZ5LsbXQ7-KVPReasoXwzEEL1EWWfE4,52190
|
341
344
|
orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
342
345
|
orionis/test/view/render.py,sha256=jXZkbITBknbUwm_mD8bcTiwLDvsFkrO9qrf0ZgPwqxc,4903
|
343
|
-
orionis-0.
|
346
|
+
orionis-0.317.0.dist-info/licenses/LICENCE,sha256=-_4cF2EBKuYVS_SQpy1uapq0oJPUU1vl_RUWSy2jJTo,1111
|
344
347
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
345
348
|
tests/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
346
349
|
tests/example/test_example.py,sha256=kvWgiW3ADEZf718dGsMPtDh_rmOSx1ypEInKm7_6ZPQ,601
|
@@ -411,13 +414,14 @@ tests/services/environment/test_services_environment.py,sha256=fdkjwbY-aDEA1FT-9
|
|
411
414
|
tests/services/inspection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
412
415
|
tests/services/inspection/test_reflection.py,sha256=ZApQeaDxYLsrfGN6UqEDPbyNzocMV9CURflQ35YMfqk,13678
|
413
416
|
tests/services/inspection/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
414
|
-
tests/services/inspection/dependencies/test_reflect_dependencies.py,sha256=
|
417
|
+
tests/services/inspection/dependencies/test_reflect_dependencies.py,sha256=xjcbfWlCT-QuI0wDgXbbsfvWG5phvrvPcfMOwmUnpHQ,7279
|
415
418
|
tests/services/inspection/dependencies/mocks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
416
419
|
tests/services/inspection/dependencies/mocks/mock_user.py,sha256=RxATxe0-Vm4HfX5jKz9Tny42E2fmrdtEN6ZEntbqRL8,912
|
417
420
|
tests/services/inspection/dependencies/mocks/mock_user_controller.py,sha256=P3sOUXVZ55auudwiNtvNCEQuTz0cgAZjvhicLZ4xaz4,1208
|
418
421
|
tests/services/inspection/dependencies/mocks/mock_users_permissions.py,sha256=oENXbS2qmQUudYSmnhB8fgHBqXZdbplplB-Y2nbx4hw,1388
|
419
422
|
tests/services/inspection/reflection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
420
423
|
tests/services/inspection/reflection/test_reflection_abstract.py,sha256=MbGDfCDkfj8wVJcyAlwV6na98JLzbGaYa3QjXR1alL8,27395
|
424
|
+
tests/services/inspection/reflection/test_reflection_callable.py,sha256=GBZL9oIwfBDfhQ3VXVrZDkdwoTkymMqLfFQSBCSs8pE,7021
|
421
425
|
tests/services/inspection/reflection/test_reflection_concrete.py,sha256=5-iQh1whfpBa47jBWwtg-MIk6ysg92my5J9JdrTBm5E,44622
|
422
426
|
tests/services/inspection/reflection/test_reflection_instance.py,sha256=ZCFTLY_KtLAIq58PuDWak-T1c2PcCKiwTOdI9EDubww,46281
|
423
427
|
tests/services/inspection/reflection/test_reflection_module.py,sha256=Cl-3kWoJMQ2ufOO4VP6M28Tk6kmY4OhVEoW_b0wqw7Y,19849
|
@@ -440,8 +444,8 @@ tests/support/wrapper/test_services_wrapper_docdict.py,sha256=yeVwl-VcwkWSQYyxZu
|
|
440
444
|
tests/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
441
445
|
tests/testing/test_testing_result.py,sha256=MrGK3ZimedL0b5Ydu69Dg8Iul017AzLTm7VPxpXlpfU,4315
|
442
446
|
tests/testing/test_testing_unit.py,sha256=DjLBtvVn8B1KlVJNNkstBT8_csA1yeaMqnGrbanN_J4,7438
|
443
|
-
orionis-0.
|
444
|
-
orionis-0.
|
445
|
-
orionis-0.
|
446
|
-
orionis-0.
|
447
|
-
orionis-0.
|
447
|
+
orionis-0.317.0.dist-info/METADATA,sha256=ArEDpHbIszs0kFP63nOxlOuuEJygJsbjqGR8xCRQ9gM,4772
|
448
|
+
orionis-0.317.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
449
|
+
orionis-0.317.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
|
450
|
+
orionis-0.317.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
451
|
+
orionis-0.317.0.dist-info/RECORD,,
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import asyncio
|
1
2
|
from orionis.services.introspection.dependencies.reflect_dependencies import (
|
2
3
|
ReflectDependencies,
|
3
4
|
ClassDependency,
|
@@ -91,4 +92,36 @@ class TestReflectDependencies(TestCase):
|
|
91
92
|
self.assertEqual(dep_permissions.module_name, 'builtins')
|
92
93
|
self.assertEqual(dep_permissions.class_name, 'list')
|
93
94
|
self.assertEqual(dep_permissions.full_class_path, 'builtins.list')
|
94
|
-
self.assertEqual(dep_permissions.type, list[str])
|
95
|
+
self.assertEqual(dep_permissions.type, list[str])
|
96
|
+
|
97
|
+
async def testReflectionDependenciesGetCallableDependencies(self):
|
98
|
+
"""
|
99
|
+
Tests the `getCallableDependencies` method of the `ReflectDependencies` class for a callable function.
|
100
|
+
This test verifies:
|
101
|
+
- The returned dependencies are an instance of `MethodDependency`.
|
102
|
+
- There are no unresolved dependencies.
|
103
|
+
- The 'x' and 'y' parameters are correctly resolved as instances of `ResolvedDependency` with the expected
|
104
|
+
module name, class name, full class path, and type (`int`).
|
105
|
+
"""
|
106
|
+
|
107
|
+
async def fake_function(x: int = 3, y: int = 4) -> int:
|
108
|
+
"""Asynchronously adds two integers with a short delay."""
|
109
|
+
await asyncio.sleep(0.1)
|
110
|
+
return x + y
|
111
|
+
|
112
|
+
depend = ReflectDependencies()
|
113
|
+
callable_dependencies = depend.getCallableDependencies(fake_function)
|
114
|
+
|
115
|
+
# Check Instance of MethodDependency
|
116
|
+
self.assertIsInstance(callable_dependencies, MethodDependency)
|
117
|
+
|
118
|
+
# Check unresolved dependencies
|
119
|
+
self.assertEqual(callable_dependencies.unresolved, [])
|
120
|
+
|
121
|
+
# Check Instance of ResolvedDependency for 'x'
|
122
|
+
dep_x:ResolvedDependency = callable_dependencies.resolved.get('x')
|
123
|
+
self.assertEqual(dep_x, 3)
|
124
|
+
|
125
|
+
# Check Instance of ResolvedDependency for 'y'
|
126
|
+
dep_y:ResolvedDependency = callable_dependencies.resolved.get('y')
|
127
|
+
self.assertEqual(dep_y, 4)
|
@@ -0,0 +1,157 @@
|
|
1
|
+
from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
|
2
|
+
from orionis.unittesting import TestCase
|
3
|
+
from orionis.services.introspection.exceptions.reflection_type_error import ReflectionTypeError
|
4
|
+
from orionis.services.introspection.exceptions.reflection_attribute_error import ReflectionAttributeError
|
5
|
+
from orionis.services.introspection.dependencies.entities.method_dependencies import MethodDependency as CallableDependency
|
6
|
+
from orionis.services.introspection.dependencies.reflect_dependencies import ReflectDependencies
|
7
|
+
|
8
|
+
class TestReflectionCallable(TestCase):
|
9
|
+
|
10
|
+
async def testInitValidFunction(self):
|
11
|
+
"""
|
12
|
+
Tests that a ReflectionCallable object can be correctly initialized with a valid function.
|
13
|
+
Verifies that the callable stored in the ReflectionCallable instance matches the original function.
|
14
|
+
"""
|
15
|
+
def sample_function(a, b=2):
|
16
|
+
"""Sample docstring."""
|
17
|
+
return a + b
|
18
|
+
rc = ReflectionCallable(sample_function)
|
19
|
+
self.assertEqual(rc.getCallable(), sample_function)
|
20
|
+
|
21
|
+
async def testInitInvalid(self):
|
22
|
+
"""
|
23
|
+
Test that initializing ReflectionCallable with an invalid argument (e.g., an integer)
|
24
|
+
raises a ReflectionTypeError.
|
25
|
+
"""
|
26
|
+
with self.assertRaises(ReflectionTypeError):
|
27
|
+
ReflectionCallable(123)
|
28
|
+
|
29
|
+
async def testGetName(self):
|
30
|
+
"""
|
31
|
+
Tests that the ReflectionCallable.getName() method correctly returns the name of the provided function.
|
32
|
+
|
33
|
+
This test defines a sample function, wraps it with ReflectionCallable, and asserts that getName()
|
34
|
+
returns the function's name as a string.
|
35
|
+
"""
|
36
|
+
def sample_function(a, b=2):
|
37
|
+
"""Sample docstring."""
|
38
|
+
return a + b
|
39
|
+
rc = ReflectionCallable(sample_function)
|
40
|
+
self.assertEqual(rc.getName(), "sample_function")
|
41
|
+
|
42
|
+
async def testGetModuleName(self):
|
43
|
+
"""
|
44
|
+
Tests that the getModuleName method of ReflectionCallable returns the correct module name
|
45
|
+
for a given function. It verifies that the module name returned matches the __module__ attribute
|
46
|
+
of the sample function.
|
47
|
+
"""
|
48
|
+
def sample_function(a, b=2):
|
49
|
+
"""Sample docstring."""
|
50
|
+
return a + b
|
51
|
+
rc = ReflectionCallable(sample_function)
|
52
|
+
self.assertEqual(rc.getModuleName(), sample_function.__module__)
|
53
|
+
|
54
|
+
async def testGetModuleWithCallableName(self):
|
55
|
+
"""
|
56
|
+
Tests that the `getModuleWithCallableName` method of the `ReflectionCallable` class
|
57
|
+
correctly returns the fully qualified name of a given callable, including its module
|
58
|
+
and function name.
|
59
|
+
|
60
|
+
The test defines a sample function, wraps it with `ReflectionCallable`, and asserts
|
61
|
+
that the returned string matches the expected format: "<module>.<function_name>".
|
62
|
+
"""
|
63
|
+
def sample_function(a, b=2):
|
64
|
+
"""Sample docstring."""
|
65
|
+
return a + b
|
66
|
+
rc = ReflectionCallable(sample_function)
|
67
|
+
expected = f"{sample_function.__module__}.sample_function"
|
68
|
+
self.assertEqual(rc.getModuleWithCallableName(), expected)
|
69
|
+
|
70
|
+
async def testGetDocstring(self):
|
71
|
+
"""
|
72
|
+
Tests that the getDocstring method of ReflectionCallable correctly retrieves the docstring from a given function.
|
73
|
+
"""
|
74
|
+
def sample_function(a, b=2):
|
75
|
+
"""Sample docstring."""
|
76
|
+
return a + b
|
77
|
+
rc = ReflectionCallable(sample_function)
|
78
|
+
self.assertIn("Sample docstring", rc.getDocstring())
|
79
|
+
|
80
|
+
async def testGetSourceCode(self):
|
81
|
+
"""
|
82
|
+
Tests that the getSourceCode method of ReflectionCallable correctly retrieves
|
83
|
+
the source code of a given function. Verifies that the returned code contains
|
84
|
+
the function definition for 'sample_function'.
|
85
|
+
"""
|
86
|
+
def sample_function(a, b=2):
|
87
|
+
"""Sample docstring."""
|
88
|
+
return a + b
|
89
|
+
rc = ReflectionCallable(sample_function)
|
90
|
+
code = rc.getSourceCode()
|
91
|
+
self.assertIn("def sample_function", code)
|
92
|
+
|
93
|
+
async def testGetSourceCodeError(self):
|
94
|
+
"""
|
95
|
+
Test that ReflectionCallable.getSourceCode() raises a ReflectionTypeError
|
96
|
+
when called on a built-in function (e.g., len) that does not have accessible source code.
|
97
|
+
"""
|
98
|
+
with self.assertRaises(ReflectionTypeError):
|
99
|
+
rc = ReflectionCallable(len)
|
100
|
+
rc.getSourceCode()
|
101
|
+
|
102
|
+
async def testGetFile(self):
|
103
|
+
"""
|
104
|
+
Tests that the getFile() method of the ReflectionCallable class returns the correct file path
|
105
|
+
for a given callable. Verifies that the returned file path ends with '.py', indicating it is a
|
106
|
+
Python source file.
|
107
|
+
"""
|
108
|
+
def sample_function(a, b=2):
|
109
|
+
"""Sample docstring."""
|
110
|
+
return a + b
|
111
|
+
rc = ReflectionCallable(sample_function)
|
112
|
+
file_path = rc.getFile()
|
113
|
+
self.assertTrue(file_path.endswith(".py"))
|
114
|
+
|
115
|
+
async def testCallSync(self):
|
116
|
+
"""
|
117
|
+
Tests the synchronous call functionality of the ReflectionCallable class.
|
118
|
+
|
119
|
+
This test defines a sample function with one required and one optional argument,
|
120
|
+
wraps it with ReflectionCallable, and asserts that calling it with specific arguments
|
121
|
+
returns the expected result.
|
122
|
+
"""
|
123
|
+
def sample_function(a, b=2):
|
124
|
+
"""Sample docstring."""
|
125
|
+
return a + b
|
126
|
+
rc = ReflectionCallable(sample_function)
|
127
|
+
self.assertEqual(rc.call(1, 2), 3)
|
128
|
+
|
129
|
+
async def testCallAsync(self):
|
130
|
+
"""
|
131
|
+
Tests the ReflectionCallable's ability to call an asynchronous function synchronously.
|
132
|
+
|
133
|
+
This test defines an asynchronous function `sample_async_function` that takes two arguments and returns their sum.
|
134
|
+
It then wraps this function with `ReflectionCallable` and asserts that calling it with arguments (1, 2) returns 3.
|
135
|
+
"""
|
136
|
+
async def sample_async_function(a, b=2):
|
137
|
+
"""Async docstring."""
|
138
|
+
return a + b
|
139
|
+
rc = ReflectionCallable(sample_async_function)
|
140
|
+
self.assertEqual(await rc.call(1, 2), 3)
|
141
|
+
|
142
|
+
async def testGetDependencies(self):
|
143
|
+
"""
|
144
|
+
Tests the getDependencies method of the ReflectionCallable class.
|
145
|
+
|
146
|
+
This test defines a sample function with one required and one default argument,
|
147
|
+
creates a ReflectionCallable instance for it, and retrieves its dependencies.
|
148
|
+
It then asserts that the returned dependencies object has both 'resolved' and
|
149
|
+
'unresolved' attributes.
|
150
|
+
"""
|
151
|
+
def sample_function(a, b=2):
|
152
|
+
"""Sample docstring."""
|
153
|
+
return a + b
|
154
|
+
rc = ReflectionCallable(sample_function)
|
155
|
+
deps = rc.getDependencies()
|
156
|
+
self.assertTrue(hasattr(deps, "resolved"))
|
157
|
+
self.assertTrue(hasattr(deps, "unresolved"))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|