orionis 0.316.0__py3-none-any.whl → 0.318.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.
@@ -1,21 +1,146 @@
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 threading
9
12
 
10
13
  class Container:
11
14
 
15
+ # Singleton instance of the container.
16
+ # This is a class variable that holds the single instance of the Container class.
17
+ _instance = None
18
+
19
+ # Lock for thread-safe singleton instantiation.
20
+ # This lock ensures that only one thread can create the instance at a time,
21
+ # preventing
22
+ _lock = threading.Lock()
23
+
24
+ # Class variable to track if the container has been initialized.
25
+ # This is used to ensure that the initialization logic runs only once,
26
+ # regardless of how many times the class is instantiated.
27
+ _initialized = False
28
+
29
+ def __new__(cls, *args, **kwargs):
30
+ """
31
+ Creates and returns a singleton instance of the class.
32
+
33
+ This method implements the singleton pattern, ensuring that only one instance
34
+ of the class exists. If an instance does not exist, it acquires a lock to
35
+ ensure thread safety and creates the instance. Subsequent calls return the
36
+ existing instance.
37
+ Parameters
38
+ ----------
39
+ *args : tuple
40
+ Variable length argument list.
41
+ **kwargs : dict
42
+ Arbitrary keyword arguments.
43
+ Returns
44
+ -------
45
+ Container
46
+ The singleton instance of the class.
47
+ """
48
+ if cls._instance is None:
49
+ with cls._lock:
50
+ if cls._instance is None:
51
+ cls._instance = super(Container, cls).__new__(cls)
52
+ return cls._instance
53
+
12
54
  def __init__(self):
13
- self.__transient = {}
14
- self.__singleton = {}
15
- self.__scoped = {}
16
- self.__instance = {}
55
+ """
56
+ Initializes a new instance of the container.
57
+
58
+ This constructor sets up the internal dictionaries for bindings and aliases,
59
+ ensuring that these are only initialized once per class. The initialization
60
+ is guarded by the `_initialized` class attribute to prevent redundant setup.
61
+
62
+ Notes
63
+ -----
64
+ - The `__bindings` dictionary is used to store service bindings.
65
+ - The `__aliasses` dictionary is used to store service aliases.
66
+ - Initialization occurs only once per class, regardless of the number of instances.
67
+ """
68
+ if not self.__class__._initialized:
69
+ self.__bindings = {}
70
+ self.__aliasses = {}
71
+ self.__class__._initialized = True
72
+
73
+ def __dropService(
74
+ self,
75
+ abstract: Callable[..., Any] = None,
76
+ alias: str = None
77
+ ) -> None:
78
+ """
79
+ Drops a service from the container.
80
+
81
+ Parameters
82
+ ----------
83
+ abstract : Callable[..., Any]
84
+ The abstract type or interface to be removed.
85
+ alias : str, optional
86
+ The alias of the service to be removed. If not provided, the service will be removed by its abstract type.
87
+
88
+ Raises
89
+ ------
90
+ OrionisContainerException
91
+ If the service does not exist in the container.
92
+ """
93
+
94
+ # If abstract is provided
95
+ if abstract:
96
+
97
+ # Remove the abstract service from the bindings if it exists
98
+ if abstract in self.__bindings:
99
+ del self.__bindings[abstract]
100
+
101
+ # Remove the default alias (module + class name) from aliases if it exists
102
+ abs_alias = ReflectionAbstract(abstract).getModuleWithClassName()
103
+ if abs_alias in self.__aliasses:
104
+ del self.__aliasses[abs_alias]
17
105
 
18
- def __ensureAliasType(self, value: Any) -> None:
106
+ # If a custom alias is provided
107
+ if alias:
108
+
109
+ # Remove it from the aliases dictionary if it exists
110
+ if alias in self.__aliasses:
111
+ del self.__aliasses[alias]
112
+
113
+ # Remove the binding associated with the alias
114
+ if alias in self.__bindings:
115
+ del self.__bindings[alias]
116
+
117
+ def __ensureIsCallable(
118
+ self,
119
+ value: Any
120
+ ) -> None:
121
+ """
122
+ Ensures that the provided value is callable.
123
+
124
+ Parameters
125
+ ----------
126
+ value : Any
127
+ The value to check.
128
+
129
+ Raises
130
+ ------
131
+ OrionisContainerTypeError
132
+ If the value is not callable.
133
+ """
134
+
135
+ if not callable(value):
136
+ raise OrionisContainerTypeError(
137
+ f"Expected a callable type, but got {type(value).__name__} instead."
138
+ )
139
+
140
+ def __ensureAliasType(
141
+ self,
142
+ value: Any
143
+ ) -> None:
19
144
  """
20
145
  Ensures that the provided value is a valid alias of type str and does not contain invalid characters.
21
146
 
@@ -49,7 +174,11 @@ class Container:
49
174
  "Aliases must not contain whitespace or special symbols."
50
175
  )
51
176
 
52
- def __ensureAbstractClass(self, abstract: Callable[..., Any], lifetime: str) -> None:
177
+ def __ensureAbstractClass(
178
+ self,
179
+ abstract: Callable[..., Any],
180
+ lifetime: str
181
+ ) -> None:
53
182
  """
54
183
  Ensures that the provided abstract is an abstract class.
55
184
 
@@ -72,7 +201,11 @@ class Container:
72
201
  f"Unexpected error registering {lifetime} service: {e}"
73
202
  ) from e
74
203
 
75
- def __ensureConcreteClass(self, concrete: Callable[..., Any], lifetime: str) -> None:
204
+ def __ensureConcreteClass(
205
+ self,
206
+ concrete: Callable[..., Any],
207
+ lifetime: str
208
+ ) -> None:
76
209
  """
77
210
  Ensures that the provided concrete is a concrete (non-abstract) class.
78
211
 
@@ -95,7 +228,11 @@ class Container:
95
228
  f"Unexpected error registering {lifetime} service: {e}"
96
229
  ) from e
97
230
 
98
- def __ensureIsSubclass(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
231
+ def __ensureIsSubclass(
232
+ self,
233
+ abstract: Callable[..., Any],
234
+ concrete: Callable[..., Any]
235
+ ) -> None:
99
236
  """
100
237
  Validates that the concrete class is a subclass of the provided abstract class.
101
238
 
@@ -122,7 +259,11 @@ class Container:
122
259
  "Please ensure that the concrete class is a subclass of the specified abstract class."
123
260
  )
124
261
 
125
- def __ensureIsNotSubclass(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
262
+ def __ensureIsNotSubclass(
263
+ self,
264
+ abstract: Callable[..., Any],
265
+ concrete: Callable[..., Any]
266
+ ) -> None:
126
267
  """
127
268
  Validates that the concrete class is NOT a subclass of the provided abstract class.
128
269
 
@@ -148,7 +289,10 @@ class Container:
148
289
  "Please ensure that the concrete class is not a subclass of the specified abstract class."
149
290
  )
150
291
 
151
- def __ensureInstance(self, instance: Any) -> None:
292
+ def __ensureInstance(
293
+ self,
294
+ instance: Any
295
+ ) -> None:
152
296
  """
153
297
  Ensures that the provided object is a valid instance.
154
298
 
@@ -175,7 +319,13 @@ class Container:
175
319
  f"Error registering instance: {e}"
176
320
  ) from e
177
321
 
178
- def __ensureImplementation(self, *, abstract: Callable[..., Any] = None, concrete: Callable[..., Any] = None, instance: Any = None) -> None:
322
+ def __ensureImplementation(
323
+ self,
324
+ *,
325
+ abstract: Callable[..., Any] = None,
326
+ concrete: Callable[..., Any] = None,
327
+ instance: Any = None
328
+ ) -> None:
179
329
  """
180
330
  Ensures that a concrete class or instance implements all abstract methods defined in an abstract class.
181
331
 
@@ -229,7 +379,14 @@ class Container:
229
379
  "Please ensure that all abstract methods are implemented."
230
380
  )
231
381
 
232
- def transient(self, abstract: Callable[..., Any], concrete: Callable[..., Any], alias: str = None, enforce_decoupling: bool = False) -> bool:
382
+ def transient(
383
+ self,
384
+ abstract: Callable[..., Any],
385
+ concrete: Callable[..., Any],
386
+ *,
387
+ alias: str = None,
388
+ enforce_decoupling: bool = False
389
+ ) -> bool:
233
390
  """
234
391
  Registers a service with a transient lifetime.
235
392
 
@@ -267,11 +424,12 @@ class Container:
267
424
  # Ensure that concrete is a concrete class
268
425
  self.__ensureConcreteClass(concrete, Lifetime.TRANSIENT)
269
426
 
427
+ # Ensure that concrete is NOT a subclass of abstract
270
428
  if enforce_decoupling:
271
- # Ensure that concrete is NOT a subclass of abstract
272
429
  self.__ensureIsNotSubclass(abstract, concrete)
430
+
431
+ # Validate that concrete is a subclass of abstract
273
432
  else:
274
- # Validate that concrete is a subclass of abstract
275
433
  self.__ensureIsSubclass(abstract, concrete)
276
434
 
277
435
  # Ensure implementation
@@ -284,20 +442,37 @@ class Container:
284
442
  if alias:
285
443
  self.__ensureAliasType(alias)
286
444
 
445
+ # Extract the module and class name for the alias
446
+ else:
447
+ rf_asbtract = ReflectionAbstract(abstract)
448
+ alias = rf_asbtract.getModuleWithClassName()
449
+
450
+ # If the service is already registered, drop it
451
+ self.__dropService(abstract, alias)
452
+
287
453
  # Register the service with transient lifetime
288
- self.__transient[abstract] = concrete
454
+ self.__bindings[abstract] = Binding(
455
+ contract = abstract,
456
+ concrete = concrete,
457
+ lifetime = Lifetime.TRANSIENT,
458
+ enforce_decoupling = enforce_decoupling,
459
+ alias = alias
460
+ )
289
461
 
290
- # If an alias is provided, register it as well
291
- if alias:
292
- self.__transient[alias] = concrete
293
- elif hasattr(abstract, '__name__'):
294
- alias = abstract.__name__
295
- self.__transient[alias] = concrete
462
+ # Register the alias
463
+ self.__aliasses[alias] = self.__bindings[abstract]
296
464
 
297
465
  # Return True to indicate successful registration
298
466
  return True
299
467
 
300
- def singleton(self, abstract: Callable[..., Any], concrete: Callable[..., Any], alias: str = None, enforce_decoupling: bool = False) -> bool:
468
+ def singleton(
469
+ self,
470
+ abstract: Callable[..., Any],
471
+ concrete: Callable[..., Any],
472
+ *,
473
+ alias: str = None,
474
+ enforce_decoupling: bool = False
475
+ ) -> bool:
301
476
  """
302
477
  Registers a service with a singleton lifetime.
303
478
 
@@ -334,11 +509,12 @@ class Container:
334
509
  # Ensure that concrete is a concrete class
335
510
  self.__ensureConcreteClass(concrete, Lifetime.SINGLETON)
336
511
 
512
+ # Ensure that concrete is NOT a subclass of abstract
337
513
  if enforce_decoupling:
338
- # Ensure that concrete is NOT a subclass of abstract
339
514
  self.__ensureIsNotSubclass(abstract, concrete)
515
+
516
+ # Validate that concrete is a subclass of abstract
340
517
  else:
341
- # Validate that concrete is a subclass of abstract
342
518
  self.__ensureIsSubclass(abstract, concrete)
343
519
 
344
520
  # Ensure implementation
@@ -350,21 +526,36 @@ class Container:
350
526
  # Ensure that the alias is a valid string if provided
351
527
  if alias:
352
528
  self.__ensureAliasType(alias)
529
+ else:
530
+ rf_asbtract = ReflectionAbstract(abstract)
531
+ alias = rf_asbtract.getModuleWithClassName()
532
+
533
+ # If the service is already registered, drop it
534
+ self.__dropService(abstract, alias)
353
535
 
354
536
  # Register the service with singleton lifetime
355
- self.__singleton[abstract] = concrete
537
+ self.__bindings[abstract] = Binding(
538
+ contract = abstract,
539
+ concrete = concrete,
540
+ lifetime = Lifetime.SINGLETON,
541
+ enforce_decoupling = enforce_decoupling,
542
+ alias = alias
543
+ )
356
544
 
357
- # If an alias is provided, register it as well
358
- if alias:
359
- self.__singleton[alias] = concrete
360
- elif hasattr(abstract, '__name__'):
361
- alias = abstract.__name__
362
- self.__singleton[alias] = concrete
545
+ # Register the alias
546
+ self.__aliasses[alias] = self.__bindings[abstract]
363
547
 
364
548
  # Return True to indicate successful registration
365
549
  return True
366
550
 
367
- def scoped(self, abstract: Callable[..., Any], concrete: Callable[..., Any], alias: str = None, enforce_decoupling: bool = False) -> bool:
551
+ def scoped(
552
+ self,
553
+ abstract: Callable[..., Any],
554
+ concrete: Callable[..., Any],
555
+ *,
556
+ alias: str = None,
557
+ enforce_decoupling: bool = False
558
+ ) -> bool:
368
559
  """
369
560
  Registers a service with a scoped lifetime.
370
561
 
@@ -401,11 +592,12 @@ class Container:
401
592
  # Ensure that concrete is a concrete class
402
593
  self.__ensureConcreteClass(concrete, Lifetime.SCOPED)
403
594
 
595
+ # Ensure that concrete is NOT a subclass of abstract
404
596
  if enforce_decoupling:
405
- # Ensure that concrete is NOT a subclass of abstract
406
597
  self.__ensureIsNotSubclass(abstract, concrete)
598
+
599
+ # Validate that concrete is a subclass of abstract
407
600
  else:
408
- # Validate that concrete is a subclass of abstract
409
601
  self.__ensureIsSubclass(abstract, concrete)
410
602
 
411
603
  # Ensure implementation
@@ -417,21 +609,36 @@ class Container:
417
609
  # Ensure that the alias is a valid string if provided
418
610
  if alias:
419
611
  self.__ensureAliasType(alias)
612
+ else:
613
+ rf_asbtract = ReflectionAbstract(abstract)
614
+ alias = rf_asbtract.getModuleWithClassName()
615
+
616
+ # If the service is already registered, drop it
617
+ self.__dropService(abstract, alias)
420
618
 
421
619
  # Register the service with scoped lifetime
422
- self.__scoped[abstract] = concrete
620
+ self.__bindings[abstract] = Binding(
621
+ contract = abstract,
622
+ concrete = concrete,
623
+ lifetime = Lifetime.SCOPED,
624
+ enforce_decoupling = enforce_decoupling,
625
+ alias = alias
626
+ )
423
627
 
424
- # If an alias is provided, register it as well
425
- if alias:
426
- self.__scoped[alias] = concrete
427
- elif hasattr(abstract, '__name__'):
428
- alias = abstract.__name__
429
- self.__scoped[alias] = concrete
628
+ # Register the alias
629
+ self.__aliasses[alias] = self.__bindings[abstract]
430
630
 
431
631
  # Return True to indicate successful registration
432
632
  return True
433
633
 
434
- def instance(self, abstract: Callable[..., Any], instance: Any, alias: str = None, enforce_decoupling: bool = False) -> bool:
634
+ def instance(
635
+ self,
636
+ abstract: Callable[..., Any],
637
+ instance: Any,
638
+ *,
639
+ alias: str = None,
640
+ enforce_decoupling: bool = False
641
+ ) -> bool:
435
642
  """
436
643
  Registers an instance of a class or interface in the container.
437
644
  Parameters
@@ -466,11 +673,12 @@ class Container:
466
673
  # Ensure that the instance is a valid instance
467
674
  self.__ensureInstance(instance)
468
675
 
676
+ # Ensure that instance is NOT a subclass of abstract
469
677
  if enforce_decoupling:
470
- # Ensure that instance is NOT a subclass of abstract
471
678
  self.__ensureIsNotSubclass(abstract, instance.__class__)
679
+
680
+ # Validate that instance is a subclass of abstract
472
681
  else:
473
- # Validate that instance is a subclass of abstract
474
682
  self.__ensureIsSubclass(abstract, instance.__class__)
475
683
 
476
684
  # Ensure implementation
@@ -482,19 +690,165 @@ class Container:
482
690
  # Ensure that the alias is a valid string if provided
483
691
  if alias:
484
692
  self.__ensureAliasType(alias)
693
+ else:
694
+ rf_asbtract = ReflectionAbstract(abstract)
695
+ alias = rf_asbtract.getModuleWithClassName()
696
+
697
+ # If the service is already registered, drop it
698
+ self.__dropService(abstract, alias)
485
699
 
486
700
  # Register the instance with the abstract type
487
- self.__instance[abstract] = instance
701
+ self.__bindings[abstract] = Binding(
702
+ contract = abstract,
703
+ instance = instance,
704
+ lifetime = Lifetime.SINGLETON,
705
+ enforce_decoupling = enforce_decoupling,
706
+ alias = alias
707
+ )
488
708
 
489
- # If an alias is provided, register it as well
490
- if alias:
491
- self.__instance[alias] = instance
492
- elif hasattr(abstract, '__name__'):
493
- alias = abstract.__name__
494
- self.__instance[alias] = instance
709
+ # Register the alias
710
+ self.__aliasses[alias] = self.__bindings[abstract]
495
711
 
496
712
  # Return True to indicate successful registration
497
713
  return True
498
714
 
499
- def bind(self, concrete_instance_or_function, lifetime: str = Lifetime.TRANSIENT, alias: str = None) -> None:
500
- pass
715
+ def function(
716
+ self,
717
+ alias: str,
718
+ function: Callable[..., Any],
719
+ *,
720
+ lifetime: Lifetime = Lifetime.TRANSIENT
721
+ ) -> bool:
722
+ """
723
+ Registers a function or factory under a given alias.
724
+
725
+ Parameters
726
+ ----------
727
+ alias : str
728
+ The alias to register the function under.
729
+ function : Callable[..., Any]
730
+ The function or factory to register.
731
+ lifetime : Lifetime, optional
732
+ The lifetime of the function registration (default is TRANSIENT).
733
+
734
+ Returns
735
+ -------
736
+ bool
737
+ True if the function was registered successfully.
738
+
739
+ Raises
740
+ ------
741
+ OrionisContainerTypeError
742
+ If the alias is invalid or the function is not callable.
743
+ OrionisContainerException
744
+ If the lifetime is not allowed for the function signature.
745
+ """
746
+ # Normalize and validate the lifetime parameter
747
+ if not isinstance(lifetime, Lifetime):
748
+ if isinstance(lifetime, str):
749
+ lifetime_key = lifetime.strip().upper()
750
+ if lifetime_key in Lifetime.__members__:
751
+ lifetime = Lifetime[lifetime_key]
752
+ else:
753
+ valid = ', '.join(Lifetime.__members__.keys())
754
+ raise OrionisContainerTypeError(
755
+ f"Invalid lifetime '{lifetime}'. Valid options are: {valid}."
756
+ )
757
+ else:
758
+ raise OrionisContainerTypeError(
759
+ f"Lifetime must be of type str or Lifetime enum, got {type(lifetime).__name__}."
760
+ )
761
+
762
+ # Ensure that the alias is a valid string
763
+ self.__ensureAliasType(alias)
764
+
765
+ # Validate that the function is callable
766
+ self.__ensureIsCallable(function)
767
+
768
+ # Inspect the function signature
769
+ params = ReflectionCallable(function).getDependencies()
770
+
771
+ # If the function requires arguments, only allow TRANSIENT
772
+ if (len(params.resolved) + len(params.unresolved)) > 0 and lifetime != Lifetime.TRANSIENT:
773
+ raise OrionisContainerException(
774
+ "Functions that require arguments can only be registered with a TRANSIENT lifetime."
775
+ )
776
+
777
+ # If the service is already registered, drop it
778
+ self.__dropService(None, alias)
779
+
780
+ # Register the function with the specified alias and lifetime
781
+ self.__bindings[alias] = Binding(
782
+ function=function,
783
+ lifetime=lifetime,
784
+ alias=alias
785
+ )
786
+
787
+ # Register the function as a binding
788
+ self.__aliasses[alias] = self.__bindings[alias]
789
+
790
+ return True
791
+
792
+ def bound(
793
+ self,
794
+ abstract_or_alias: Any,
795
+ ) -> bool:
796
+ """
797
+ Checks if a service (by abstract type or alias) is registered in the container.
798
+
799
+ Parameters
800
+ ----------
801
+ abstract_or_alias : Any
802
+ The abstract class, interface, or alias (str) to check for registration.
803
+
804
+ Returns
805
+ -------
806
+ bool
807
+ True if the service is registered (either as an abstract type or alias), False otherwise.
808
+
809
+ Notes
810
+ -----
811
+ This method allows you to verify whether a service has been registered in the container,
812
+ either by its abstract type or by its alias. It supports both class-based and string-based lookups.
813
+ """
814
+ return (
815
+ abstract_or_alias in self.__bindings
816
+ or abstract_or_alias in self.__aliasses
817
+ )
818
+
819
+ def make(
820
+ self,
821
+ abstract_or_alias: Any,
822
+ *args,
823
+ **kwargs
824
+ ) -> Any:
825
+ """
826
+ Resolves and returns an instance of the requested service.
827
+
828
+ Parameters
829
+ ----------
830
+ abstract_or_alias : Any
831
+ The abstract class, interface, or alias (str) to resolve.
832
+ *args : tuple
833
+ Positional arguments to pass to the constructor of the resolved service.
834
+ **kwargs : dict
835
+ Keyword arguments to pass to the constructor of the resolved service.
836
+
837
+ Returns
838
+ -------
839
+ Any
840
+ An instance of the requested service.
841
+
842
+ Raises
843
+ ------
844
+ OrionisContainerException
845
+ If the requested service is not registered in the container.
846
+ """
847
+ if not self.bound(abstract_or_alias):
848
+ raise OrionisContainerException(
849
+ f"The requested service '{abstract_or_alias}' is not registered in the container."
850
+ )
851
+
852
+ binding = self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
853
+
854
+ print(binding)