orionis 0.139.0__py3-none-any.whl → 0.151.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.
Files changed (80) hide show
  1. orionis/framework.py +1 -1
  2. orionis/luminate/console/base/command.py +6 -3
  3. orionis/luminate/console/command_filter.py +1 -1
  4. orionis/luminate/console/commands/cache_clear.py +4 -3
  5. orionis/luminate/console/commands/schedule_work.py +1 -1
  6. orionis/luminate/console/kernel.py +1 -2
  7. orionis/luminate/console/output/console.py +1 -1
  8. orionis/luminate/console/output/executor.py +1 -1
  9. orionis/luminate/console/output/progress_bar.py +1 -1
  10. orionis/luminate/console/parser.py +1 -1
  11. orionis/luminate/container/container.py +379 -270
  12. orionis/luminate/container/container_integrity.py +235 -0
  13. orionis/luminate/container/lifetimes.py +13 -0
  14. orionis/luminate/contracts/console/{i_parser.py → parser.py} +0 -1
  15. orionis/luminate/contracts/container/container.py +346 -0
  16. orionis/luminate/contracts/container/container_integrity.py +168 -0
  17. orionis/luminate/facades/commands/commands_facade.py +1 -1
  18. orionis/luminate/facades/commands/scheduler_facade.py +1 -1
  19. orionis/luminate/facades/config/config_facade.py +1 -1
  20. orionis/luminate/facades/environment/environment_facade.py +1 -2
  21. orionis/luminate/facades/files/path_facade.py +1 -1
  22. orionis/luminate/facades/log/log_facade.py +1 -1
  23. orionis/luminate/facades/tests/tests_facade.py +1 -1
  24. orionis/luminate/foundation/config/config_bootstrapper.py +1 -1
  25. orionis/luminate/foundation/console/command_bootstrapper.py +1 -1
  26. orionis/luminate/foundation/environment/environment_bootstrapper.py +1 -1
  27. orionis/luminate/foundation/providers/service_providers_bootstrapper.py +1 -1
  28. orionis/luminate/providers/commands/reactor_commands_service_provider.py +2 -1
  29. orionis/luminate/providers/commands/scheduler_provider.py +2 -1
  30. orionis/luminate/providers/config/config_service_provider.py +2 -1
  31. orionis/luminate/providers/environment/environment__service_provider.py +2 -1
  32. orionis/luminate/providers/files/paths_provider.py +2 -1
  33. orionis/luminate/providers/log/log_service_provider.py +2 -1
  34. orionis/luminate/providers/service_provider.py +1 -2
  35. orionis/luminate/services/commands/reactor_commands_service.py +1 -2
  36. orionis/luminate/services/commands/scheduler_service.py +1 -2
  37. orionis/luminate/services/config/config_service.py +1 -2
  38. orionis/luminate/services/environment/environment_service.py +1 -2
  39. orionis/luminate/services/files/path_resolver_service.py +1 -2
  40. orionis/luminate/services/log/log_service.py +1 -2
  41. orionis/luminate/support/exception_to_dict.py +1 -1
  42. orionis/luminate/support/reflection.py +1 -1
  43. orionis/luminate/support/std.py +1 -1
  44. {orionis-0.139.0.dist-info → orionis-0.151.0.dist-info}/METADATA +1 -1
  45. {orionis-0.139.0.dist-info → orionis-0.151.0.dist-info}/RECORD +77 -76
  46. orionis/luminate/container/types.py +0 -51
  47. orionis/luminate/contracts/container/i_container.py +0 -239
  48. orionis/luminate/contracts/container/i_types.py +0 -16
  49. /orionis/luminate/contracts/console/base/{i_command.py → command.py} +0 -0
  50. /orionis/luminate/contracts/console/{i_command_filter.py → command_filter.py} +0 -0
  51. /orionis/luminate/contracts/console/{i_kernel.py → kernel.py} +0 -0
  52. /orionis/luminate/contracts/console/output/{i_console.py → console.py} +0 -0
  53. /orionis/luminate/contracts/console/output/{i_executor.py → executor.py} +0 -0
  54. /orionis/luminate/contracts/console/output/{i_progress_bar.py → progress_bar.py} +0 -0
  55. /orionis/luminate/contracts/console/{i_task_manager.py → task_manager.py} +0 -0
  56. /orionis/luminate/contracts/facades/commands/{i_commands_facade.py → commands_facade.py} +0 -0
  57. /orionis/luminate/contracts/facades/commands/{i_scheduler_facade.py → scheduler_facade.py} +0 -0
  58. /orionis/luminate/contracts/facades/config/{i_config_facade.py → config_facade.py} +0 -0
  59. /orionis/luminate/contracts/facades/environment/{i_environment_facade.py → environment_facade.py} +0 -0
  60. /orionis/luminate/contracts/facades/files/{i_path_facade.py → path_facade.py} +0 -0
  61. /orionis/luminate/contracts/facades/log/{i_log_facade.py → log_facade.py} +0 -0
  62. /orionis/luminate/contracts/facades/tests/{i_tests_facade.py → tests_facade.py} +0 -0
  63. /orionis/luminate/contracts/foundation/config/{i_config_bootstrapper.py → config_bootstrapper.py} +0 -0
  64. /orionis/luminate/contracts/foundation/console/{i_command_bootstrapper.py → command_bootstrapper.py} +0 -0
  65. /orionis/luminate/contracts/foundation/environment/{i_environment_bootstrapper.py → environment_bootstrapper.py} +0 -0
  66. /orionis/luminate/contracts/foundation/providers/{i_service_providers_bootstrapper.py → service_providers_bootstrapper.py} +0 -0
  67. /orionis/luminate/contracts/providers/{i_service_provider.py → service_provider.py} +0 -0
  68. /orionis/luminate/contracts/services/commands/{i_reactor_commands_service.py → reactor_commands_service.py} +0 -0
  69. /orionis/luminate/contracts/services/commands/{i_schedule_service.py → schedule_service.py} +0 -0
  70. /orionis/luminate/contracts/services/config/{i_config_service.py → config_service.py} +0 -0
  71. /orionis/luminate/contracts/services/environment/{i_environment_service.py → environment_service.py} +0 -0
  72. /orionis/luminate/contracts/services/files/{i_path_resolver_service.py → path_resolver_service.py} +0 -0
  73. /orionis/luminate/contracts/services/log/{i_log_service.py → log_service.py} +0 -0
  74. /orionis/luminate/contracts/support/{i_exception_to_dict.py → exception_to_dict.py} +0 -0
  75. /orionis/luminate/contracts/support/{i_reflection.py → reflection.py} +0 -0
  76. /orionis/luminate/contracts/support/{i_std.py → std.py} +0 -0
  77. {orionis-0.139.0.dist-info → orionis-0.151.0.dist-info}/LICENCE +0 -0
  78. {orionis-0.139.0.dist-info → orionis-0.151.0.dist-info}/WHEEL +0 -0
  79. {orionis-0.139.0.dist-info → orionis-0.151.0.dist-info}/entry_points.txt +0 -0
  80. {orionis-0.139.0.dist-info → orionis-0.151.0.dist-info}/top_level.txt +0 -0
@@ -1,16 +1,11 @@
1
1
  import inspect
2
- from collections import deque
3
2
  from threading import Lock
4
- from typing import Callable, Any, Dict, get_args, get_origin
5
- from orionis.luminate.contracts.container.i_container import IContainer
3
+ from typing import Callable, Any, Dict, Deque, Optional, Type, get_origin, get_args
4
+ from collections import deque
5
+ from orionis.luminate.container.container_integrity import ContainerIntegrity
6
+ from orionis.luminate.container.lifetimes import Lifetime
6
7
  from orionis.luminate.container.exception import OrionisContainerException, OrionisContainerValueError, OrionisContainerTypeError
7
- from orionis.luminate.container.types import Types
8
-
9
- BINDING = 'binding'
10
- TRANSIENT = 'transient'
11
- SINGLETON = 'singleton'
12
- SCOPED = 'scoped'
13
- INSTANCE = 'instance'
8
+ from orionis.luminate.contracts.container.container import IContainer
14
9
 
15
10
  class Container(IContainer):
16
11
  """
@@ -23,399 +18,494 @@ class Container(IContainer):
23
18
  _instance = None
24
19
  _lock = Lock()
25
20
 
21
+ @classmethod
22
+ def destroy(cls):
23
+ """
24
+ Destroys the current instance of the container.
25
+
26
+ This method resets the singleton instance, effectively clearing all registered
27
+ services and instances.
28
+
29
+ Examples
30
+ --------
31
+ >>> Container.destroy()
32
+ """
33
+ cls._instance = None
34
+
26
35
  @classmethod
27
36
  def reset(cls):
28
37
  """
29
- Reset the container instance to None, allowing a new instance to be created.
38
+ Resets the container to its initial state.
39
+
40
+ This method destroys the current instance and immediately creates a new one.
41
+
42
+ Examples
43
+ --------
44
+ >>> Container.reset()
30
45
  """
31
46
  cls._instance = None
32
47
  super().__new__(cls)
33
48
 
34
49
  def __new__(cls):
50
+ """
51
+ Create a new instance of the container.
52
+ """
35
53
  if cls._instance is None:
36
54
  with cls._lock:
37
55
  if cls._instance is None:
38
56
  cls._instance = super().__new__(cls)
39
- cls._instance._bindings = {}
40
- cls._instance._transients = {}
41
- cls._instance._singletons = {}
42
- cls._instance._scoped_services = {}
43
- cls._instance._instances = {}
44
- cls._instance._aliases = {}
45
57
  cls._instance._scoped_instances = {}
46
- cls._instance._validate_types = Types()
58
+ cls._instance._singleton_instances = {}
59
+ cls._instance._instances_services = {}
60
+ cls._instance._transient_services = {}
61
+ cls._instance._scoped_services = {}
62
+ cls._instance._singleton_services = {}
63
+ cls._instance._aliases_services = {}
47
64
  return cls._instance
48
65
 
49
- def _ensureNotMain(self, concrete: Callable[..., Any]) -> str:
66
+ def bind(self, abstract: Callable[..., Any], concrete: Callable[..., Any], lifetime: str = Lifetime.TRANSIENT.value) -> None:
50
67
  """
51
- Ensure that a class is not defined in the main script.
68
+ Binds an abstract type to a concrete implementation with a specified lifetime.
52
69
 
53
70
  Parameters
54
71
  ----------
72
+ abstract : Callable[..., Any]
73
+ The abstract base type or alias to be bound.
55
74
  concrete : Callable[..., Any]
56
- The class or function to check.
57
-
58
- Returns
59
- -------
60
- str
61
- The fully qualified name of the class.
75
+ The concrete implementation to associate with the abstract type.
76
+ lifetime : str
77
+ The lifecycle of the binding. Must be one of 'transient', 'scoped', or 'singleton'.
62
78
 
63
79
  Raises
64
80
  ------
65
81
  OrionisContainerValueError
66
- If the class is defined in the main module.
82
+ If an invalid lifetime is provided or the concrete implementation is None.
83
+
84
+ Examples
85
+ --------
86
+ >>> container.bind(MyService, MyServiceImplementation, "singleton")
67
87
  """
68
- if concrete.__module__ == "__main__":
69
- raise OrionisContainerValueError("Cannot register a class from the main module in the container.")
70
88
 
71
- def _ensureUniqueService(self, obj: Any) -> None:
89
+ if lifetime not in [member.value for member in Lifetime]:
90
+ raise OrionisContainerValueError(f"Invalid lifetime type '{lifetime}'.")
91
+
92
+ if concrete is None:
93
+ raise OrionisContainerValueError("Concrete implementation cannot be None when binding a service.")
94
+
95
+ abstract = abstract or concrete
96
+ ContainerIntegrity.ensureIsCallable(concrete)
97
+ ContainerIntegrity.ensureNotMain(concrete)
98
+
99
+ service_entry = {
100
+ "concrete": concrete,
101
+ "async": inspect.iscoroutinefunction(concrete)
102
+ }
103
+
104
+ service_registry = {
105
+ Lifetime.TRANSIENT.value: self._transient_services,
106
+ Lifetime.SCOPED.value: self._scoped_services,
107
+ Lifetime.SINGLETON.value: self._singleton_services
108
+ }
109
+
110
+ if ContainerIntegrity.isAbstract(abstract):
111
+ ContainerIntegrity.ensureImplementation(abstract, concrete)
112
+ service_registry[lifetime][abstract] = service_entry
113
+ return
114
+
115
+ if ContainerIntegrity.isAlias(abstract):
116
+ service_registry[lifetime][abstract] = service_entry
117
+ return
118
+
119
+ raise OrionisContainerValueError(f"Invalid abstract type '{abstract}'. It must be a valid alias or an abstract class.")
120
+
121
+ def transient(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
72
122
  """
73
- Ensure that a service is not already registered.
123
+ Registers a service with a transient lifetime.
74
124
 
75
125
  Parameters
76
126
  ----------
77
- obj : Any
78
- The service to check.
127
+ abstract : Callable[..., Any]
128
+ The abstract base type or alias to be bound.
129
+ concrete : Callable[..., Any]
130
+ The concrete implementation to associate with the abstract type.
79
131
 
80
- Raises
81
- ------
82
- OrionisContainerValueError
83
- If the service is already registered.
132
+ Examples
133
+ --------
134
+ >>> container.transient(MyService, MyServiceImplementation)
84
135
  """
85
- if self.has(obj):
86
- raise OrionisContainerValueError(f"The service ({str(obj)}) is already registered in the container.")
87
136
 
88
- def _ensureIsCallable(self, concrete: Callable[..., Any]) -> None:
137
+ self.bind(abstract, concrete, Lifetime.TRANSIENT.value)
138
+
139
+ def scoped(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
89
140
  """
90
- Ensure that the given implementation is callable or instantiable.
141
+ Registers a service with a scoped lifetime.
91
142
 
92
143
  Parameters
93
144
  ----------
145
+ abstract : Callable[..., Any]
146
+ The abstract base type or alias to be bound.
94
147
  concrete : Callable[..., Any]
95
- The implementation to check.
148
+ The concrete implementation to associate with the abstract type.
96
149
 
97
- Raises
98
- ------
99
- OrionisContainerTypeError
100
- If the implementation is not callable.
150
+ Examples
151
+ --------
152
+ >>> container.scoped(MyService, MyServiceImplementation)
101
153
  """
102
- if not callable(concrete):
103
- raise OrionisContainerTypeError(f"The implementation '{str(concrete)}' must be callable or an instantiable class.")
104
154
 
105
- def _ensureIsInstance(self, instance: Any) -> None:
155
+ self.bind(abstract, concrete, Lifetime.SCOPED.value)
156
+
157
+ def singleton(self, abstract: Callable[..., Any], concrete: Callable[..., Any]) -> None:
106
158
  """
107
- Ensure that the given instance is a valid object.
159
+ Registers a service with a singleton lifetime.
108
160
 
109
161
  Parameters
110
162
  ----------
163
+ abstract : Callable[..., Any]
164
+ The abstract base type or alias to be bound.
165
+ concrete : Callable[..., Any]
166
+ The concrete implementation to associate with the abstract type.
167
+
168
+ Examples
169
+ --------
170
+ >>> container.singleton(MyService, MyServiceImplementation)
171
+ """
172
+
173
+ self.bind(abstract, concrete, Lifetime.SINGLETON.value)
174
+
175
+ def instance(self, abstract: Callable[..., Any], instance: Any) -> None:
176
+ """
177
+ Registers an already instantiated object in the container.
178
+
179
+ Parameters
180
+ ----------
181
+ abstract : Callable[..., Any]
182
+ The abstract base type or alias to be bound.
111
183
  instance : Any
112
- The instance to check.
184
+ The instance to be stored.
113
185
 
114
186
  Raises
115
187
  ------
116
188
  OrionisContainerValueError
117
- If the instance is not a valid object.
118
- """
119
- if not isinstance(instance, object) or instance.__class__.__module__ in ['builtins', 'abc']:
120
- raise OrionisContainerValueError(f"The instance '{str(instance)}' must be a valid object.")
189
+ If the instance is None.
121
190
 
122
- def forgetScopedInstances(self) -> None:
191
+ Examples
192
+ --------
193
+ >>> container.instance(MyService, my_service_instance)
123
194
  """
124
- Reset scoped instances at the beginning of a new request.
125
- """
126
- self._scoped_instances = {}
127
195
 
128
- def bind(self, concrete: Callable[..., Any]) -> str:
129
- """
130
- Bind a callable to the container.
131
- This method ensures that the provided callable is not the main function,
132
- is unique within the container, and is indeed callable. It then creates
133
- a unique key for the callable based on its module and name, and stores
134
- the callable in the container's bindings.
135
- Args:
136
- concrete (Callable[..., Any]): The callable to be bound to the container.
137
- Returns:
138
- str: The unique key generated for the callable.
139
- """
140
- self._ensureNotMain(concrete)
141
- self._ensureUniqueService(concrete)
142
- self._ensureIsCallable(concrete)
143
-
144
- key = f"{concrete.__module__}.{concrete.__name__}"
145
- self._bindings[key] = {
146
- 'concrete': concrete,
147
- 'module': concrete.__module__,
148
- 'name': concrete.__name__,
149
- 'type': BINDING
150
- }
196
+ if instance is None:
197
+ raise OrionisContainerValueError("The provided instance cannot be None.")
198
+
199
+ ContainerIntegrity.ensureIsInstance(instance)
200
+
201
+ if ContainerIntegrity.isAbstract(abstract):
202
+ ContainerIntegrity.ensureImplementation(abstract, instance.__class__)
203
+ self._instances_services[abstract] = instance
204
+ return
205
+
206
+ if ContainerIntegrity.isAlias(abstract):
207
+ self._instances_services[abstract] = instance
208
+ return
151
209
 
152
- return key
210
+ raise OrionisContainerValueError(f"Invalid abstract type '{abstract}'. It must be a valid alias or an abstract class.")
153
211
 
154
- def transient(self, concrete: Callable[..., Any]) -> str:
212
+ def bound(self, abstract_or_alias: Callable[..., Any]) -> bool:
155
213
  """
156
- Registers a transient service in the container.
157
- A transient service is created each time it is requested.
158
- Args:
159
- concrete (Callable[..., Any]): The callable that defines the service.
160
- Returns:
161
- str: The unique key generated for the callable.
214
+ Checks if a service or alias is bound in the container.
215
+
216
+ Parameters
217
+ ----------
218
+ abstract_or_alias : Callable[..., Any]
219
+ The abstract type or alias to check.
220
+
221
+ Returns
222
+ -------
223
+ bool
224
+ True if the service is bound, False otherwise.
225
+
226
+ Examples
227
+ --------
228
+ >>> container.bound(MyService)
229
+ True
162
230
  """
163
- self._ensureNotMain(concrete)
164
- self._ensureUniqueService(concrete)
165
- self._ensureIsCallable(concrete)
166
231
 
167
- key = f"{concrete.__module__}.{concrete.__name__}"
168
- self._transients[key] = {
169
- 'concrete': concrete,
170
- 'module': concrete.__module__,
171
- 'name': concrete.__name__,
172
- 'type': TRANSIENT
173
- }
232
+ service_dicts = [
233
+ self._instances_services,
234
+ self._transient_services,
235
+ self._scoped_services,
236
+ self._singleton_services,
237
+ self._aliases_services
238
+ ]
239
+ return any(abstract_or_alias in service_dict for service_dict in service_dicts)
174
240
 
175
- return key
176
-
177
- def singleton(self, concrete: Callable[..., Any]) -> str:
178
- """
179
- Registers a callable as a singleton in the container.
180
- This method ensures that the provided callable is not the main module,
181
- is unique within the container, and is indeed callable. It then registers
182
- the callable as a singleton, storing it in the container's singleton registry.
183
- Args:
184
- concrete (Callable[..., Any]): The callable to be registered as a singleton.
185
- Returns:
186
- str: The key under which the singleton is registered in the container.
187
- """
188
- self._ensureNotMain(concrete)
189
- self._ensureUniqueService(concrete)
190
- self._ensureIsCallable(concrete)
191
-
192
- key = f"{concrete.__module__}.{concrete.__name__}"
193
- self._singletons[key] = {
194
- 'concrete': concrete,
195
- 'module': concrete.__module__,
196
- 'name': concrete.__name__,
197
- 'type': SINGLETON
198
- }
241
+ def has(self, abstract_or_alias: Callable[..., Any]) -> bool:
242
+ """
243
+ Alias for `bound()` method.
199
244
 
200
- return key
201
-
202
- def scoped(self, concrete: Callable[..., Any]) -> str:
203
- """
204
- Registers a callable as a scoped service.
205
- This method ensures that the provided callable is not the main service,
206
- is unique, and is indeed callable. It then registers the callable in the
207
- scoped services dictionary with relevant metadata.
208
- Args:
209
- concrete (Callable[..., Any]): The callable to be registered as a scoped service.
210
- Returns:
211
- str: The key under which the callable is registered in the scoped services dictionary.
212
- """
213
- self._ensureNotMain(concrete)
214
- self._ensureUniqueService(concrete)
215
- self._ensureIsCallable(concrete)
216
-
217
- key = f"{concrete.__module__}.{concrete.__name__}"
218
- self._scoped_services[key] = {
219
- 'concrete': concrete,
220
- 'module': concrete.__module__,
221
- 'name': concrete.__name__,
222
- 'type': SCOPED
223
- }
245
+ Parameters
246
+ ----------
247
+ abstract_or_alias : Callable[..., Any]
248
+ The abstract type or alias to check.
224
249
 
225
- return key
250
+ Returns
251
+ -------
252
+ bool
253
+ True if the service is bound, False otherwise.
226
254
 
227
- def instance(self, instance: Any) -> str:
255
+ Examples
256
+ --------
257
+ >>> container.has(MyService)
258
+ True
228
259
  """
229
- Registers an instance as a singleton in the container.
230
- Args:
231
- instance (Any): The instance to be registered as a singleton.
232
- Returns:
233
- str: The key under which the instance is registered in the container.
260
+
261
+ return self.bound(abstract_or_alias)
262
+
263
+ def alias(self, alias: Callable[..., Any], abstract: Callable[..., Any]) -> None:
234
264
  """
235
- self._ensureNotMain(instance.__class__)
236
- self._ensureUniqueService(instance)
237
- self._ensureIsInstance(instance)
265
+ Creates an alias for an existing abstract binding.
238
266
 
239
- concrete = instance.__class__
240
- key = f"{concrete.__module__}.{concrete.__name__}"
241
- self._instances[key] = {
242
- 'instance': instance,
243
- 'module': concrete.__module__,
244
- 'name': concrete.__name__,
245
- 'type': INSTANCE
246
- }
267
+ Parameters
268
+ ----------
269
+ alias : Callable[..., Any]
270
+ The alias name.
271
+ abstract : Callable[..., Any]
272
+ The existing abstract type to alias.
247
273
 
248
- return key
274
+ Raises
275
+ ------
276
+ OrionisContainerValueError
277
+ If the abstract type is not registered or the alias is already in use.
249
278
 
250
- def alias(self, alias: str, concrete: Any) -> None:
251
- """
252
- Creates an alias for a registered service.
253
- Args:
254
- alias (str): The alias name to be used for the service.
255
- concrete (Any): The actual service instance or callable to be aliased.
256
- Raises:
257
- OrionisContainerException: If the concrete instance is not a valid object or if the alias is a primitive type.
279
+ Examples
280
+ --------
281
+ >>> container.alias("DatabaseService", MyDatabaseService)
258
282
  """
259
283
 
260
- if self._instance._validate_types.isPrimitive(alias):
261
- raise OrionisContainerException(f"Cannot use primitive type '{alias}' as an alias.")
284
+ if not self.has(abstract):
285
+ raise OrionisContainerValueError(f"Abstract '{abstract}' is not registered in the container.")
262
286
 
263
- if isinstance(concrete, str):
264
- if self.has(concrete):
265
- current_key = concrete
266
- else:
267
- raise OrionisContainerException(f"Service '{concrete}' is not registered in the container.")
268
- elif isinstance(concrete, object) and concrete.__class__.__module__ not in ['builtins', 'abc']:
269
- cls_concrete = concrete.__class__
270
- current_key = f"{cls_concrete.__module__}.{cls_concrete.__name__}"
271
- elif callable(concrete):
272
- current_key = f"{concrete.__module__}.{concrete.__name__}"
287
+ if alias in self._aliases_services:
288
+ raise OrionisContainerValueError(f"Alias '{alias}' is already in use.")
289
+
290
+ if not ContainerIntegrity.isAlias(abstract):
291
+ raise OrionisContainerValueError(f"Invalid target abstract type: {abstract}. It must be an alias.")
273
292
 
274
- self._aliases[alias] = current_key
293
+ self._aliases_services[alias] = abstract
275
294
 
276
- def has(self, obj: Any) -> bool:
295
+ def isAlias(self, name: str) -> bool:
277
296
  """
278
- Checks if a service is registered in the container.
297
+ Checks if a given name is an alias.
279
298
 
280
299
  Parameters
281
300
  ----------
282
- obj : Any
283
- The service class, instance, or alias to check.
301
+ name : str
302
+ The name to check.
284
303
 
285
304
  Returns
286
305
  -------
287
306
  bool
288
- True if the service is registered, False otherwise.
307
+ True if the name is an alias, False otherwise.
308
+
309
+ Raises
310
+ ------
311
+ OrionisContainerTypeError
312
+ If the name is not a string.
313
+
314
+ Examples
315
+ --------
316
+ >>> container.isAlias("DatabaseService")
317
+ True
289
318
  """
290
- if isinstance(obj, str):
291
- return obj in self._aliases or obj in (
292
- self._bindings | self._transients | self._singletons | self._scoped_services | self._instances
293
- )
294
319
 
295
- if isinstance(obj, object) and obj.__class__.__module__ not in {'builtins', 'abc'}:
296
- key = f"{obj.__class__.__module__}.{obj.__class__.__name__}"
297
- return key in self._instances
320
+ if not isinstance(name, str):
321
+ raise OrionisContainerTypeError("The name must be a valid string.")
322
+ return name in self._aliases_services
298
323
 
299
- if callable(obj):
300
- key = f"{obj.__module__}.{obj.__name__}"
301
- return key in (
302
- self._bindings | self._transients | self._singletons | self._scoped_services | self._aliases
303
- )
324
+ def getBindings(self) -> Dict[str, Any]:
325
+ """
326
+ Retrieves all registered service bindings.
304
327
 
305
- return False
328
+ Returns
329
+ -------
330
+ dict
331
+ A dictionary containing all instances, transient, scoped, singleton, and alias services.
332
+
333
+ Examples
334
+ --------
335
+ >>> container.getBindings()
336
+ """
337
+
338
+ return {
339
+ "instances": self._instances_services,
340
+ "transient": self._transient_services,
341
+ "scoped": self._scoped_services,
342
+ "singleton": self._singleton_services,
343
+ "aliases": self._aliases_services
344
+ }
306
345
 
307
- def bound(self, abstract: Any) -> bool:
346
+ def getAlias(self, name: str) -> Callable[..., Any]:
308
347
  """
309
- Checks if a service is bound in the container.
348
+ Retrieves the abstract type associated with an alias.
310
349
 
311
350
  Parameters
312
351
  ----------
313
- abstract : Any
314
- The service class or alias to check.
352
+ name : str
353
+ The alias name.
315
354
 
316
355
  Returns
317
356
  -------
318
- bool
319
- True if the service is bound, False otherwise.
357
+ Callable[..., Any]
358
+ The abstract type associated with the alias.
359
+
360
+ Raises
361
+ ------
362
+ OrionisContainerValueError
363
+ If the alias is not registered.
364
+
365
+ Examples
366
+ --------
367
+ >>> container.getAlias("DatabaseService")
368
+ <class 'MyDatabaseService'>
369
+ """
370
+
371
+ if not isinstance(name, str):
372
+ raise OrionisContainerValueError("The name must be a valid string.")
373
+
374
+ if name not in self._aliases_services:
375
+ raise OrionisContainerValueError(f"Alias '{name}' is not registered in the container.")
376
+
377
+ return self._aliases_services[name]
378
+
379
+ def forgetScopedInstances(self) -> None:
320
380
  """
321
- return self.has(abstract)
381
+ Clears all scoped instances.
322
382
 
323
- def make(self, abstract: Any) -> Any:
383
+ Examples
384
+ --------
385
+ >>> container.forgetScopedInstances()
324
386
  """
325
- Create and return an instance of a registered service.
387
+
388
+ self._scoped_instances = {}
389
+
390
+ def newRequest(self) -> None:
391
+ """
392
+ Resets scoped instances to handle a new request.
393
+
394
+ Examples
395
+ --------
396
+ >>> container.newRequest()
397
+ """
398
+
399
+ self.forgetScopedInstances()
400
+
401
+ async def make(self, abstract_or_alias: Callable[..., Any]) -> Any:
402
+ """
403
+ Resolves and instantiates a service from the container.
326
404
 
327
405
  Parameters
328
406
  ----------
329
- abstract : Any
330
- The service class or alias to instantiate.
407
+ abstract_or_alias : Callable[..., Any]
408
+ The abstract type or alias to resolve.
331
409
 
332
410
  Returns
333
411
  -------
334
412
  Any
335
- An instance of the requested service.
413
+ The instantiated service.
336
414
 
337
415
  Raises
338
416
  ------
339
417
  OrionisContainerException
340
- If the service is not found in the container.
418
+ If the service is not found.
419
+
420
+ Examples
421
+ --------
422
+ >>> service = await container.make(MyService)
341
423
  """
342
424
 
343
- key = abstract
425
+ if abstract_or_alias in self._aliases_services:
426
+ abstract_or_alias = self._aliases_services[abstract_or_alias]
344
427
 
345
- if isinstance(abstract, str):
346
- key = self._aliases.get(key, key)
428
+ if abstract_or_alias in self._instances_services:
429
+ return self._instances_services[abstract_or_alias]
347
430
 
348
- if callable(abstract):
349
- key = f"{abstract.__module__}.{abstract.__name__}"
431
+ if abstract_or_alias in self._singleton_services:
432
+ if abstract_or_alias not in self._singleton_instances:
433
+ service = self._singleton_services[abstract_or_alias]
434
+ self._singleton_instances[abstract_or_alias] = await service['concrete']() if service['async'] else service['concrete']()
435
+ return self._singleton_instances[abstract_or_alias]
350
436
 
351
- if isinstance(abstract, object) and abstract.__class__.__module__ not in {'builtins', 'abc'}:
352
- key = f"{abstract.__class__.__module__}.{abstract.__class__.__name__}"
437
+ if abstract_or_alias in self._scoped_services:
438
+ if abstract_or_alias not in self._scoped_instances:
439
+ service = self._scoped_services[abstract_or_alias]
440
+ self._scoped_instances[abstract_or_alias] = await service['concrete']() if service['async'] else service['concrete']()
441
+ return self._scoped_instances[abstract_or_alias]
353
442
 
354
- if key in self._instances:
355
- return self._instances[key]['instance']
443
+ if abstract_or_alias in self._transient_services:
444
+ service = self._transient_services[abstract_or_alias]
445
+ return await service['concrete']() if service['async'] else service['concrete']()
356
446
 
357
- if key in self._singletons:
358
- self._instances[key] = {'instance': self._resolve(self._singletons[key]['concrete'])}
359
- return self._instances[key]['instance']
447
+ raise OrionisContainerException(f"No binding found for '{abstract_or_alias}' in the container.")
360
448
 
361
- if key in self._scoped_services:
362
- if key not in self._scoped_instances:
363
- self._scoped_instances[key] = self._resolve(self._scoped_services[key]['concrete'])
364
- return self._scoped_instances[key]
449
+ def _resolve(self, concrete: Callable[..., Any], resolving: Optional[Deque[Type]] = None) -> Any:
450
+ """
451
+ Resolves dependencies recursively and instantiates a class.
365
452
 
366
- if key in self._transients:
367
- return self._resolve(self._transients[key]['concrete'])
453
+ Parameters
454
+ ----------
455
+ concrete : Callable[..., Any]
456
+ The concrete implementation to instantiate.
457
+ resolving : Optional[Deque[Type]], optional
458
+ A queue to track resolving dependencies and prevent circular dependencies.
368
459
 
369
- if key in self._bindings:
370
- return self._resolve(self._bindings[key]['concrete'])
460
+ Returns
461
+ -------
462
+ Any
463
+ The instantiated object.
371
464
 
372
- raise OrionisContainerException(f"Service '{abstract}' is not registered in the container.")
465
+ Raises
466
+ ------
467
+ OrionisContainerException
468
+ If circular dependencies are detected or instantiation fails.
373
469
 
374
- def _resolve(self, concrete: Callable[..., Any]) -> Any:
470
+ Examples
471
+ --------
472
+ >>> instance = container._resolve(MyClass)
375
473
  """
376
- Resolve and instantiate a given service class or function.
377
474
 
378
- This method analyzes the constructor of the given class (or callable),
379
- retrieves its dependencies, and resolves them recursively, while respecting
380
- the service lifecycle.
381
- """
475
+ if resolving is None:
476
+ resolving = deque()
477
+
478
+ if concrete in resolving:
479
+ raise OrionisContainerException(f"Circular dependency detected for {concrete}.")
480
+
481
+ resolving.append(concrete)
382
482
 
383
- # Step 1: Retrieve the constructor signature of the class or callable.
384
483
  try:
385
484
  signature = inspect.signature(concrete)
386
485
  except ValueError as e:
387
486
  raise OrionisContainerException(f"Unable to inspect signature of {concrete}: {str(e)}")
388
487
 
389
- # Step 2: Prepare a dictionary for resolved dependencies and a queue for unresolved ones.
390
488
  resolved_dependencies: Dict[str, Any] = {}
391
489
  unresolved_dependencies = deque()
392
490
 
393
- # Step 3: Iterate through the parameters of the constructor.
394
491
  for param_name, param in signature.parameters.items():
395
-
396
- # Skip 'self' in methods
397
492
  if param_name == 'self':
398
493
  continue
399
494
 
400
- # Handle parameters that are VAR_POSITIONAL or VAR_KEYWORD
401
495
  if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD):
402
496
  continue
403
497
 
404
- # If parameter has no annotation and no default value, it's unresolved
405
498
  if param.annotation is param.empty and param.default is param.empty:
406
499
  unresolved_dependencies.append(param_name)
407
500
  continue
408
501
 
409
- # Resolve parameters with default values (without annotations)
410
502
  if param.default is not param.empty:
411
503
  resolved_dependencies[param_name] = param.default
412
504
  continue
413
505
 
414
- # Resolve dependencies based on annotations (excluding primitive types)
415
506
  if param.annotation is not param.empty:
416
507
  param_type = param.annotation
417
508
 
418
- # Check if it's a generic type, get the origin type
419
509
  if get_origin(param_type) is not None:
420
510
  param_type = get_args(param_type)[0]
421
511
 
@@ -423,36 +513,55 @@ class Container(IContainer):
423
513
  if self.has(param_type):
424
514
  resolved_dependencies[param_name] = self.make(f"{param_type.__module__}.{param_type.__name__}")
425
515
  else:
426
- resolved_dependencies[param_name] = self._resolve_dependency(param_type)
516
+ resolved_dependencies[param_name] = self._resolve_dependency(param_type, resolving)
427
517
  else:
428
518
  resolved_dependencies[param_name] = param_type
429
519
 
430
- # Step 4: Resolve any remaining unresolved dependencies.
431
520
  while unresolved_dependencies:
432
521
  dep_name = unresolved_dependencies.popleft()
433
522
  if dep_name not in resolved_dependencies:
434
- resolved_dependencies[dep_name] = self._resolve_dependency(dep_name)
523
+ resolved_dependencies[dep_name] = self._resolve_dependency(dep_name, resolving)
435
524
 
436
- # Step 5: Instantiate the class with resolved dependencies.
437
525
  try:
438
- return concrete(**resolved_dependencies)
526
+ instance = concrete(**resolved_dependencies)
527
+ resolving.pop()
528
+ return instance
439
529
  except Exception as e:
440
530
  raise OrionisContainerException(f"Failed to instantiate {concrete}: {str(e)}")
441
531
 
442
- def _resolve_dependency(self, dep_type: Any) -> Any:
532
+ def _resolve_dependency(self, dep_type: Any, resolving: Optional[Deque[Type]] = None) -> Any:
443
533
  """
444
- Resolves a dependency based on the provided type.
534
+ Resolves a dependency by instantiating or retrieving it from the container.
535
+
536
+ Parameters
537
+ ----------
538
+ dep_type : Any
539
+ The dependency type to resolve.
540
+ resolving : Optional[Deque[Type]], optional
541
+ A queue to track resolving dependencies.
542
+
543
+ Returns
544
+ -------
545
+ Any
546
+ The resolved dependency.
445
547
 
446
- This method looks for the type in the container and returns the instance,
447
- respecting the lifecycle of the service (transient, singleton, etc.).
548
+ Raises
549
+ ------
550
+ OrionisContainerException
551
+ If the dependency cannot be resolved.
552
+
553
+ Examples
554
+ --------
555
+ >>> dependency = container._resolve_dependency(MyDependency)
448
556
  """
449
- # Check if the dependency exists in the container or create it if necessary, If it's a class type
557
+
558
+ if resolving is None:
559
+ resolving = deque()
560
+
450
561
  if isinstance(dep_type, type):
451
562
  if self.has(dep_type):
452
- # Resolves the service through the container
453
563
  return self.make(f"{dep_type.__module__}.{dep_type.__name__}")
454
564
  else:
455
- # Instantiate the class if not found in the container
456
- return self._resolve(dep_type)
565
+ return self._resolve(dep_type, resolving)
457
566
 
458
- raise OrionisContainerException(f"Cannot resolve dependency of type {dep_type}")
567
+ raise OrionisContainerException(f"Cannot resolve dependency of type {dep_type}")