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