orionis 0.318.0__py3-none-any.whl → 0.320.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,16 +1,26 @@
1
+ import threading
1
2
  from typing import Any, Callable
3
+ from orionis.container.contracts.container import IContainer
2
4
  from orionis.container.entities.binding import Binding
3
5
  from orionis.container.enums.lifetimes import Lifetime
4
6
  from orionis.container.exceptions.container_exception import OrionisContainerException
5
7
  from orionis.container.exceptions.type_error_exception import OrionisContainerTypeError
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
6
16
  from orionis.services.introspection.abstract.reflection_abstract import ReflectionAbstract
7
17
  from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
8
18
  from orionis.services.introspection.concretes.reflection_concrete import ReflectionConcrete
19
+ from orionis.services.introspection.dependencies.entities.resolved_dependencies import ResolvedDependency
9
20
  from orionis.services.introspection.instances.reflection_instance import ReflectionInstance
10
21
  from orionis.services.introspection.reflection import Reflection
11
- import threading
12
22
 
13
- class Container:
23
+ class Container(IContainer):
14
24
 
15
25
  # Singleton instance of the container.
16
26
  # This is a class variable that holds the single instance of the Container class.
@@ -26,7 +36,11 @@ class Container:
26
36
  # regardless of how many times the class is instantiated.
27
37
  _initialized = False
28
38
 
29
- def __new__(cls, *args, **kwargs):
39
+ def __new__(
40
+ cls,
41
+ *args,
42
+ **kwargs
43
+ ) -> 'Container':
30
44
  """
31
45
  Creates and returns a singleton instance of the class.
32
46
 
@@ -45,339 +59,53 @@ class Container:
45
59
  Container
46
60
  The singleton instance of the class.
47
61
  """
62
+
63
+ # Check if the class has an existing instance
48
64
  if cls._instance is None:
65
+
66
+ # Acquire the lock to ensure thread safety during instance creation
49
67
  with cls._lock:
68
+
69
+ # If the instance is still None, create a new instance
50
70
  if cls._instance is None:
71
+
72
+ # Call the superclass's __new__ method to create a new instance
51
73
  cls._instance = super(Container, cls).__new__(cls)
74
+
75
+ # Return the existing instance of the class
52
76
  return cls._instance
53
77
 
54
- def __init__(self):
78
+ def __init__(
79
+ self
80
+ ) -> None:
55
81
  """
56
82
  Initializes a new instance of the container.
57
83
 
58
84
  This constructor sets up the internal dictionaries for bindings and aliases,
59
85
  ensuring that these are only initialized once per class. The initialization
60
86
  is guarded by the `_initialized` class attribute to prevent redundant setup.
87
+ The container also registers itself as a service to allow for injection.
61
88
 
62
89
  Notes
63
90
  -----
64
91
  - The `__bindings` dictionary is used to store service bindings.
65
92
  - The `__aliasses` dictionary is used to store service aliases.
66
93
  - Initialization occurs only once per class, regardless of the number of instances.
94
+ - The container registers itself under the IContainer interface to allow for dependency injection.
67
95
  """
96
+
97
+ # Check if the class has been initialized
68
98
  if not self.__class__._initialized:
99
+
100
+ # Initialize the container's internal state
69
101
  self.__bindings = {}
70
102
  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]
105
-
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:
144
- """
145
- Ensures that the provided value is a valid alias of type str and does not contain invalid characters.
146
-
147
- Parameters
148
- ----------
149
- value : Any
150
- The value to check.
151
-
152
- Raises
153
- ------
154
- OrionisContainerTypeError
155
- If the value is not of type str or contains invalid characters.
156
-
157
- Notes
158
- -----
159
- This method validates that a given value is a string and does not contain characters
160
- that could cause errors when resolving dependencies (e.g., whitespace, special symbols).
161
- """
162
-
163
- # Check if the value is a string
164
- if not isinstance(value, str):
165
- raise OrionisContainerTypeError(
166
- f"Expected a string type for alias, but got {type(value).__name__} instead."
167
- )
168
-
169
- # Define a set of invalid characters for aliases
170
- invalid_chars = set(' \t\n\r\x0b\x0c!@#$%^&*()[]{};:,/<>?\\|`~"\'')
171
- if any(char in invalid_chars for char in value):
172
- raise OrionisContainerTypeError(
173
- f"Alias '{value}' contains invalid characters. "
174
- "Aliases must not contain whitespace or special symbols."
175
- )
176
-
177
- def __ensureAbstractClass(
178
- self,
179
- abstract: Callable[..., Any],
180
- lifetime: str
181
- ) -> None:
182
- """
183
- Ensures that the provided abstract is an abstract class.
184
-
185
- Parameters
186
- ----------
187
- abstract : Callable[..., Any]
188
- The class intended to represent the abstract type.
189
- lifetime : str
190
- The service lifetime descriptor, used for error messages.
191
-
192
- Raises
193
- ------
194
- OrionisContainerTypeError
195
- If the abstract class check fails.
196
- """
197
- try:
198
- ReflectionAbstract.ensureIsAbstractClass(abstract)
199
- except Exception as e:
200
- raise OrionisContainerTypeError(
201
- f"Unexpected error registering {lifetime} service: {e}"
202
- ) from e
203
-
204
- def __ensureConcreteClass(
205
- self,
206
- concrete: Callable[..., Any],
207
- lifetime: str
208
- ) -> None:
209
- """
210
- Ensures that the provided concrete is a concrete (non-abstract) class.
211
-
212
- Parameters
213
- ----------
214
- concrete : Callable[..., Any]
215
- The class intended to represent the concrete implementation.
216
- lifetime : str
217
- The service lifetime descriptor, used for error messages.
218
-
219
- Raises
220
- ------
221
- OrionisContainerTypeError
222
- If the concrete class check fails.
223
- """
224
- try:
225
- ReflectionConcrete.ensureIsConcreteClass(concrete)
226
- except Exception as e:
227
- raise OrionisContainerTypeError(
228
- f"Unexpected error registering {lifetime} service: {e}"
229
- ) from e
230
-
231
- def __ensureIsSubclass(
232
- self,
233
- abstract: Callable[..., Any],
234
- concrete: Callable[..., Any]
235
- ) -> None:
236
- """
237
- Validates that the concrete class is a subclass of the provided abstract class.
238
-
239
- Parameters
240
- ----------
241
- abstract : Callable[..., Any]
242
- The abstract base class or interface.
243
- concrete : Callable[..., Any]
244
- The concrete implementation class to check.
245
-
246
- Raises
247
- ------
248
- OrionisContainerException
249
- If the concrete class is NOT a subclass of the abstract class.
250
-
251
- Notes
252
- -----
253
- This method ensures that the concrete implementation inherits from the abstract class,
254
- which is required for proper dependency injection and interface enforcement.
255
- """
256
- if not issubclass(concrete, abstract):
257
- raise OrionisContainerException(
258
- "The concrete class must inherit from the provided abstract class. "
259
- "Please ensure that the concrete class is a subclass of the specified abstract class."
260
- )
261
-
262
- def __ensureIsNotSubclass(
263
- self,
264
- abstract: Callable[..., Any],
265
- concrete: Callable[..., Any]
266
- ) -> None:
267
- """
268
- Validates that the concrete class is NOT a subclass of the provided abstract class.
269
-
270
- Parameters
271
- ----------
272
- abstract : Callable[..., Any]
273
- The abstract base class or interface.
274
- concrete : Callable[..., Any]
275
- The concrete implementation class to check.
276
-
277
- Raises
278
- ------
279
- OrionisContainerException
280
- If the concrete class IS a subclass of the abstract class.
281
-
282
- Notes
283
- -----
284
- This method ensures that the concrete implementation does NOT inherit from the abstract class.
285
- """
286
- if issubclass(concrete, abstract):
287
- raise OrionisContainerException(
288
- "The concrete class must NOT inherit from the provided abstract class. "
289
- "Please ensure that the concrete class is not a subclass of the specified abstract class."
290
- )
291
-
292
- def __ensureInstance(
293
- self,
294
- instance: Any
295
- ) -> None:
296
- """
297
- Ensures that the provided object is a valid instance.
298
-
299
- Parameters
300
- ----------
301
- instance : Any
302
- The object to be validated as an instance.
303
-
304
- Raises
305
- ------
306
- OrionisContainerTypeError
307
- If the provided object is not a valid instance.
308
-
309
- Notes
310
- -----
311
- This method uses ReflectionInstance to verify that the given object
312
- is a proper instance (not a class or abstract type). If the check fails,
313
- an OrionisContainerTypeError is raised with a descriptive message.
314
- """
315
- try:
316
- ReflectionInstance.ensureIsInstance(instance)
317
- except Exception as e:
318
- raise OrionisContainerTypeError(
319
- f"Error registering instance: {e}"
320
- ) from e
321
-
322
- def __ensureImplementation(
323
- self,
324
- *,
325
- abstract: Callable[..., Any] = None,
326
- concrete: Callable[..., Any] = None,
327
- instance: Any = None
328
- ) -> None:
329
- """
330
- Ensures that a concrete class or instance implements all abstract methods defined in an abstract class.
331
-
332
- Parameters
333
- ----------
334
- abstract : Callable[..., Any]
335
- The abstract class containing abstract methods.
336
- concrete : Callable[..., Any], optional
337
- The concrete class that should implement the abstract methods.
338
- instance : Any, optional
339
- The instance that should implement the abstract methods.
340
-
341
- Raises
342
- ------
343
- OrionisContainerException
344
- If the concrete class or instance does not implement all abstract methods defined in the abstract class.
345
-
346
- Notes
347
- -----
348
- This method checks that all abstract methods in the given abstract class are implemented
349
- in the provided concrete class or instance. If any methods are missing, an exception is raised with
350
- details about the missing implementations.
351
- """
352
- if abstract is None:
353
- raise OrionisContainerException("Abstract class must be provided for implementation check.")
354
-
355
- abstract_methods = getattr(abstract, '__abstractmethods__', set())
356
- if not abstract_methods:
357
- raise OrionisContainerException(
358
- f"The abstract class '{abstract.__name__}' does not define any abstract methods. "
359
- "An abstract class must have at least one abstract method."
360
- )
361
-
362
- target = concrete if concrete is not None else instance
363
- if target is None:
364
- raise OrionisContainerException("Either concrete class or instance must be provided for implementation check.")
365
-
366
- target_class = target if Reflection.isClass(target) else target.__class__
367
- target_name = target_class.__name__
368
- abstract_name = abstract.__name__
369
103
 
370
- not_implemented = []
371
- for method in abstract_methods:
372
- if not hasattr(target, str(method).replace(f"_{abstract_name}", f"_{target_name}")):
373
- not_implemented.append(method)
104
+ # Set the initialized flag to True to prevent re-initialization
105
+ self.__class__._initialized = True
374
106
 
375
- if not_implemented:
376
- formatted_methods = "\n • " + "\n • ".join(not_implemented)
377
- raise OrionisContainerException(
378
- f"'{target_name}' does not implement the following abstract methods defined in '{abstract_name}':{formatted_methods}\n"
379
- "Please ensure that all abstract methods are implemented."
380
- )
107
+ # Register the container itself as a service
108
+ self.instance(IContainer, self)
381
109
 
382
110
  def transient(
383
111
  self,
@@ -419,28 +147,28 @@ class Container:
419
147
  """
420
148
 
421
149
  # Ensure that abstract is an abstract class
422
- self.__ensureAbstractClass(abstract, Lifetime.TRANSIENT)
150
+ IsAbstractClass(abstract, Lifetime.TRANSIENT)
423
151
 
424
152
  # Ensure that concrete is a concrete class
425
- self.__ensureConcreteClass(concrete, Lifetime.TRANSIENT)
153
+ IsConcreteClass(concrete, Lifetime.TRANSIENT)
426
154
 
427
155
  # Ensure that concrete is NOT a subclass of abstract
428
156
  if enforce_decoupling:
429
- self.__ensureIsNotSubclass(abstract, concrete)
157
+ IsNotSubclass(abstract, concrete)
430
158
 
431
159
  # Validate that concrete is a subclass of abstract
432
160
  else:
433
- self.__ensureIsSubclass(abstract, concrete)
161
+ IsSubclass(abstract, concrete)
434
162
 
435
163
  # Ensure implementation
436
- self.__ensureImplementation(
164
+ ImplementsAbstractMethods(
437
165
  abstract=abstract,
438
166
  concrete=concrete
439
167
  )
440
168
 
441
169
  # Ensure that the alias is a valid string if provided
442
170
  if alias:
443
- self.__ensureAliasType(alias)
171
+ IsValidAlias(alias)
444
172
 
445
173
  # Extract the module and class name for the alias
446
174
  else:
@@ -448,7 +176,7 @@ class Container:
448
176
  alias = rf_asbtract.getModuleWithClassName()
449
177
 
450
178
  # If the service is already registered, drop it
451
- self.__dropService(abstract, alias)
179
+ self.drop(abstract, alias)
452
180
 
453
181
  # Register the service with transient lifetime
454
182
  self.__bindings[abstract] = Binding(
@@ -504,34 +232,34 @@ class Container:
504
232
  """
505
233
 
506
234
  # Ensure that abstract is an abstract class
507
- self.__ensureAbstractClass(abstract, Lifetime.SINGLETON)
235
+ IsAbstractClass(abstract, Lifetime.SINGLETON)
508
236
 
509
237
  # Ensure that concrete is a concrete class
510
- self.__ensureConcreteClass(concrete, Lifetime.SINGLETON)
238
+ IsConcreteClass(concrete, Lifetime.SINGLETON)
511
239
 
512
240
  # Ensure that concrete is NOT a subclass of abstract
513
241
  if enforce_decoupling:
514
- self.__ensureIsNotSubclass(abstract, concrete)
242
+ IsNotSubclass(abstract, concrete)
515
243
 
516
244
  # Validate that concrete is a subclass of abstract
517
245
  else:
518
- self.__ensureIsSubclass(abstract, concrete)
246
+ IsSubclass(abstract, concrete)
519
247
 
520
248
  # Ensure implementation
521
- self.__ensureImplementation(
249
+ ImplementsAbstractMethods(
522
250
  abstract=abstract,
523
251
  concrete=concrete
524
252
  )
525
253
 
526
254
  # Ensure that the alias is a valid string if provided
527
255
  if alias:
528
- self.__ensureAliasType(alias)
256
+ IsValidAlias(alias)
529
257
  else:
530
258
  rf_asbtract = ReflectionAbstract(abstract)
531
259
  alias = rf_asbtract.getModuleWithClassName()
532
260
 
533
261
  # If the service is already registered, drop it
534
- self.__dropService(abstract, alias)
262
+ self.drop(abstract, alias)
535
263
 
536
264
  # Register the service with singleton lifetime
537
265
  self.__bindings[abstract] = Binding(
@@ -587,34 +315,34 @@ class Container:
587
315
  """
588
316
 
589
317
  # Ensure that abstract is an abstract class
590
- self.__ensureAbstractClass(abstract, Lifetime.SCOPED)
318
+ IsAbstractClass(abstract, Lifetime.SCOPED)
591
319
 
592
320
  # Ensure that concrete is a concrete class
593
- self.__ensureConcreteClass(concrete, Lifetime.SCOPED)
321
+ IsConcreteClass(concrete, Lifetime.SCOPED)
594
322
 
595
323
  # Ensure that concrete is NOT a subclass of abstract
596
324
  if enforce_decoupling:
597
- self.__ensureIsNotSubclass(abstract, concrete)
325
+ IsNotSubclass(abstract, concrete)
598
326
 
599
327
  # Validate that concrete is a subclass of abstract
600
328
  else:
601
- self.__ensureIsSubclass(abstract, concrete)
329
+ IsSubclass(abstract, concrete)
602
330
 
603
331
  # Ensure implementation
604
- self.__ensureImplementation(
332
+ ImplementsAbstractMethods(
605
333
  abstract=abstract,
606
334
  concrete=concrete
607
335
  )
608
336
 
609
337
  # Ensure that the alias is a valid string if provided
610
338
  if alias:
611
- self.__ensureAliasType(alias)
339
+ IsValidAlias(alias)
612
340
  else:
613
341
  rf_asbtract = ReflectionAbstract(abstract)
614
342
  alias = rf_asbtract.getModuleWithClassName()
615
343
 
616
344
  # If the service is already registered, drop it
617
- self.__dropService(abstract, alias)
345
+ self.drop(abstract, alias)
618
346
 
619
347
  # Register the service with scoped lifetime
620
348
  self.__bindings[abstract] = Binding(
@@ -668,34 +396,34 @@ class Container:
668
396
  """
669
397
 
670
398
  # Ensure that the abstract is an abstract class
671
- self.__ensureAbstractClass(abstract, f"Instance {Lifetime.SINGLETON}")
399
+ IsAbstractClass(abstract, f"Instance {Lifetime.SINGLETON}")
672
400
 
673
401
  # Ensure that the instance is a valid instance
674
- self.__ensureInstance(instance)
402
+ IsInstance(instance)
675
403
 
676
404
  # Ensure that instance is NOT a subclass of abstract
677
405
  if enforce_decoupling:
678
- self.__ensureIsNotSubclass(abstract, instance.__class__)
406
+ IsNotSubclass(abstract, instance.__class__)
679
407
 
680
408
  # Validate that instance is a subclass of abstract
681
409
  else:
682
- self.__ensureIsSubclass(abstract, instance.__class__)
410
+ IsSubclass(abstract, instance.__class__)
683
411
 
684
412
  # Ensure implementation
685
- self.__ensureImplementation(
413
+ ImplementsAbstractMethods(
686
414
  abstract=abstract,
687
415
  instance=instance
688
416
  )
689
417
 
690
418
  # Ensure that the alias is a valid string if provided
691
419
  if alias:
692
- self.__ensureAliasType(alias)
420
+ IsValidAlias(alias)
693
421
  else:
694
422
  rf_asbtract = ReflectionAbstract(abstract)
695
423
  alias = rf_asbtract.getModuleWithClassName()
696
424
 
697
425
  # If the service is already registered, drop it
698
- self.__dropService(abstract, alias)
426
+ self.drop(abstract, alias)
699
427
 
700
428
  # Register the instance with the abstract type
701
429
  self.__bindings[abstract] = Binding(
@@ -715,7 +443,7 @@ class Container:
715
443
  def function(
716
444
  self,
717
445
  alias: str,
718
- function: Callable[..., Any],
446
+ fn: Callable[..., Any],
719
447
  *,
720
448
  lifetime: Lifetime = Lifetime.TRANSIENT
721
449
  ) -> bool:
@@ -726,7 +454,7 @@ class Container:
726
454
  ----------
727
455
  alias : str
728
456
  The alias to register the function under.
729
- function : Callable[..., Any]
457
+ fn : Callable[..., Any]
730
458
  The function or factory to register.
731
459
  lifetime : Lifetime, optional
732
460
  The lifetime of the function registration (default is TRANSIENT).
@@ -760,13 +488,13 @@ class Container:
760
488
  )
761
489
 
762
490
  # Ensure that the alias is a valid string
763
- self.__ensureAliasType(alias)
491
+ IsValidAlias(alias)
764
492
 
765
493
  # Validate that the function is callable
766
- self.__ensureIsCallable(function)
494
+ IsCallable(fn)
767
495
 
768
496
  # Inspect the function signature
769
- params = ReflectionCallable(function).getDependencies()
497
+ params = ReflectionCallable(fn).getDependencies()
770
498
 
771
499
  # If the function requires arguments, only allow TRANSIENT
772
500
  if (len(params.resolved) + len(params.unresolved)) > 0 and lifetime != Lifetime.TRANSIENT:
@@ -775,11 +503,11 @@ class Container:
775
503
  )
776
504
 
777
505
  # If the service is already registered, drop it
778
- self.__dropService(None, alias)
506
+ self.drop(None, alias)
779
507
 
780
508
  # Register the function with the specified alias and lifetime
781
509
  self.__bindings[alias] = Binding(
782
- function=function,
510
+ function=fn,
783
511
  lifetime=lifetime,
784
512
  alias=alias
785
513
  )
@@ -816,11 +544,104 @@ class Container:
816
544
  or abstract_or_alias in self.__aliasses
817
545
  )
818
546
 
547
+ def __getService(
548
+ self,
549
+ abstract_or_alias: Any
550
+ ) -> Binding:
551
+ """
552
+ Retrieves the binding for the requested abstract type or alias.
553
+
554
+ Parameters
555
+ ----------
556
+ abstract_or_alias : Any
557
+ The abstract class, interface, or alias (str) to retrieve.
558
+
559
+ Returns
560
+ -------
561
+ Binding
562
+ The binding associated with the requested abstract type or alias.
563
+ """
564
+ return self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
565
+
566
+ def __getFirstService(
567
+ self,
568
+ abstract_or_aliasses: list
569
+ ) -> Binding:
570
+ """
571
+ Retrieves the first binding from a list of abstract types or aliases.
572
+
573
+ Parameters
574
+ ----------
575
+ abstract_or_aliasses : list
576
+ A list of abstract classes, interfaces, or aliases (str) to retrieve.
577
+
578
+ Returns
579
+ -------
580
+ Binding
581
+ The first binding found in the container for the provided abstract types or aliases.
582
+ """
583
+ for item in abstract_or_aliasses:
584
+ binding = self.__getService(item)
585
+ if binding:
586
+ return binding
587
+ return None
588
+
589
+ def drop(
590
+ self,
591
+ abstract: Callable[..., Any] = None,
592
+ alias: str = None
593
+ ) -> None:
594
+ """
595
+ Drops a service from the container by removing its bindings and aliases.
596
+ This method allows removing registered services from the dependency injection container,
597
+ either by their abstract type or by their alias. When a service is dropped,
598
+ all its bindings and aliases are removed from the container.
599
+
600
+ Warning
601
+ -------
602
+ Using this method irresponsibly can severely damage the system's logic.
603
+ Only use it when you are certain about the consequences, as removing
604
+ critical services may lead to system failures and unexpected behavior.
605
+ abstract : Callable[..., Any], optional
606
+ The abstract type or interface to be removed from the container.
607
+ If provided, both the binding and the default alias for this type will be removed.
608
+ The alias of the service to be removed. If provided, both the alias entry
609
+ and any associated binding will be removed.
610
+
611
+ Notes
612
+ -----
613
+ At least one parameter (abstract or alias) must be provided for the method to take effect.
614
+ If both are provided, both will be processed independently.
615
+ """
616
+
617
+ # If abstract is provided
618
+ if abstract:
619
+
620
+ # Remove the abstract service from the bindings if it exists
621
+ if abstract in self.__bindings:
622
+ del self.__bindings[abstract]
623
+
624
+ # Remove the default alias (module + class name) from aliases if it exists
625
+ abs_alias = ReflectionAbstract(abstract).getModuleWithClassName()
626
+ if abs_alias in self.__aliasses:
627
+ del self.__aliasses[abs_alias]
628
+
629
+ # If a custom alias is provided
630
+ if alias:
631
+
632
+ # Remove it from the aliases dictionary if it exists
633
+ if alias in self.__aliasses:
634
+ del self.__aliasses[alias]
635
+
636
+ # Remove the binding associated with the alias
637
+ if alias in self.__bindings:
638
+ del self.__bindings[alias]
639
+
819
640
  def make(
820
641
  self,
821
642
  abstract_or_alias: Any,
822
- *args,
823
- **kwargs
643
+ *args: tuple,
644
+ **kwargs: dict
824
645
  ) -> Any:
825
646
  """
826
647
  Resolves and returns an instance of the requested service.
@@ -844,11 +665,341 @@ class Container:
844
665
  OrionisContainerException
845
666
  If the requested service is not registered in the container.
846
667
  """
847
- if not self.bound(abstract_or_alias):
668
+ # Retrieve the binding for the requested abstract or alias
669
+ binding = self.__getService(abstract_or_alias)
670
+
671
+ # Check if the requested service is registered in the container
672
+ if not binding:
848
673
  raise OrionisContainerException(
849
674
  f"The requested service '{abstract_or_alias}' is not registered in the container."
850
675
  )
851
676
 
852
- binding = self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
677
+ # Handle based on binding type and lifetime
678
+ if binding.lifetime == Lifetime.TRANSIENT:
679
+ return self.__resolveTransient(binding, *args, **kwargs)
680
+ elif binding.lifetime == Lifetime.SINGLETON:
681
+ return self.__resolveSingleton(binding, *args, **kwargs)
682
+ elif binding.lifetime == Lifetime.SCOPED:
683
+ # TODO: Implement scoped lifetime resolution
684
+ raise OrionisContainerException(
685
+ "Scoped lifetime resolution is not yet implemented."
686
+ )
687
+
688
+ def __resolveTransient(self, binding: Binding, *args, **kwargs) -> Any:
689
+ """
690
+ Resolves a service with transient lifetime.
691
+
692
+ Parameters
693
+ ----------
694
+ binding : Binding
695
+ The binding to resolve.
696
+ *args : tuple
697
+ Positional arguments to pass to the constructor.
698
+ **kwargs : dict
699
+ Keyword arguments to pass to the constructor.
853
700
 
854
- print(binding)
701
+ Returns
702
+ -------
703
+ Any
704
+ A new instance of the requested service.
705
+ """
706
+
707
+ # Check if the binding has a concrete class or function defined
708
+ if binding.concrete:
709
+ if args or kwargs:
710
+ return self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
711
+ else:
712
+ return self.__instantiateConcreteReflective(binding.concrete)
713
+
714
+ # If the binding has a function defined
715
+ elif binding.function:
716
+ if args or kwargs:
717
+ return self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
718
+ else:
719
+ return self.__instantiateCallableReflective(binding.function)
720
+
721
+ # If neither concrete class nor function is defined
722
+ else:
723
+ raise OrionisContainerException(
724
+ "Cannot resolve transient binding: neither a concrete class nor a function is defined."
725
+ )
726
+
727
+ def __resolveSingleton(self, binding: Binding, *args, **kwargs) -> Any:
728
+ """
729
+ Resolves a service with singleton lifetime.
730
+
731
+ Parameters
732
+ ----------
733
+ binding : Binding
734
+ The binding to resolve.
735
+ *args : tuple
736
+ Positional arguments to pass to the constructor (only used if instance doesn't exist yet).
737
+ **kwargs : dict
738
+ Keyword arguments to pass to the constructor (only used if instance doesn't exist yet).
739
+
740
+ Returns
741
+ -------
742
+ Any
743
+ The singleton instance of the requested service.
744
+ """
745
+ # Return existing instance if available
746
+ if binding.instance:
747
+ return binding.instance
748
+
749
+ # Create instance if needed
750
+ if binding.concrete:
751
+ if args or kwargs:
752
+ binding.instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
753
+ else:
754
+ binding.instance = self.__instantiateConcreteReflective(binding.concrete)
755
+ return binding.instance
756
+
757
+ # If the binding has a function defined
758
+ elif binding.function:
759
+ if args or kwargs:
760
+ result = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
761
+ else:
762
+ result = self.__instantiateCallableReflective(binding.function)
763
+
764
+ # Store the result directly as the singleton instance
765
+ # We don't automatically invoke factory function results anymore
766
+ binding.instance = result
767
+ return binding.instance
768
+
769
+ # If neither concrete class nor function is defined
770
+ else:
771
+ raise OrionisContainerException(
772
+ "Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
773
+ )
774
+
775
+ def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
776
+ """
777
+ Instantiates a concrete class with the provided arguments.
778
+
779
+ Parameters
780
+ ----------
781
+ concrete : Callable[..., Any]
782
+ Class to instantiate.
783
+ *args : tuple
784
+ Positional arguments to pass to the constructor.
785
+ **kwargs : dict
786
+ Keyword arguments to pass to the constructor.
787
+
788
+ Returns
789
+ -------
790
+ object
791
+ A new instance of the specified concrete class.
792
+ """
793
+
794
+ # try to instantiate the concrete class with the provided arguments
795
+ try:
796
+
797
+ # If the concrete is a class, instantiate it directly
798
+ return concrete(*args, **kwargs)
799
+
800
+ except TypeError as e:
801
+
802
+ # If instantiation fails, use ReflectionConcrete to get class name and constructor signature
803
+ rf_concrete = ReflectionConcrete(concrete)
804
+ class_name = rf_concrete.getClassName()
805
+ signature = rf_concrete.getConstructorSignature()
806
+
807
+ # Raise an exception with detailed information about the failure
808
+ raise OrionisContainerException(
809
+ f"Failed to instantiate [{class_name}] with the provided arguments: {e}\n"
810
+ f"Expected constructor signature: [{signature}]"
811
+ ) from e
812
+
813
+ def __instantiateCallableWithArgs(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
814
+ """
815
+ Invokes a callable with the provided arguments.
816
+
817
+ Parameters
818
+ ----------
819
+ fn : Callable[..., Any]
820
+ The callable to invoke.
821
+ *args : tuple
822
+ Positional arguments to pass to the callable.
823
+ **kwargs : dict
824
+ Keyword arguments to pass to the callable.
825
+
826
+ Returns
827
+ -------
828
+ Any
829
+ The result of the callable.
830
+ """
831
+
832
+ # Try to invoke the callable with the provided arguments
833
+ try:
834
+
835
+ # If the callable is a function, invoke it directly
836
+ return fn(*args, **kwargs)
837
+
838
+ except TypeError as e:
839
+
840
+ # If invocation fails, use ReflectionCallable to get function name and signature
841
+ rf_callable = ReflectionCallable(fn)
842
+ function_name = rf_callable.getName()
843
+ signature = rf_callable.getSignature()
844
+
845
+ # Raise an exception with detailed information about the failure
846
+ raise OrionisContainerException(
847
+ f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
848
+ f"Expected function signature: [{signature}]"
849
+ ) from e
850
+
851
+ def __instantiateConcreteReflective(self, concrete: Callable[..., Any]) -> Any:
852
+ """
853
+ Instantiates a concrete class reflectively, resolving its dependencies from the container.
854
+
855
+ Parameters
856
+ ----------
857
+ concrete : Callable[..., Any]
858
+ The concrete class to instantiate.
859
+
860
+ Returns
861
+ -------
862
+ Any
863
+ A new instance of the concrete class.
864
+ """
865
+ # Resolve dependencies for the concrete class
866
+ params = self.__resolveDependencies(concrete, is_class=True)
867
+
868
+ # Instantiate the concrete class with resolved dependencies
869
+ return concrete(**params)
870
+
871
+ def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
872
+ """
873
+ Invokes a callable reflectively, resolving its dependencies from the container.
874
+
875
+ Parameters
876
+ ----------
877
+ fn : Callable[..., Any]
878
+ The callable to invoke.
879
+
880
+ Returns
881
+ -------
882
+ Any
883
+ The result of the callable.
884
+ """
885
+
886
+ # Resolve dependencies for the callable
887
+ params = self.__resolveDependencies(fn, is_class=False)
888
+
889
+ # Invoke the callable with resolved dependencies
890
+ return fn(**params)
891
+
892
+ def __resolveDependencies(
893
+ self,
894
+ target: Callable[..., Any],
895
+ *,
896
+ is_class: bool = False
897
+ ) -> dict:
898
+ """
899
+ Resolves dependencies for a target callable or class.
900
+
901
+ Parameters
902
+ ----------
903
+ target : Callable[..., Any]
904
+ The target callable or class whose dependencies to resolve.
905
+ is_class : bool, optional
906
+ Whether the target is a class (True) or a callable (False).
907
+
908
+ Returns
909
+ -------
910
+ dict
911
+ A dictionary of resolved dependencies.
912
+ """
913
+ try:
914
+
915
+ # Use ReflectionConcrete for classes and ReflectionCallable for callables
916
+ if is_class:
917
+ reflection = ReflectionConcrete(target)
918
+ dependencies = reflection.getConstructorDependencies()
919
+ name = reflection.getClassName()
920
+
921
+ # If the target is a callable, use ReflectionCallable
922
+ else:
923
+ reflection = ReflectionCallable(target)
924
+ dependencies = reflection.getDependencies()
925
+ name = reflection.getName()
926
+
927
+ # Check for unresolved dependencies
928
+ if dependencies.unresolved:
929
+ unresolved_args = ', '.join(dependencies.unresolved)
930
+ raise OrionisContainerException(
931
+ f"Cannot resolve '{name}' because the following required arguments are missing: [{unresolved_args}]."
932
+ )
933
+
934
+ # Resolve dependencies
935
+ params = {}
936
+ for param_name, dep in dependencies.resolved.items():
937
+
938
+ # If the dependency is a ResolvedDependency, resolve it
939
+ if isinstance(dep, ResolvedDependency):
940
+
941
+ # If the dependency is a built-in type, raise an exception
942
+ if dep.module_name == 'builtins':
943
+ raise OrionisContainerException(
944
+ f"Cannot resolve '{name}' because parameter '{param_name}' depends on built-in type '{dep.type.__name__}'."
945
+ )
946
+
947
+ # Try to resolve from container
948
+ service = self.__getFirstService([dep.type, dep.full_class_path])
949
+ if service:
950
+ params[param_name] = self.make(service.alias)
951
+
952
+ # Try to instantiate directly if it's a concrete class
953
+ elif ReflectionConcrete.isConcreteClass(dep.type):
954
+ params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=True))
955
+
956
+ # Try to call directly if it's a callable
957
+ elif callable(dep.type) and not isinstance(dep.type, type):
958
+ params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=False))
959
+
960
+ # If the dependency cannot be resolved, raise an exception
961
+ else:
962
+ raise OrionisContainerException(
963
+ f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}' for '{name}'."
964
+ )
965
+ else:
966
+ # Use default value
967
+ params[param_name] = dep
968
+
969
+ # Return the resolved parameters
970
+ return params
971
+
972
+ except ImportError as e:
973
+
974
+ # Extract module name from the error message if possible
975
+ import_msg = str(e)
976
+ module_name = target.__module__ if hasattr(target, '__module__') else "unknown module"
977
+
978
+ # Check for potential circular import patterns
979
+ if "circular import" in import_msg.lower() or "cannot import name" in import_msg.lower():
980
+ raise OrionisContainerException(
981
+ f"Circular import detected while resolving dependencies for '{target.__name__}' in module '{module_name}'.\n"
982
+ f"This typically happens when two modules import each other. Consider:\n"
983
+ f"1. Restructuring your code to avoid circular dependencies\n"
984
+ f"2. Using delayed imports inside methods rather than at module level\n"
985
+ f"3. Using dependency injection to break the cycle\n"
986
+ f"Original error: {import_msg}"
987
+ ) from e
988
+ else:
989
+ raise OrionisContainerException(
990
+ f"Import error while resolving dependencies for '{target.__name__}' in module '{module_name}':\n"
991
+ f"{import_msg}"
992
+ ) from e
993
+
994
+ except Exception as e:
995
+
996
+ # Get more context about where the error occurred
997
+ target_type = "class" if isinstance(target, type) else "function"
998
+ target_name = target.__name__ if hasattr(target, '__name__') else str(target)
999
+ module_name = target.__module__ if hasattr(target, '__module__') else "unknown module"
1000
+
1001
+ raise OrionisContainerException(
1002
+ f"Error resolving dependencies for {target_type} '{target_name}' in '{module_name}':\n"
1003
+ f"{str(e)}\n"
1004
+ f"Check that all dependencies are properly registered in the container."
1005
+ ) from e