orionis 0.320.0__py3-none-any.whl → 0.322.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,6 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Any, Callable
3
3
  from orionis.container.enums.lifetimes import Lifetime
4
+ from orionis.container.entities.binding import Binding
4
5
 
5
6
  class IContainer(ABC):
6
7
  """
@@ -18,7 +19,7 @@ class IContainer(ABC):
18
19
  enforce_decoupling: bool = False
19
20
  ) -> bool:
20
21
  """
21
- Register a service as a singleton.
22
+ Registers a service with a singleton lifetime.
22
23
 
23
24
  Parameters
24
25
  ----------
@@ -27,7 +28,7 @@ class IContainer(ABC):
27
28
  concrete : Callable[..., Any]
28
29
  The concrete implementation to associate with the abstract type.
29
30
  alias : str, optional
30
- An alternative name to register the service under.
31
+ An alternative name to register the service under. If not provided, the abstract's class name is used.
31
32
  enforce_decoupling : bool, optional
32
33
  Whether to enforce that concrete is not a subclass of abstract.
33
34
 
@@ -48,7 +49,7 @@ class IContainer(ABC):
48
49
  enforce_decoupling: bool = False
49
50
  ) -> bool:
50
51
  """
51
- Register a service as transient.
52
+ Registers a service with a transient lifetime.
52
53
 
53
54
  Parameters
54
55
  ----------
@@ -57,7 +58,7 @@ class IContainer(ABC):
57
58
  concrete : Callable[..., Any]
58
59
  The concrete implementation to associate with the abstract type.
59
60
  alias : str, optional
60
- An alternative name to register the service under.
61
+ An alternative name to register the service under. If not provided, the abstract's class name is used.
61
62
  enforce_decoupling : bool, optional
62
63
  Whether to enforce that concrete is not a subclass of abstract.
63
64
 
@@ -78,7 +79,7 @@ class IContainer(ABC):
78
79
  enforce_decoupling: bool = False
79
80
  ) -> bool:
80
81
  """
81
- Register a service as scoped.
82
+ Registers a service with a scoped lifetime.
82
83
 
83
84
  Parameters
84
85
  ----------
@@ -87,7 +88,7 @@ class IContainer(ABC):
87
88
  concrete : Callable[..., Any]
88
89
  The concrete implementation to associate with the abstract type.
89
90
  alias : str, optional
90
- An alternative name to register the service under.
91
+ An alternative name to register the service under. If not provided, the abstract's class name is used.
91
92
  enforce_decoupling : bool, optional
92
93
  Whether to enforce that concrete is not a subclass of abstract.
93
94
 
@@ -108,7 +109,7 @@ class IContainer(ABC):
108
109
  enforce_decoupling: bool = False
109
110
  ) -> bool:
110
111
  """
111
- Register an instance of a service.
112
+ Registers an instance of a class or interface in the container.
112
113
 
113
114
  Parameters
114
115
  ----------
@@ -117,7 +118,8 @@ class IContainer(ABC):
117
118
  instance : Any
118
119
  The concrete instance to register.
119
120
  alias : str, optional
120
- An optional alias to register the instance under.
121
+ An optional alias to register the instance under. If not provided,
122
+ the abstract's `__name__` attribute will be used as the alias if available.
121
123
  enforce_decoupling : bool, optional
122
124
  Whether to enforce that instance's class is not a subclass of abstract.
123
125
 
@@ -129,7 +131,7 @@ class IContainer(ABC):
129
131
  pass
130
132
 
131
133
  @abstractmethod
132
- def function(
134
+ def callable(
133
135
  self,
134
136
  alias: str,
135
137
  fn: Callable[..., Any],
@@ -137,7 +139,7 @@ class IContainer(ABC):
137
139
  lifetime: Lifetime = Lifetime.TRANSIENT
138
140
  ) -> bool:
139
141
  """
140
- Register a function as a service.
142
+ Registers a function or factory under a given alias.
141
143
 
142
144
  Parameters
143
145
  ----------
@@ -163,16 +165,16 @@ class IContainer(ABC):
163
165
  **kwargs: dict
164
166
  ) -> Any:
165
167
  """
166
- Resolve a service from the container.
168
+ Resolves and returns an instance of the requested service.
167
169
 
168
170
  Parameters
169
171
  ----------
170
172
  abstract_or_alias : Any
171
173
  The abstract class, interface, or alias (str) to resolve.
172
174
  *args : tuple
173
- Positional arguments to pass to the constructor.
175
+ Positional arguments to pass to the constructor of the resolved service.
174
176
  **kwargs : dict
175
- Keyword arguments to pass to the constructor.
177
+ Keyword arguments to pass to the constructor of the resolved service.
176
178
 
177
179
  Returns
178
180
  -------
@@ -187,16 +189,60 @@ class IContainer(ABC):
187
189
  abstract_or_alias: Any
188
190
  ) -> bool:
189
191
  """
190
- Check if a service is registered in the container.
192
+ Checks if a service (by abstract type or alias) is registered in the container.
191
193
 
192
194
  Parameters
193
195
  ----------
194
196
  abstract_or_alias : Any
195
- The abstract class, interface, or alias (str) to check.
197
+ The abstract class, interface, or alias (str) to check for registration.
196
198
 
197
199
  Returns
198
200
  -------
199
201
  bool
200
- True if the service is registered, False otherwise.
202
+ True if the service is registered (either as an abstract type or alias), False otherwise.
203
+ """
204
+ pass
205
+
206
+ @abstractmethod
207
+ def getBinding(
208
+ self,
209
+ abstract_or_alias: Any
210
+ ) -> Binding:
211
+ """
212
+ Retrieves the binding for the requested abstract type or alias.
213
+
214
+ Parameters
215
+ ----------
216
+ abstract_or_alias : Any
217
+ The abstract class, interface, or alias (str) to retrieve.
218
+
219
+ Returns
220
+ -------
221
+ Binding
222
+ The binding associated with the requested abstract type or alias.
223
+ """
224
+ pass
225
+
226
+ @abstractmethod
227
+ def drop(
228
+ self,
229
+ abstract: Callable[..., Any] = None,
230
+ alias: str = None
231
+ ) -> None:
232
+ """
233
+ Drops a service from the container by removing its bindings and aliases.
234
+
235
+ Warning
236
+ -------
237
+ Using this method irresponsibly can severely damage the system's logic.
238
+ Only use it when you are certain about the consequences, as removing
239
+ critical services may lead to system failures and unexpected behavior.
240
+
241
+ Parameters
242
+ ----------
243
+ abstract : Callable[..., Any], optional
244
+ The abstract type or interface to be removed from the container.
245
+ alias : str, optional
246
+ The alias of the service to be removed.
201
247
  """
202
248
  pass
@@ -1,7 +1,12 @@
1
+ from typing import Any, Callable
2
+ from orionis.container.context.scope import ScopedContext
1
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
7
+ from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
8
+ from orionis.services.introspection.concretes.reflection_concrete import ReflectionConcrete
9
+ from orionis.services.introspection.dependencies.entities.resolved_dependencies import ResolvedDependency
5
10
 
6
11
  class Resolver:
7
12
  """
@@ -10,19 +15,439 @@ class Resolver:
10
15
 
11
16
  def __init__(
12
17
  self,
13
- container:IContainer,
14
- lifetime:Lifetime
18
+ container:IContainer
15
19
  ):
20
+ """
21
+ Initialize the resolver.
22
+
23
+ This method initializes the resolver with a reference to the container.
24
+
25
+ Parameters
26
+ ----------
27
+ container : IContainer
28
+ The container instance that this resolver will use to resolve dependencies.
29
+ """
16
30
  self.container = container
17
- self.lifetime = lifetime
18
31
 
19
- def transient(
32
+ def resolve(
20
33
  self,
21
34
  binding:Binding,
22
35
  *args,
23
36
  **kwargs
24
37
  ):
25
38
  """
26
- Register a transient service.
39
+ Resolves an instance from a binding.
40
+ This method resolves an instance based on the binding's lifetime and type.
41
+ It delegates to specific resolution methods based on the lifetime (transient, singleton, or scoped).
42
+ Args:
43
+ binding (Binding): The binding to resolve.
44
+ *args: Additional positional arguments to pass to the constructor.
45
+ **kwargs: Additional keyword arguments to pass to the constructor.
46
+ Returns:
47
+ Any: The resolved instance.
48
+ Raises:
49
+ OrionisContainerException: If the binding is not an instance of Binding
50
+ or if scoped lifetime resolution is attempted (not yet implemented).
51
+ """
52
+
53
+ # Ensure the binding is an instance of Binding
54
+ if not isinstance(binding, Binding):
55
+ raise OrionisContainerException(
56
+ "The binding must be an instance of Binding."
57
+ )
58
+
59
+ # Handle based on binding type and lifetime
60
+ if binding.lifetime == Lifetime.TRANSIENT:
61
+ return self.__resolveTransient(binding, *args, **kwargs)
62
+ elif binding.lifetime == Lifetime.SINGLETON:
63
+ return self.__resolveSingleton(binding, *args, **kwargs)
64
+ elif binding.lifetime == Lifetime.SCOPED:
65
+ return self.__resolveScoped(binding, *args, **kwargs)
66
+
67
+ def __resolveTransient(self, binding: Binding, *args, **kwargs) -> Any:
68
+ """
69
+ Resolves a service with transient lifetime.
70
+
71
+ Parameters
72
+ ----------
73
+ binding : Binding
74
+ The binding to resolve.
75
+ *args : tuple
76
+ Positional arguments to pass to the constructor.
77
+ **kwargs : dict
78
+ Keyword arguments to pass to the constructor.
79
+
80
+ Returns
81
+ -------
82
+ Any
83
+ A new instance of the requested service.
84
+ """
85
+
86
+ # Check if the binding has a concrete class or function defined
87
+ if binding.concrete:
88
+ if args or kwargs:
89
+ return self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
90
+ else:
91
+ return self.__instantiateConcreteReflective(binding.concrete)
92
+
93
+ # If the binding has a function defined
94
+ elif binding.function:
95
+ if args or kwargs:
96
+ return self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
97
+ else:
98
+ return self.__instantiateCallableReflective(binding.function)
99
+
100
+ # If neither concrete class nor function is defined
101
+ else:
102
+ raise OrionisContainerException(
103
+ "Cannot resolve transient binding: neither a concrete class nor a function is defined."
104
+ )
105
+
106
+ def __resolveSingleton(self, binding: Binding, *args, **kwargs) -> Any:
107
+ """
108
+ Resolves a service with singleton lifetime.
109
+
110
+ Parameters
111
+ ----------
112
+ binding : Binding
113
+ The binding to resolve.
114
+ *args : tuple
115
+ Positional arguments to pass to the constructor (only used if instance doesn't exist yet).
116
+ **kwargs : dict
117
+ Keyword arguments to pass to the constructor (only used if instance doesn't exist yet).
118
+
119
+ Returns
120
+ -------
121
+ Any
122
+ The singleton instance of the requested service.
123
+ """
124
+ # Return existing instance if available
125
+ if binding.instance:
126
+ return binding.instance
127
+
128
+ # Create instance if needed
129
+ if binding.concrete:
130
+ if args or kwargs:
131
+ binding.instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
132
+ else:
133
+ binding.instance = self.__instantiateConcreteReflective(binding.concrete)
134
+ return binding.instance
135
+
136
+ # If the binding has a function defined
137
+ elif binding.function:
138
+ if args or kwargs:
139
+ result = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
140
+ else:
141
+ result = self.__instantiateCallableReflective(binding.function)
142
+
143
+ # Store the result directly as the singleton instance
144
+ # We don't automatically invoke factory function results anymore
145
+ binding.instance = result
146
+ return binding.instance
147
+
148
+ # If neither concrete class nor function is defined
149
+ else:
150
+ raise OrionisContainerException(
151
+ "Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
152
+ )
153
+
154
+ def __resolveScoped(self, binding: Binding, *args, **kwargs) -> Any:
155
+ """
156
+ Resolves a service with scoped lifetime.
157
+
158
+ Parameters
159
+ ----------
160
+ binding : Binding
161
+ The binding to resolve.
162
+ *args : tuple
163
+ Positional arguments to pass to the constructor.
164
+ **kwargs : dict
165
+ Keyword arguments to pass to the constructor.
166
+
167
+ Returns
168
+ -------
169
+ Any
170
+ The scoped instance of the requested service.
171
+
172
+ Raises
173
+ ------
174
+ OrionisContainerException
175
+ If no scope is active or service can't be resolved.
176
+ """
177
+ scope = ScopedContext.getCurrentScope()
178
+ if scope is None:
179
+ raise OrionisContainerException(
180
+ f"No active scope found while resolving scoped service '{binding.alias}'. "
181
+ f"Use 'with container.createContext():' to create a scope context."
182
+ )
183
+
184
+ if binding.alias in scope:
185
+ return scope[binding.alias]
186
+
187
+ # Create a new instance
188
+ if binding.concrete:
189
+ if args or kwargs:
190
+ instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
191
+ else:
192
+ instance = self.__instantiateConcreteReflective(binding.concrete)
193
+ elif binding.function:
194
+ if args or kwargs:
195
+ instance = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
196
+ else:
197
+ instance = self.__instantiateCallableReflective(binding.function)
198
+ else:
199
+ raise OrionisContainerException(
200
+ "Cannot resolve scoped binding: neither a concrete class nor a function is defined."
201
+ )
202
+
203
+ scope[binding.alias] = instance
204
+ return instance
205
+
206
+ def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
207
+ """
208
+ Instantiates a concrete class with the provided arguments.
209
+
210
+ Parameters
211
+ ----------
212
+ concrete : Callable[..., Any]
213
+ Class to instantiate.
214
+ *args : tuple
215
+ Positional arguments to pass to the constructor.
216
+ **kwargs : dict
217
+ Keyword arguments to pass to the constructor.
218
+
219
+ Returns
220
+ -------
221
+ object
222
+ A new instance of the specified concrete class.
223
+ """
224
+
225
+ # try to instantiate the concrete class with the provided arguments
226
+ try:
227
+
228
+ # If the concrete is a class, instantiate it directly
229
+ return concrete(*args, **kwargs)
230
+
231
+ except TypeError as e:
232
+
233
+ # If instantiation fails, use ReflectionConcrete to get class name and constructor signature
234
+ rf_concrete = ReflectionConcrete(concrete)
235
+ class_name = rf_concrete.getClassName()
236
+ signature = rf_concrete.getConstructorSignature()
237
+
238
+ # Raise an exception with detailed information about the failure
239
+ raise OrionisContainerException(
240
+ f"Failed to instantiate [{class_name}] with the provided arguments: {e}\n"
241
+ f"Expected constructor signature: [{signature}]"
242
+ ) from e
243
+
244
+ def __instantiateCallableWithArgs(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
245
+ """
246
+ Invokes a callable with the provided arguments.
247
+
248
+ Parameters
249
+ ----------
250
+ fn : Callable[..., Any]
251
+ The callable to invoke.
252
+ *args : tuple
253
+ Positional arguments to pass to the callable.
254
+ **kwargs : dict
255
+ Keyword arguments to pass to the callable.
256
+
257
+ Returns
258
+ -------
259
+ Any
260
+ The result of the callable.
261
+ """
262
+
263
+ # Try to invoke the callable with the provided arguments
264
+ try:
265
+
266
+ # If the callable is a function, invoke it directly
267
+ return fn(*args, **kwargs)
268
+
269
+ except TypeError as e:
270
+
271
+ # If invocation fails, use ReflectionCallable to get function name and signature
272
+ rf_callable = ReflectionCallable(fn)
273
+ function_name = rf_callable.getName()
274
+ signature = rf_callable.getSignature()
275
+
276
+ # Raise an exception with detailed information about the failure
277
+ raise OrionisContainerException(
278
+ f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
279
+ f"Expected function signature: [{signature}]"
280
+ ) from e
281
+
282
+ def __instantiateConcreteReflective(self, concrete: Callable[..., Any]) -> Any:
283
+ """
284
+ Instantiates a concrete class reflectively, resolving its dependencies from the container.
285
+
286
+ Parameters
287
+ ----------
288
+ concrete : Callable[..., Any]
289
+ The concrete class to instantiate.
290
+
291
+ Returns
292
+ -------
293
+ Any
294
+ A new instance of the concrete class.
295
+ """
296
+ # Resolve dependencies for the concrete class
297
+ params = self.__resolveDependencies(concrete, is_class=True)
298
+
299
+ # Instantiate the concrete class with resolved dependencies
300
+ return concrete(**params)
301
+
302
+ def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
303
+ """
304
+ Invokes a callable reflectively, resolving its dependencies from the container.
305
+
306
+ Parameters
307
+ ----------
308
+ fn : Callable[..., Any]
309
+ The callable to invoke.
310
+
311
+ Returns
312
+ -------
313
+ Any
314
+ The result of the callable.
315
+ """
316
+
317
+ # Resolve dependencies for the callable
318
+ params = self.__resolveDependencies(fn, is_class=False)
319
+
320
+ # Invoke the callable with resolved dependencies
321
+ return fn(**params)
322
+
323
+ def __resolveDependencies(
324
+ self,
325
+ target: Callable[..., Any],
326
+ *,
327
+ is_class: bool = False
328
+ ) -> dict:
27
329
  """
28
- return self.container.transient(service, implementation, **kwargs)
330
+ Resolves dependencies for a target callable or class.
331
+
332
+ Parameters
333
+ ----------
334
+ target : Callable[..., Any]
335
+ The target callable or class whose dependencies to resolve.
336
+ is_class : bool, optional
337
+ Whether the target is a class (True) or a callable (False).
338
+
339
+ Returns
340
+ -------
341
+ dict
342
+ A dictionary of resolved dependencies.
343
+ """
344
+ try:
345
+
346
+ # Use ReflectionConcrete for classes and ReflectionCallable for callables
347
+ if is_class:
348
+ reflection = ReflectionConcrete(target)
349
+ dependencies = reflection.getConstructorDependencies()
350
+ name = reflection.getClassName()
351
+
352
+ # If the target is a callable, use ReflectionCallable
353
+ else:
354
+ reflection = ReflectionCallable(target)
355
+ dependencies = reflection.getDependencies()
356
+ name = reflection.getName()
357
+
358
+ # Check for unresolved dependencies
359
+ if dependencies.unresolved:
360
+ unresolved_args = ', '.join(dependencies.unresolved)
361
+ raise OrionisContainerException(
362
+ f"Cannot resolve '{name}' because the following required arguments are missing: [{unresolved_args}]."
363
+ )
364
+
365
+ # Resolve dependencies
366
+ params = {}
367
+ for param_name, dep in dependencies.resolved.items():
368
+
369
+ # If the dependency is a ResolvedDependency, resolve it
370
+ if isinstance(dep, ResolvedDependency):
371
+
372
+ # If the dependency is a built-in type, raise an exception
373
+ if dep.module_name == 'builtins':
374
+ raise OrionisContainerException(
375
+ f"Cannot resolve '{name}' because parameter '{param_name}' depends on built-in type '{dep.type.__name__}'."
376
+ )
377
+
378
+ # Try to resolve from container using type (Abstract or Interface)
379
+ if self.container.bound(dep.type):
380
+ params[param_name] = self.resolve(
381
+ self.container.getBinding(dep.type)
382
+ )
383
+
384
+ # Try to resolve from container using full class path
385
+ elif self.container.bound(dep.full_class_path):
386
+ params[param_name] = self.resolve(
387
+ self.container.getBinding(dep.full_class_path)
388
+ )
389
+
390
+ # Try to instantiate directly if it's a concrete class
391
+ elif ReflectionConcrete.isConcreteClass(dep.type):
392
+ params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=True))
393
+
394
+ # Try to call directly if it's a callable
395
+ elif callable(dep.type) and not isinstance(dep.type, type):
396
+ params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=False))
397
+
398
+ # If the dependency cannot be resolved, raise an exception
399
+ else:
400
+ raise OrionisContainerException(
401
+ f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}' for '{name}'."
402
+ )
403
+ else:
404
+ # Use default value
405
+ params[param_name] = dep
406
+
407
+ # Return the resolved parameters
408
+ return params
409
+
410
+ except ImportError as e:
411
+
412
+ # Get target name safely
413
+ target_name = getattr(target, '__name__', str(target))
414
+ module_name = getattr(target, '__module__', "unknown module")
415
+
416
+ # Improved circular import detection with more helpful guidance
417
+ if "circular import" in str(e).lower() or "cannot import name" in str(e).lower():
418
+ raise OrionisContainerException(
419
+ f"Circular import detected while resolving dependencies for '{target_name}' in module '{module_name}'.\n"
420
+ f"This typically happens when two modules import each other. Consider:\n"
421
+ f"1. Restructuring your code to avoid circular dependencies\n"
422
+ f"2. Using delayed imports inside methods rather than at module level\n"
423
+ f"3. Using dependency injection to break the cycle\n"
424
+ f"Original error: {str(e)}"
425
+ ) from e
426
+ else:
427
+ raise OrionisContainerException(
428
+ f"Import error while resolving dependencies for '{target_name}' in module '{module_name}':\n"
429
+ f"{str(e)}"
430
+ ) from e
431
+
432
+ except Exception as e:
433
+
434
+ # More robust attribute extraction with fallbacks
435
+ target_type = "class" if isinstance(target, type) else "function"
436
+ target_name = getattr(target, '__name__', str(target))
437
+ module_name = getattr(target, '__module__', "unknown module")
438
+
439
+ # More detailed error message with context about the failure
440
+ error_msg = (
441
+ f"Error resolving dependencies for {target_type} '{target_name}' in '{module_name}':\n"
442
+ f"{str(e)}\n"
443
+ )
444
+
445
+ # Add specific guidance based on error type
446
+ if isinstance(e, TypeError):
447
+ error_msg += "This may be caused by incompatible argument types or missing required parameters."
448
+ elif isinstance(e, AttributeError):
449
+ error_msg += "This may be caused by accessing undefined attributes in the dependency chain."
450
+ error_msg += "\nCheck that all dependencies are properly registered in the container."
451
+
452
+ # Raise a more informative exception
453
+ raise OrionisContainerException(error_msg) from e
@@ -0,0 +1,53 @@
1
+ from typing import Any, Union
2
+ from orionis.container.enums.lifetimes import Lifetime
3
+ from orionis.container.exceptions.type_error_exception import OrionisContainerTypeError
4
+
5
+ class __LifetimeValidator:
6
+ """
7
+ Validator that checks if a value is a valid lifetime and converts string representations
8
+ to Lifetime enum values.
9
+ """
10
+
11
+ def __call__(self, lifetime: Union[str, Lifetime, Any]) -> Lifetime:
12
+ """
13
+ Validates and normalizes the provided lifetime value.
14
+
15
+ Parameters
16
+ ----------
17
+ lifetime : Union[str, Lifetime, Any]
18
+ The lifetime value to validate. Can be a Lifetime enum or a string
19
+ representing a valid lifetime.
20
+
21
+ Returns
22
+ -------
23
+ Lifetime
24
+ The validated Lifetime enum value.
25
+
26
+ Raises
27
+ ------
28
+ OrionisContainerTypeError
29
+ If the value is not a valid Lifetime enum or string representation,
30
+ or if the string doesn't match any valid Lifetime value.
31
+ """
32
+ # Already a Lifetime enum
33
+ if isinstance(lifetime, Lifetime):
34
+ return lifetime
35
+
36
+ # String that might represent a Lifetime
37
+ if isinstance(lifetime, str):
38
+ lifetime_key = lifetime.strip().upper()
39
+ if lifetime_key in Lifetime.__members__:
40
+ return Lifetime[lifetime_key]
41
+
42
+ valid_options = ', '.join(Lifetime.__members__.keys())
43
+ raise OrionisContainerTypeError(
44
+ f"Invalid lifetime '{lifetime}'. Valid options are: {valid_options}."
45
+ )
46
+
47
+ # Invalid type
48
+ raise OrionisContainerTypeError(
49
+ f"Lifetime must be of type str or Lifetime enum, got {type(lifetime).__name__}."
50
+ )
51
+
52
+ # Exported singleton instance
53
+ LifetimeValidator = __LifetimeValidator()
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.320.0"
8
+ VERSION = "0.322.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"