orionis 0.319.0__py3-none-any.whl → 0.321.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.
@@ -4,13 +4,18 @@ from orionis.container.contracts.container import IContainer
4
4
  from orionis.container.entities.binding import Binding
5
5
  from orionis.container.enums.lifetimes import Lifetime
6
6
  from orionis.container.exceptions.container_exception import OrionisContainerException
7
- from orionis.container.exceptions.type_error_exception import OrionisContainerTypeError
7
+ from orionis.container.resolver import Resolver
8
+ from orionis.container.validators.implements import ImplementsAbstractMethods
9
+ from orionis.container.validators.is_abstract_class import IsAbstractClass
10
+ from orionis.container.validators.is_callable import IsCallable
11
+ from orionis.container.validators.is_concrete_class import IsConcreteClass
12
+ from orionis.container.validators.is_instance import IsInstance
13
+ from orionis.container.validators.is_not_subclass import IsNotSubclass
14
+ from orionis.container.validators.is_subclass import IsSubclass
15
+ from orionis.container.validators.is_valid_alias import IsValidAlias
16
+ from orionis.container.validators.lifetime import LifetimeValidator
8
17
  from orionis.services.introspection.abstract.reflection_abstract import ReflectionAbstract
9
18
  from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
10
- from orionis.services.introspection.concretes.reflection_concrete import ReflectionConcrete
11
- from orionis.services.introspection.dependencies.entities.resolved_dependencies import ResolvedDependency
12
- from orionis.services.introspection.instances.reflection_instance import ReflectionInstance
13
- from orionis.services.introspection.reflection import Reflection
14
19
 
15
20
  class Container(IContainer):
16
21
 
@@ -29,9 +34,7 @@ class Container(IContainer):
29
34
  _initialized = False
30
35
 
31
36
  def __new__(
32
- cls,
33
- *args,
34
- **kwargs
37
+ cls
35
38
  ) -> 'Container':
36
39
  """
37
40
  Creates and returns a singleton instance of the class.
@@ -40,21 +43,26 @@ class Container(IContainer):
40
43
  of the class exists. If an instance does not exist, it acquires a lock to
41
44
  ensure thread safety and creates the instance. Subsequent calls return the
42
45
  existing instance.
43
- Parameters
44
- ----------
45
- *args : tuple
46
- Variable length argument list.
47
- **kwargs : dict
48
- Arbitrary keyword arguments.
46
+
49
47
  Returns
50
48
  -------
51
49
  Container
52
50
  The singleton instance of the class.
53
51
  """
52
+
53
+ # Check if the class has an existing instance
54
54
  if cls._instance is None:
55
+
56
+ # Acquire the lock to ensure thread safety during instance creation
55
57
  with cls._lock:
58
+
59
+ # If the instance is still None, create a new instance
56
60
  if cls._instance is None:
61
+
62
+ # Call the superclass's __new__ method to create a new instance
57
63
  cls._instance = super(Container, cls).__new__(cls)
64
+
65
+ # Return the existing instance of the class
58
66
  return cls._instance
59
67
 
60
68
  def __init__(
@@ -75,362 +83,19 @@ class Container(IContainer):
75
83
  - Initialization occurs only once per class, regardless of the number of instances.
76
84
  - The container registers itself under the IContainer interface to allow for dependency injection.
77
85
  """
86
+
87
+ # Check if the class has been initialized
78
88
  if not self.__class__._initialized:
89
+
90
+ # Initialize the container's internal state
79
91
  self.__bindings = {}
80
92
  self.__aliasses = {}
81
- self.__class__._initialized = True
82
- self.instance(IContainer, self)
83
-
84
- def __dropService(
85
- self,
86
- abstract: Callable[..., Any] = None,
87
- alias: str = None
88
- ) -> None:
89
- """
90
- Drops a service from the container.
91
-
92
- Parameters
93
- ----------
94
- abstract : Callable[..., Any]
95
- The abstract type or interface to be removed.
96
- alias : str, optional
97
- The alias of the service to be removed. If not provided, the service will be removed by its abstract type.
98
-
99
- Raises
100
- ------
101
- OrionisContainerException
102
- If the service does not exist in the container.
103
- """
104
-
105
- # If abstract is provided
106
- if abstract:
107
-
108
- # Remove the abstract service from the bindings if it exists
109
- if abstract in self.__bindings:
110
- del self.__bindings[abstract]
111
-
112
- # Remove the default alias (module + class name) from aliases if it exists
113
- abs_alias = ReflectionAbstract(abstract).getModuleWithClassName()
114
- if abs_alias in self.__aliasses:
115
- del self.__aliasses[abs_alias]
116
-
117
- # If a custom alias is provided
118
- if alias:
119
-
120
- # Remove it from the aliases dictionary if it exists
121
- if alias in self.__aliasses:
122
- del self.__aliasses[alias]
123
-
124
- # Remove the binding associated with the alias
125
- if alias in self.__bindings:
126
- del self.__bindings[alias]
127
-
128
- def __ensureIsCallable(
129
- self,
130
- value: Any
131
- ) -> None:
132
- """
133
- Ensures that the provided value is callable.
134
-
135
- Parameters
136
- ----------
137
- value : Any
138
- The value to check.
139
-
140
- Raises
141
- ------
142
- OrionisContainerTypeError
143
- If the value is not callable.
144
- """
145
-
146
- if not callable(value):
147
- raise OrionisContainerTypeError(
148
- f"Expected a callable type, but got {type(value).__name__} instead."
149
- )
150
-
151
- def __ensureAliasType(
152
- self,
153
- value: Any
154
- ) -> None:
155
- """
156
- Ensures that the provided value is a valid alias of type str and does not contain invalid characters.
157
-
158
- Parameters
159
- ----------
160
- value : Any
161
- The value to check.
162
-
163
- Raises
164
- ------
165
- OrionisContainerTypeError
166
- If the value is not of type str or contains invalid characters.
167
-
168
- Notes
169
- -----
170
- This method validates that a given value is a string and does not contain characters
171
- that could cause errors when resolving dependencies (e.g., whitespace, special symbols).
172
- """
173
-
174
- # Check if the value is a string
175
- if not isinstance(value, str):
176
- raise OrionisContainerTypeError(
177
- f"Expected a string type for alias, but got {type(value).__name__} instead."
178
- )
179
-
180
- # Define a set of invalid characters for aliases
181
- invalid_chars = set(' \t\n\r\x0b\x0c!@#$%^&*()[]{};:,/<>?\\|`~"\'')
182
- if any(char in invalid_chars for char in value):
183
- raise OrionisContainerTypeError(
184
- f"Alias '{value}' contains invalid characters. "
185
- "Aliases must not contain whitespace or special symbols."
186
- )
187
-
188
- def __ensureAbstractClass(
189
- self,
190
- abstract: Callable[..., Any],
191
- lifetime: str
192
- ) -> None:
193
- """
194
- Ensures that the provided abstract is an abstract class.
195
-
196
- Parameters
197
- ----------
198
- abstract : Callable[..., Any]
199
- The class intended to represent the abstract type.
200
- lifetime : str
201
- The service lifetime descriptor, used for error messages.
202
-
203
- Raises
204
- ------
205
- OrionisContainerTypeError
206
- If the abstract class check fails.
207
- """
208
- try:
209
- ReflectionAbstract.ensureIsAbstractClass(abstract)
210
- except Exception as e:
211
- raise OrionisContainerTypeError(
212
- f"Unexpected error registering {lifetime} service: {e}"
213
- ) from e
214
-
215
- def __ensureConcreteClass(
216
- self,
217
- concrete: Callable[..., Any],
218
- lifetime: str
219
- ) -> None:
220
- """
221
- Ensures that the provided concrete is a concrete (non-abstract) class.
222
-
223
- Parameters
224
- ----------
225
- concrete : Callable[..., Any]
226
- The class intended to represent the concrete implementation.
227
- lifetime : str
228
- The service lifetime descriptor, used for error messages.
229
-
230
- Raises
231
- ------
232
- OrionisContainerTypeError
233
- If the concrete class check fails.
234
- """
235
- try:
236
- ReflectionConcrete.ensureIsConcreteClass(concrete)
237
- except Exception as e:
238
- raise OrionisContainerTypeError(
239
- f"Unexpected error registering {lifetime} service: {e}"
240
- ) from e
241
-
242
- def __ensureIsSubclass(
243
- self,
244
- abstract: Callable[..., Any],
245
- concrete: Callable[..., Any]
246
- ) -> None:
247
- """
248
- Validates that the concrete class is a subclass of the provided abstract class.
249
-
250
- Parameters
251
- ----------
252
- abstract : Callable[..., Any]
253
- The abstract base class or interface.
254
- concrete : Callable[..., Any]
255
- The concrete implementation class to check.
256
-
257
- Raises
258
- ------
259
- OrionisContainerException
260
- If the concrete class is NOT a subclass of the abstract class.
261
-
262
- Notes
263
- -----
264
- This method ensures that the concrete implementation inherits from the abstract class,
265
- which is required for proper dependency injection and interface enforcement.
266
- """
267
- if not issubclass(concrete, abstract):
268
- raise OrionisContainerException(
269
- "The concrete class must inherit from the provided abstract class. "
270
- "Please ensure that the concrete class is a subclass of the specified abstract class."
271
- )
272
-
273
- def __ensureIsNotSubclass(
274
- self,
275
- abstract: Callable[..., Any],
276
- concrete: Callable[..., Any]
277
- ) -> None:
278
- """
279
- Validates that the concrete class is NOT a subclass of the provided abstract class.
280
-
281
- Parameters
282
- ----------
283
- abstract : Callable[..., Any]
284
- The abstract base class or interface.
285
- concrete : Callable[..., Any]
286
- The concrete implementation class to check.
287
-
288
- Raises
289
- ------
290
- OrionisContainerException
291
- If the concrete class IS a subclass of the abstract class.
292
-
293
- Notes
294
- -----
295
- This method ensures that the concrete implementation does NOT inherit from the abstract class.
296
- """
297
- if issubclass(concrete, abstract):
298
- raise OrionisContainerException(
299
- "The concrete class must NOT inherit from the provided abstract class. "
300
- "Please ensure that the concrete class is not a subclass of the specified abstract class."
301
- )
302
-
303
- def __ensureInstance(
304
- self,
305
- instance: Any
306
- ) -> None:
307
- """
308
- Ensures that the provided object is a valid instance.
309
-
310
- Parameters
311
- ----------
312
- instance : Any
313
- The object to be validated as an instance.
314
-
315
- Raises
316
- ------
317
- OrionisContainerTypeError
318
- If the provided object is not a valid instance.
319
-
320
- Notes
321
- -----
322
- This method uses ReflectionInstance to verify that the given object
323
- is a proper instance (not a class or abstract type). If the check fails,
324
- an OrionisContainerTypeError is raised with a descriptive message.
325
- """
326
- try:
327
- ReflectionInstance.ensureIsInstance(instance)
328
- except Exception as e:
329
- raise OrionisContainerTypeError(
330
- f"Error registering instance: {e}"
331
- ) from e
332
-
333
- def __ensureImplementation(
334
- self,
335
- *,
336
- abstract: Callable[..., Any] = None,
337
- concrete: Callable[..., Any] = None,
338
- instance: Any = None
339
- ) -> None:
340
- """
341
- Ensures that a concrete class or instance implements all abstract methods defined in an abstract class.
342
-
343
- Parameters
344
- ----------
345
- abstract : Callable[..., Any]
346
- The abstract class containing abstract methods.
347
- concrete : Callable[..., Any], optional
348
- The concrete class that should implement the abstract methods.
349
- instance : Any, optional
350
- The instance that should implement the abstract methods.
351
-
352
- Raises
353
- ------
354
- OrionisContainerException
355
- If the concrete class or instance does not implement all abstract methods defined in the abstract class.
356
-
357
- Notes
358
- -----
359
- This method checks that all abstract methods in the given abstract class are implemented
360
- in the provided concrete class or instance. If any methods are missing, an exception is raised with
361
- details about the missing implementations.
362
- """
363
- if abstract is None:
364
- raise OrionisContainerException("Abstract class must be provided for implementation check.")
365
-
366
- abstract_methods = getattr(abstract, '__abstractmethods__', set())
367
- if not abstract_methods:
368
- raise OrionisContainerException(
369
- f"The abstract class '{abstract.__name__}' does not define any abstract methods. "
370
- "An abstract class must have at least one abstract method."
371
- )
372
-
373
- target = concrete if concrete is not None else instance
374
- if target is None:
375
- raise OrionisContainerException("Either concrete class or instance must be provided for implementation check.")
376
93
 
377
- target_class = target if Reflection.isClass(target) else target.__class__
378
- target_name = target_class.__name__
379
- abstract_name = abstract.__name__
380
-
381
- not_implemented = []
382
- for method in abstract_methods:
383
- if not hasattr(target, str(method).replace(f"_{abstract_name}", f"_{target_name}")):
384
- not_implemented.append(method)
385
-
386
- if not_implemented:
387
- formatted_methods = "\n • " + "\n • ".join(not_implemented)
388
- raise OrionisContainerException(
389
- f"'{target_name}' does not implement the following abstract methods defined in '{abstract_name}':{formatted_methods}\n"
390
- "Please ensure that all abstract methods are implemented."
391
- )
392
-
393
- def __getService(
394
- self,
395
- abstract_or_alias: Any
396
- ) -> Binding:
397
- """
398
- Retrieves the binding for the requested abstract type or alias.
399
-
400
- Parameters
401
- ----------
402
- abstract_or_alias : Any
403
- The abstract class, interface, or alias (str) to retrieve.
404
-
405
- Returns
406
- -------
407
- Binding
408
- The binding associated with the requested abstract type or alias.
409
- """
410
- return self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
411
-
412
- def __getFirstService(
413
- self,
414
- abstract_or_aliasses: list
415
- ) -> Binding:
416
- """
417
- Retrieves the first binding from a list of abstract types or aliases.
418
-
419
- Parameters
420
- ----------
421
- abstract_or_aliasses : list
422
- A list of abstract classes, interfaces, or aliases (str) to retrieve.
94
+ # Set the initialized flag to True to prevent re-initialization
95
+ self.__class__._initialized = True
423
96
 
424
- Returns
425
- -------
426
- Binding
427
- The first binding found in the container for the provided abstract types or aliases.
428
- """
429
- for item in abstract_or_aliasses:
430
- binding = self.__getService(item)
431
- if binding:
432
- return binding
433
- return None
97
+ # Register the container itself as a service
98
+ self.instance(IContainer, self)
434
99
 
435
100
  def transient(
436
101
  self,
@@ -472,28 +137,28 @@ class Container(IContainer):
472
137
  """
473
138
 
474
139
  # Ensure that abstract is an abstract class
475
- self.__ensureAbstractClass(abstract, Lifetime.TRANSIENT)
140
+ IsAbstractClass(abstract, Lifetime.TRANSIENT)
476
141
 
477
142
  # Ensure that concrete is a concrete class
478
- self.__ensureConcreteClass(concrete, Lifetime.TRANSIENT)
143
+ IsConcreteClass(concrete, Lifetime.TRANSIENT)
479
144
 
480
145
  # Ensure that concrete is NOT a subclass of abstract
481
146
  if enforce_decoupling:
482
- self.__ensureIsNotSubclass(abstract, concrete)
147
+ IsNotSubclass(abstract, concrete)
483
148
 
484
149
  # Validate that concrete is a subclass of abstract
485
150
  else:
486
- self.__ensureIsSubclass(abstract, concrete)
151
+ IsSubclass(abstract, concrete)
487
152
 
488
153
  # Ensure implementation
489
- self.__ensureImplementation(
154
+ ImplementsAbstractMethods(
490
155
  abstract=abstract,
491
156
  concrete=concrete
492
157
  )
493
158
 
494
159
  # Ensure that the alias is a valid string if provided
495
160
  if alias:
496
- self.__ensureAliasType(alias)
161
+ IsValidAlias(alias)
497
162
 
498
163
  # Extract the module and class name for the alias
499
164
  else:
@@ -501,7 +166,7 @@ class Container(IContainer):
501
166
  alias = rf_asbtract.getModuleWithClassName()
502
167
 
503
168
  # If the service is already registered, drop it
504
- self.__dropService(abstract, alias)
169
+ self.drop(abstract, alias)
505
170
 
506
171
  # Register the service with transient lifetime
507
172
  self.__bindings[abstract] = Binding(
@@ -557,34 +222,34 @@ class Container(IContainer):
557
222
  """
558
223
 
559
224
  # Ensure that abstract is an abstract class
560
- self.__ensureAbstractClass(abstract, Lifetime.SINGLETON)
225
+ IsAbstractClass(abstract, Lifetime.SINGLETON)
561
226
 
562
227
  # Ensure that concrete is a concrete class
563
- self.__ensureConcreteClass(concrete, Lifetime.SINGLETON)
228
+ IsConcreteClass(concrete, Lifetime.SINGLETON)
564
229
 
565
230
  # Ensure that concrete is NOT a subclass of abstract
566
231
  if enforce_decoupling:
567
- self.__ensureIsNotSubclass(abstract, concrete)
232
+ IsNotSubclass(abstract, concrete)
568
233
 
569
234
  # Validate that concrete is a subclass of abstract
570
235
  else:
571
- self.__ensureIsSubclass(abstract, concrete)
236
+ IsSubclass(abstract, concrete)
572
237
 
573
238
  # Ensure implementation
574
- self.__ensureImplementation(
239
+ ImplementsAbstractMethods(
575
240
  abstract=abstract,
576
241
  concrete=concrete
577
242
  )
578
243
 
579
244
  # Ensure that the alias is a valid string if provided
580
245
  if alias:
581
- self.__ensureAliasType(alias)
246
+ IsValidAlias(alias)
582
247
  else:
583
248
  rf_asbtract = ReflectionAbstract(abstract)
584
249
  alias = rf_asbtract.getModuleWithClassName()
585
250
 
586
251
  # If the service is already registered, drop it
587
- self.__dropService(abstract, alias)
252
+ self.drop(abstract, alias)
588
253
 
589
254
  # Register the service with singleton lifetime
590
255
  self.__bindings[abstract] = Binding(
@@ -640,34 +305,34 @@ class Container(IContainer):
640
305
  """
641
306
 
642
307
  # Ensure that abstract is an abstract class
643
- self.__ensureAbstractClass(abstract, Lifetime.SCOPED)
308
+ IsAbstractClass(abstract, Lifetime.SCOPED)
644
309
 
645
310
  # Ensure that concrete is a concrete class
646
- self.__ensureConcreteClass(concrete, Lifetime.SCOPED)
311
+ IsConcreteClass(concrete, Lifetime.SCOPED)
647
312
 
648
313
  # Ensure that concrete is NOT a subclass of abstract
649
314
  if enforce_decoupling:
650
- self.__ensureIsNotSubclass(abstract, concrete)
315
+ IsNotSubclass(abstract, concrete)
651
316
 
652
317
  # Validate that concrete is a subclass of abstract
653
318
  else:
654
- self.__ensureIsSubclass(abstract, concrete)
319
+ IsSubclass(abstract, concrete)
655
320
 
656
321
  # Ensure implementation
657
- self.__ensureImplementation(
322
+ ImplementsAbstractMethods(
658
323
  abstract=abstract,
659
324
  concrete=concrete
660
325
  )
661
326
 
662
327
  # Ensure that the alias is a valid string if provided
663
328
  if alias:
664
- self.__ensureAliasType(alias)
329
+ IsValidAlias(alias)
665
330
  else:
666
331
  rf_asbtract = ReflectionAbstract(abstract)
667
332
  alias = rf_asbtract.getModuleWithClassName()
668
333
 
669
334
  # If the service is already registered, drop it
670
- self.__dropService(abstract, alias)
335
+ self.drop(abstract, alias)
671
336
 
672
337
  # Register the service with scoped lifetime
673
338
  self.__bindings[abstract] = Binding(
@@ -721,34 +386,34 @@ class Container(IContainer):
721
386
  """
722
387
 
723
388
  # Ensure that the abstract is an abstract class
724
- self.__ensureAbstractClass(abstract, f"Instance {Lifetime.SINGLETON}")
389
+ IsAbstractClass(abstract, f"Instance {Lifetime.SINGLETON}")
725
390
 
726
391
  # Ensure that the instance is a valid instance
727
- self.__ensureInstance(instance)
392
+ IsInstance(instance)
728
393
 
729
394
  # Ensure that instance is NOT a subclass of abstract
730
395
  if enforce_decoupling:
731
- self.__ensureIsNotSubclass(abstract, instance.__class__)
396
+ IsNotSubclass(abstract, instance.__class__)
732
397
 
733
398
  # Validate that instance is a subclass of abstract
734
399
  else:
735
- self.__ensureIsSubclass(abstract, instance.__class__)
400
+ IsSubclass(abstract, instance.__class__)
736
401
 
737
402
  # Ensure implementation
738
- self.__ensureImplementation(
403
+ ImplementsAbstractMethods(
739
404
  abstract=abstract,
740
405
  instance=instance
741
406
  )
742
407
 
743
408
  # Ensure that the alias is a valid string if provided
744
409
  if alias:
745
- self.__ensureAliasType(alias)
410
+ IsValidAlias(alias)
746
411
  else:
747
412
  rf_asbtract = ReflectionAbstract(abstract)
748
413
  alias = rf_asbtract.getModuleWithClassName()
749
414
 
750
415
  # If the service is already registered, drop it
751
- self.__dropService(abstract, alias)
416
+ self.drop(abstract, alias)
752
417
 
753
418
  # Register the instance with the abstract type
754
419
  self.__bindings[abstract] = Binding(
@@ -796,27 +461,15 @@ class Container(IContainer):
796
461
  OrionisContainerException
797
462
  If the lifetime is not allowed for the function signature.
798
463
  """
464
+
799
465
  # Normalize and validate the lifetime parameter
800
- if not isinstance(lifetime, Lifetime):
801
- if isinstance(lifetime, str):
802
- lifetime_key = lifetime.strip().upper()
803
- if lifetime_key in Lifetime.__members__:
804
- lifetime = Lifetime[lifetime_key]
805
- else:
806
- valid = ', '.join(Lifetime.__members__.keys())
807
- raise OrionisContainerTypeError(
808
- f"Invalid lifetime '{lifetime}'. Valid options are: {valid}."
809
- )
810
- else:
811
- raise OrionisContainerTypeError(
812
- f"Lifetime must be of type str or Lifetime enum, got {type(lifetime).__name__}."
813
- )
466
+ lifetime = LifetimeValidator(lifetime)
814
467
 
815
468
  # Ensure that the alias is a valid string
816
- self.__ensureAliasType(alias)
469
+ IsValidAlias(alias)
817
470
 
818
471
  # Validate that the function is callable
819
- self.__ensureIsCallable(fn)
472
+ IsCallable(fn)
820
473
 
821
474
  # Inspect the function signature
822
475
  params = ReflectionCallable(fn).getDependencies()
@@ -828,7 +481,7 @@ class Container(IContainer):
828
481
  )
829
482
 
830
483
  # If the service is already registered, drop it
831
- self.__dropService(None, alias)
484
+ self.drop(None, alias)
832
485
 
833
486
  # Register the function with the specified alias and lifetime
834
487
  self.__bindings[alias] = Binding(
@@ -869,369 +522,115 @@ class Container(IContainer):
869
522
  or abstract_or_alias in self.__aliasses
870
523
  )
871
524
 
872
- def make(
525
+ def getBinding(
873
526
  self,
874
- abstract_or_alias: Any,
875
- *args: tuple,
876
- **kwargs: dict
877
- ) -> Any:
527
+ abstract_or_alias: Any
528
+ ) -> Binding:
878
529
  """
879
- Resolves and returns an instance of the requested service.
530
+ Retrieves the binding for the requested abstract type or alias.
880
531
 
881
532
  Parameters
882
533
  ----------
883
534
  abstract_or_alias : Any
884
- The abstract class, interface, or alias (str) to resolve.
885
- *args : tuple
886
- Positional arguments to pass to the constructor of the resolved service.
887
- **kwargs : dict
888
- Keyword arguments to pass to the constructor of the resolved service.
535
+ The abstract class, interface, or alias (str) to retrieve.
889
536
 
890
537
  Returns
891
538
  -------
892
- Any
893
- An instance of the requested service.
894
-
895
- Raises
896
- ------
897
- OrionisContainerException
898
- If the requested service is not registered in the container.
539
+ Binding
540
+ The binding associated with the requested abstract type or alias.
899
541
  """
900
- # Retrieve the binding for the requested abstract or alias
901
- binding = self.__getService(abstract_or_alias)
902
-
903
- # Check if the requested service is registered in the container
904
- if not binding:
905
- raise OrionisContainerException(
906
- f"The requested service '{abstract_or_alias}' is not registered in the container."
907
- )
908
-
909
- # Handle based on binding type and lifetime
910
- if binding.lifetime == Lifetime.TRANSIENT:
911
- return self.__resolveTransient(binding, *args, **kwargs)
912
- elif binding.lifetime == Lifetime.SINGLETON:
913
- return self.__resolveSingleton(binding, *args, **kwargs)
914
- elif binding.lifetime == Lifetime.SCOPED:
915
- # TODO: Implement scoped lifetime resolution
916
- raise OrionisContainerException(
917
- "Scoped lifetime resolution is not yet implemented."
918
- )
542
+ return self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
919
543
 
920
- def __resolveTransient(self, binding: Binding, *args, **kwargs) -> Any:
544
+ def drop(
545
+ self,
546
+ abstract: Callable[..., Any] = None,
547
+ alias: str = None
548
+ ) -> None:
921
549
  """
922
- Resolves a service with transient lifetime.
550
+ Drops a service from the container by removing its bindings and aliases.
551
+ This method allows removing registered services from the dependency injection container,
552
+ either by their abstract type or by their alias. When a service is dropped,
553
+ all its bindings and aliases are removed from the container.
923
554
 
924
- Parameters
925
- ----------
926
- binding : Binding
927
- The binding to resolve.
928
- *args : tuple
929
- Positional arguments to pass to the constructor.
930
- **kwargs : dict
931
- Keyword arguments to pass to the constructor.
932
-
933
- Returns
555
+ Warning
934
556
  -------
935
- Any
936
- A new instance of the requested service.
937
- """
938
-
939
- # Check if the binding has a concrete class or function defined
940
- if binding.concrete:
941
- if args or kwargs:
942
- return self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
943
- else:
944
- return self.__instantiateConcreteReflective(binding.concrete)
945
-
946
- # If the binding has a function defined
947
- elif binding.function:
948
- if args or kwargs:
949
- return self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
950
- else:
951
- return self.__instantiateCallableReflective(binding.function)
952
-
953
- # If neither concrete class nor function is defined
954
- else:
955
- raise OrionisContainerException(
956
- "Cannot resolve transient binding: neither a concrete class nor a function is defined."
957
- )
557
+ Using this method irresponsibly can severely damage the system's logic.
558
+ Only use it when you are certain about the consequences, as removing
559
+ critical services may lead to system failures and unexpected behavior.
560
+ abstract : Callable[..., Any], optional
561
+ The abstract type or interface to be removed from the container.
562
+ If provided, both the binding and the default alias for this type will be removed.
563
+ The alias of the service to be removed. If provided, both the alias entry
564
+ and any associated binding will be removed.
958
565
 
959
- def __resolveSingleton(self, binding: Binding, *args, **kwargs) -> Any:
960
- """
961
- Resolves a service with singleton lifetime.
962
-
963
- Parameters
964
- ----------
965
- binding : Binding
966
- The binding to resolve.
967
- *args : tuple
968
- Positional arguments to pass to the constructor (only used if instance doesn't exist yet).
969
- **kwargs : dict
970
- Keyword arguments to pass to the constructor (only used if instance doesn't exist yet).
971
-
972
- Returns
973
- -------
974
- Any
975
- The singleton instance of the requested service.
976
- """
977
- # Return existing instance if available
978
- if binding.instance:
979
- return binding.instance
980
-
981
- # Create instance if needed
982
- if binding.concrete:
983
- if args or kwargs:
984
- binding.instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
985
- else:
986
- binding.instance = self.__instantiateConcreteReflective(binding.concrete)
987
- return binding.instance
988
-
989
- # If the binding has a function defined
990
- elif binding.function:
991
- if args or kwargs:
992
- result = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
993
- else:
994
- result = self.__instantiateCallableReflective(binding.function)
995
-
996
- # Store the result directly as the singleton instance
997
- # We don't automatically invoke factory function results anymore
998
- binding.instance = result
999
- return binding.instance
1000
-
1001
- # If neither concrete class nor function is defined
1002
- else:
1003
- raise OrionisContainerException(
1004
- "Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
1005
- )
1006
-
1007
- def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
566
+ Notes
567
+ -----
568
+ At least one parameter (abstract or alias) must be provided for the method to take effect.
569
+ If both are provided, both will be processed independently.
1008
570
  """
1009
- Instantiates a concrete class with the provided arguments.
1010
-
1011
- Parameters
1012
- ----------
1013
- concrete : Callable[..., Any]
1014
- Class to instantiate.
1015
- *args : tuple
1016
- Positional arguments to pass to the constructor.
1017
- **kwargs : dict
1018
- Keyword arguments to pass to the constructor.
1019
571
 
1020
- Returns
1021
- -------
1022
- object
1023
- A new instance of the specified concrete class.
1024
- """
572
+ # If abstract is provided
573
+ if abstract:
1025
574
 
1026
- # try to instantiate the concrete class with the provided arguments
1027
- try:
575
+ # Remove the abstract service from the bindings if it exists
576
+ if abstract in self.__bindings:
577
+ del self.__bindings[abstract]
1028
578
 
1029
- # If the concrete is a class, instantiate it directly
1030
- return concrete(*args, **kwargs)
579
+ # Remove the default alias (module + class name) from aliases if it exists
580
+ abs_alias = ReflectionAbstract(abstract).getModuleWithClassName()
581
+ if abs_alias in self.__aliasses:
582
+ del self.__aliasses[abs_alias]
1031
583
 
1032
- except TypeError as e:
584
+ # If a custom alias is provided
585
+ if alias:
1033
586
 
1034
- # If instantiation fails, use ReflectionConcrete to get class name and constructor signature
1035
- rf_concrete = ReflectionConcrete(concrete)
1036
- class_name = rf_concrete.getClassName()
1037
- signature = rf_concrete.getConstructorSignature()
587
+ # Remove it from the aliases dictionary if it exists
588
+ if alias in self.__aliasses:
589
+ del self.__aliasses[alias]
1038
590
 
1039
- # Raise an exception with detailed information about the failure
1040
- raise OrionisContainerException(
1041
- f"Failed to instantiate [{class_name}] with the provided arguments: {e}\n"
1042
- f"Expected constructor signature: [{signature}]"
1043
- ) from e
591
+ # Remove the binding associated with the alias
592
+ if alias in self.__bindings:
593
+ del self.__bindings[alias]
1044
594
 
1045
- def __instantiateCallableWithArgs(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
595
+ def make(
596
+ self,
597
+ abstract_or_alias: Any,
598
+ *args: tuple,
599
+ **kwargs: dict
600
+ ) -> Any:
1046
601
  """
1047
- Invokes a callable with the provided arguments.
602
+ Resolves and returns an instance of the requested service.
1048
603
 
1049
604
  Parameters
1050
605
  ----------
1051
- fn : Callable[..., Any]
1052
- The callable to invoke.
606
+ abstract_or_alias : Any
607
+ The abstract class, interface, or alias (str) to resolve.
1053
608
  *args : tuple
1054
- Positional arguments to pass to the callable.
609
+ Positional arguments to pass to the constructor of the resolved service.
1055
610
  **kwargs : dict
1056
- Keyword arguments to pass to the callable.
1057
-
1058
- Returns
1059
- -------
1060
- Any
1061
- The result of the callable.
1062
- """
1063
-
1064
- # Try to invoke the callable with the provided arguments
1065
- try:
1066
-
1067
- # If the callable is a function, invoke it directly
1068
- return fn(*args, **kwargs)
1069
-
1070
- except TypeError as e:
1071
-
1072
- # If invocation fails, use ReflectionCallable to get function name and signature
1073
- rf_callable = ReflectionCallable(fn)
1074
- function_name = rf_callable.getName()
1075
- signature = rf_callable.getSignature()
1076
-
1077
- # Raise an exception with detailed information about the failure
1078
- raise OrionisContainerException(
1079
- f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
1080
- f"Expected function signature: [{signature}]"
1081
- ) from e
1082
-
1083
- def __instantiateConcreteReflective(self, concrete: Callable[..., Any]) -> Any:
1084
- """
1085
- Instantiates a concrete class reflectively, resolving its dependencies from the container.
1086
-
1087
- Parameters
1088
- ----------
1089
- concrete : Callable[..., Any]
1090
- The concrete class to instantiate.
1091
-
1092
- Returns
1093
- -------
1094
- Any
1095
- A new instance of the concrete class.
1096
- """
1097
- # Resolve dependencies for the concrete class
1098
- params = self.__resolveDependencies(concrete, is_class=True)
1099
-
1100
- # Instantiate the concrete class with resolved dependencies
1101
- return concrete(**params)
1102
-
1103
- def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
1104
- """
1105
- Invokes a callable reflectively, resolving its dependencies from the container.
1106
-
1107
- Parameters
1108
- ----------
1109
- fn : Callable[..., Any]
1110
- The callable to invoke.
611
+ Keyword arguments to pass to the constructor of the resolved service.
1111
612
 
1112
613
  Returns
1113
614
  -------
1114
615
  Any
1115
- The result of the callable.
1116
- """
1117
-
1118
- # Resolve dependencies for the callable
1119
- params = self.__resolveDependencies(fn, is_class=False)
1120
-
1121
- # Invoke the callable with resolved dependencies
1122
- return fn(**params)
616
+ An instance of the requested service.
1123
617
 
1124
- def __resolveDependencies(
1125
- self,
1126
- target: Callable[..., Any],
1127
- *,
1128
- is_class: bool = False
1129
- ) -> dict:
618
+ Raises
619
+ ------
620
+ OrionisContainerException
621
+ If the requested service is not registered in the container.
1130
622
  """
1131
- Resolves dependencies for a target callable or class.
1132
-
1133
- Parameters
1134
- ----------
1135
- target : Callable[..., Any]
1136
- The target callable or class whose dependencies to resolve.
1137
- is_class : bool, optional
1138
- Whether the target is a class (True) or a callable (False).
623
+ if not self.bound(abstract_or_alias):
624
+ raise OrionisContainerException(
625
+ f"The requested service '{abstract_or_alias}' is not registered in the container."
626
+ )
1139
627
 
1140
- Returns
1141
- -------
1142
- dict
1143
- A dictionary of resolved dependencies.
1144
- """
1145
- try:
1146
-
1147
- # Use ReflectionConcrete for classes and ReflectionCallable for callables
1148
- if is_class:
1149
- reflection = ReflectionConcrete(target)
1150
- dependencies = reflection.getConstructorDependencies()
1151
- name = reflection.getClassName()
1152
-
1153
- # If the target is a callable, use ReflectionCallable
1154
- else:
1155
- reflection = ReflectionCallable(target)
1156
- dependencies = reflection.getDependencies()
1157
- name = reflection.getName()
1158
-
1159
- # Check for unresolved dependencies
1160
- if dependencies.unresolved:
1161
- unresolved_args = ', '.join(dependencies.unresolved)
1162
- raise OrionisContainerException(
1163
- f"Cannot resolve '{name}' because the following required arguments are missing: [{unresolved_args}]."
1164
- )
1165
-
1166
- # Resolve dependencies
1167
- params = {}
1168
- for param_name, dep in dependencies.resolved.items():
1169
-
1170
- # If the dependency is a ResolvedDependency, resolve it
1171
- if isinstance(dep, ResolvedDependency):
1172
-
1173
- # If the dependency is a built-in type, raise an exception
1174
- if dep.module_name == 'builtins':
1175
- raise OrionisContainerException(
1176
- f"Cannot resolve '{name}' because parameter '{param_name}' depends on built-in type '{dep.type.__name__}'."
1177
- )
1178
-
1179
- # Try to resolve from container
1180
- service = self.__getFirstService([dep.type, dep.full_class_path])
1181
- if service:
1182
- params[param_name] = self.make(service.alias)
1183
-
1184
- # Try to instantiate directly if it's a concrete class
1185
- elif ReflectionConcrete.isConcreteClass(dep.type):
1186
- params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=True))
1187
-
1188
- # Try to call directly if it's a callable
1189
- elif callable(dep.type) and not isinstance(dep.type, type):
1190
- params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=False))
1191
-
1192
- # If the dependency cannot be resolved, raise an exception
1193
- else:
1194
- raise OrionisContainerException(
1195
- f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}' for '{name}'."
1196
- )
1197
- else:
1198
- # Use default value
1199
- params[param_name] = dep
1200
-
1201
- # Return the resolved parameters
1202
- return params
1203
-
1204
- except ImportError as e:
1205
-
1206
- # Extract module name from the error message if possible
1207
- import_msg = str(e)
1208
- module_name = target.__module__ if hasattr(target, '__module__') else "unknown module"
1209
-
1210
- # Check for potential circular import patterns
1211
- if "circular import" in import_msg.lower() or "cannot import name" in import_msg.lower():
1212
- raise OrionisContainerException(
1213
- f"Circular import detected while resolving dependencies for '{target.__name__}' in module '{module_name}'.\n"
1214
- f"This typically happens when two modules import each other. Consider:\n"
1215
- f"1. Restructuring your code to avoid circular dependencies\n"
1216
- f"2. Using delayed imports inside methods rather than at module level\n"
1217
- f"3. Using dependency injection to break the cycle\n"
1218
- f"Original error: {import_msg}"
1219
- ) from e
1220
- else:
1221
- raise OrionisContainerException(
1222
- f"Import error while resolving dependencies for '{target.__name__}' in module '{module_name}':\n"
1223
- f"{import_msg}"
1224
- ) from e
1225
-
1226
- except Exception as e:
1227
-
1228
- # Get more context about where the error occurred
1229
- target_type = "class" if isinstance(target, type) else "function"
1230
- target_name = target.__name__ if hasattr(target, '__name__') else str(target)
1231
- module_name = target.__module__ if hasattr(target, '__module__') else "unknown module"
628
+ # Get the binding for the requested abstract type or alias
629
+ binding = self.getBinding(abstract_or_alias)
1232
630
 
1233
- raise OrionisContainerException(
1234
- f"Error resolving dependencies for {target_type} '{target_name}' in '{module_name}':\n"
1235
- f"{str(e)}\n"
1236
- f"Check that all dependencies are properly registered in the container."
1237
- ) from e
631
+ # Resolve the binding using the Resolver class
632
+ return Resolver(self).resolve(
633
+ binding,
634
+ *args,
635
+ **kwargs
636
+ )