orionis 0.22.0__py3-none-any.whl → 0.23.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.
- orionis/framework.py +1 -1
- orionis/luminate/container/container.py +337 -302
- orionis/luminate/container/exception.py +18 -0
- orionis/luminate/contracts/container/container_interface.py +158 -88
- {orionis-0.22.0.dist-info → orionis-0.23.0.dist-info}/METADATA +1 -1
- {orionis-0.22.0.dist-info → orionis-0.23.0.dist-info}/RECORD +10 -10
- {orionis-0.22.0.dist-info → orionis-0.23.0.dist-info}/LICENCE +0 -0
- {orionis-0.22.0.dist-info → orionis-0.23.0.dist-info}/WHEEL +0 -0
- {orionis-0.22.0.dist-info → orionis-0.23.0.dist-info}/entry_points.txt +0 -0
- {orionis-0.22.0.dist-info → orionis-0.23.0.dist-info}/top_level.txt +0 -0
orionis/framework.py
CHANGED
@@ -1,385 +1,420 @@
|
|
1
1
|
import inspect
|
2
|
-
from
|
2
|
+
from collections import deque
|
3
|
+
from threading import Lock
|
4
|
+
from typing import Callable, Any, Dict
|
5
|
+
from orionis.luminate.container.exception import OrionisContainerException, OrionisContainerValueError, OrionisContainerTypeError
|
3
6
|
from orionis.luminate.container.types import Types
|
4
|
-
from orionis.luminate.tools.dot_dict import DotDict
|
5
7
|
from orionis.luminate.contracts.container.container_interface import IContainer
|
6
|
-
|
8
|
+
|
9
|
+
BINDING = 'binding'
|
10
|
+
TRANSIENT = 'transient'
|
11
|
+
SINGLETON = 'singleton'
|
12
|
+
SCOPED = 'scoped'
|
13
|
+
INSTANCE = 'instance'
|
7
14
|
|
8
15
|
class Container(IContainer):
|
9
|
-
"""
|
16
|
+
"""
|
17
|
+
Service container and dependency injection manager.
|
18
|
+
|
19
|
+
This class follows the singleton pattern to manage service bindings, instances,
|
20
|
+
and different lifecycle types such as transient, singleton, and scoped.
|
21
|
+
"""
|
10
22
|
|
11
23
|
_instance = None
|
24
|
+
_lock = Lock()
|
12
25
|
|
13
26
|
def __new__(cls):
|
14
27
|
if cls._instance is None:
|
15
|
-
cls.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# Initialize the PrimitiveTypes validator
|
27
|
-
cls._instance._primitive_types_validator = Types()
|
28
|
+
with cls._lock:
|
29
|
+
if cls._instance is None:
|
30
|
+
cls._instance = super().__new__(cls)
|
31
|
+
cls._instance._bindings = {}
|
32
|
+
cls._instance._transients = {}
|
33
|
+
cls._instance._singletons = {}
|
34
|
+
cls._instance._scoped_services = {}
|
35
|
+
cls._instance._instances = {}
|
36
|
+
cls._instance._aliases = {}
|
37
|
+
cls._instance._scoped_instances = {}
|
38
|
+
cls._instance._validate_types = Types()
|
28
39
|
return cls._instance
|
29
40
|
|
30
|
-
def
|
41
|
+
def _newRequest(self) -> None:
|
31
42
|
"""
|
32
|
-
|
33
|
-
|
34
|
-
Args:
|
35
|
-
section (str): _description_
|
36
|
-
data (dict): _description_
|
43
|
+
Reset scoped instances at the beginning of a new request.
|
37
44
|
"""
|
45
|
+
self._scoped_instances = {}
|
38
46
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
def _ensureNotMain(self, concrete: Callable[..., Any]) -> str:
|
48
|
+
"""
|
49
|
+
Ensure that a class is not defined in the main script.
|
50
|
+
|
51
|
+
Parameters
|
52
|
+
----------
|
53
|
+
concrete : Callable[..., Any]
|
54
|
+
The class or function to check.
|
55
|
+
|
56
|
+
Returns
|
57
|
+
-------
|
58
|
+
str
|
59
|
+
The fully qualified name of the class.
|
60
|
+
|
61
|
+
Raises
|
62
|
+
------
|
63
|
+
OrionisContainerValueError
|
64
|
+
If the class is defined in the main module.
|
65
|
+
"""
|
66
|
+
if concrete.__module__ == "__main__":
|
67
|
+
raise OrionisContainerValueError(
|
68
|
+
"Cannot register a class from the main module in the container."
|
69
|
+
)
|
70
|
+
return f"{concrete.__module__}.{concrete.__name__}"
|
51
71
|
|
52
|
-
def
|
53
|
-
"""
|
72
|
+
def _ensureUniqueService(self, obj: Any) -> None:
|
73
|
+
"""
|
74
|
+
Ensure that a service is not already registered.
|
54
75
|
|
55
|
-
|
56
|
-
|
57
|
-
|
76
|
+
Parameters
|
77
|
+
----------
|
78
|
+
obj : Any
|
79
|
+
The service to check.
|
58
80
|
|
59
|
-
Raises
|
60
|
-
|
61
|
-
|
81
|
+
Raises
|
82
|
+
------
|
83
|
+
OrionisContainerValueError
|
84
|
+
If the service is already registered.
|
62
85
|
"""
|
86
|
+
if self.has(obj):
|
87
|
+
raise OrionisContainerValueError("The service is already registered in the container.")
|
63
88
|
|
64
|
-
|
65
|
-
|
89
|
+
def _ensureIsCallable(self, concrete: Callable[..., Any]) -> None:
|
90
|
+
"""
|
91
|
+
Ensure that the given implementation is callable or instantiable.
|
66
92
|
|
67
|
-
|
68
|
-
|
93
|
+
Parameters
|
94
|
+
----------
|
95
|
+
concrete : Callable[..., Any]
|
96
|
+
The implementation to check.
|
69
97
|
|
98
|
+
Raises
|
99
|
+
------
|
100
|
+
OrionisContainerTypeError
|
101
|
+
If the implementation is not callable.
|
102
|
+
"""
|
70
103
|
if not callable(concrete):
|
71
|
-
raise
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
def transient(self, abstract: str, concrete: Callable[..., Any]) -> None:
|
76
|
-
"""Registers a service as Transient, creating a new instance on each request.
|
104
|
+
raise OrionisContainerTypeError(
|
105
|
+
f"The implementation '{str(concrete)}' must be callable or an instantiable class."
|
106
|
+
)
|
77
107
|
|
78
|
-
|
79
|
-
abstract (str): Name or key of the service to register.
|
80
|
-
concrete (Callable[..., Any]): Concrete implementation of the service.
|
81
|
-
|
82
|
-
Raises:
|
83
|
-
OrionisContainerException: If the service is already registered.
|
84
|
-
TypeError: If the implementation is not a callable or instantiable class.
|
108
|
+
def _ensureIsInstance(self, instance: Any) -> None:
|
85
109
|
"""
|
110
|
+
Ensure that the given instance is a valid object.
|
86
111
|
|
87
|
-
|
88
|
-
|
112
|
+
Parameters
|
113
|
+
----------
|
114
|
+
instance : Any
|
115
|
+
The instance to check.
|
89
116
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
"""Registers a service as Singleton, ensuring a single shared instance.
|
117
|
+
Raises
|
118
|
+
------
|
119
|
+
OrionisContainerValueError
|
120
|
+
If the instance is not a valid object.
|
121
|
+
"""
|
122
|
+
if not isinstance(instance, object) or instance.__class__.__module__ in ['builtins', 'abc']:
|
123
|
+
raise OrionisContainerValueError(
|
124
|
+
f"The instance '{str(instance)}' must be a valid object."
|
125
|
+
)
|
100
126
|
|
127
|
+
def bind(self, concrete: Callable[..., Any]) -> str:
|
128
|
+
"""
|
129
|
+
Bind a callable to the container.
|
130
|
+
This method ensures that the provided callable is not the main function,
|
131
|
+
is unique within the container, and is indeed callable. It then creates
|
132
|
+
a unique key for the callable based on its module and name, and stores
|
133
|
+
the callable in the container's bindings.
|
101
134
|
Args:
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
Raises:
|
106
|
-
OrionisContainerException: If the service is already registered.
|
107
|
-
TypeError: If the implementation is not a callable or instantiable class.
|
135
|
+
concrete (Callable[..., Any]): The callable to be bound to the container.
|
136
|
+
Returns:
|
137
|
+
str: The unique key generated for the callable.
|
108
138
|
"""
|
139
|
+
self._ensureNotMain(concrete)
|
140
|
+
self._ensureUniqueService(concrete)
|
141
|
+
self._ensureIsCallable(concrete)
|
109
142
|
|
110
|
-
|
111
|
-
self.
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
raise TypeError(f"The implementation of '{abstract}' must be a callable or instantiable class.")
|
118
|
-
|
119
|
-
self._singletons[abstract] = concrete
|
143
|
+
key = f"{concrete.__module__}.{concrete.__name__}"
|
144
|
+
self._bindings[key] = {
|
145
|
+
'callback': concrete,
|
146
|
+
'module': concrete.__module__,
|
147
|
+
'name': concrete.__name__,
|
148
|
+
'type': BINDING
|
149
|
+
}
|
120
150
|
|
121
|
-
|
122
|
-
"""Registers a service as Scoped, shared within the same request.
|
151
|
+
return key
|
123
152
|
|
153
|
+
def transient(self, concrete: Callable[..., Any]) -> str:
|
154
|
+
"""
|
155
|
+
Registers a transient service in the container.
|
156
|
+
A transient service is created each time it is requested.
|
124
157
|
Args:
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
Raises:
|
129
|
-
OrionisContainerException: If the service is already registered.
|
130
|
-
TypeError: If the implementation is not a callable or instantiable class.
|
158
|
+
concrete (Callable[..., Any]): The callable that defines the service.
|
159
|
+
Returns:
|
160
|
+
str: The unique key generated for the callable.
|
131
161
|
"""
|
162
|
+
self._ensureNotMain(concrete)
|
163
|
+
self._ensureUniqueService(concrete)
|
164
|
+
self._ensureIsCallable(concrete)
|
132
165
|
|
133
|
-
|
134
|
-
self.
|
166
|
+
key = f"{concrete.__module__}.{concrete.__name__}"
|
167
|
+
self._transients[key] = {
|
168
|
+
'callback': concrete,
|
169
|
+
'module': concrete.__module__,
|
170
|
+
'name': concrete.__name__,
|
171
|
+
'type': TRANSIENT
|
172
|
+
}
|
135
173
|
|
136
|
-
|
137
|
-
raise OrionisContainerException(f"The service '{abstract}' is already registered in the container.")
|
138
|
-
|
139
|
-
if not callable(concrete):
|
140
|
-
raise TypeError(f"The implementation of '{abstract}' must be a callable or instantiable class.")
|
141
|
-
|
142
|
-
self._scoped_services[abstract] = concrete
|
143
|
-
|
144
|
-
def instance(self, abstract: str, instance: Any) -> None:
|
145
|
-
"""Registers a specific instance in the container, allowing it to be reused.
|
174
|
+
return key
|
146
175
|
|
176
|
+
def singleton(self, concrete: Callable[..., Any]) -> str:
|
177
|
+
"""
|
178
|
+
Registers a callable as a singleton in the container.
|
179
|
+
This method ensures that the provided callable is not the main module,
|
180
|
+
is unique within the container, and is indeed callable. It then registers
|
181
|
+
the callable as a singleton, storing it in the container's singleton registry.
|
147
182
|
Args:
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
Raises:
|
152
|
-
OrionisContainerException: If the instance is already registered.
|
153
|
-
ValueError: If the provided instance is of an unexpected or invalid type.
|
183
|
+
concrete (Callable[..., Any]): The callable to be registered as a singleton.
|
184
|
+
Returns:
|
185
|
+
str: The key under which the singleton is registered in the container.
|
154
186
|
"""
|
187
|
+
self._ensureNotMain(concrete)
|
188
|
+
self._ensureUniqueService(concrete)
|
189
|
+
self._ensureIsCallable(concrete)
|
155
190
|
|
156
|
-
|
157
|
-
self.
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
raise ValueError(f"The instance of '{abstract}' must be a valid object.")
|
164
|
-
|
165
|
-
self._instances[abstract] = instance
|
191
|
+
key = f"{concrete.__module__}.{concrete.__name__}"
|
192
|
+
self._singletons[key] = {
|
193
|
+
'callback': concrete,
|
194
|
+
'module': concrete.__module__,
|
195
|
+
'name': concrete.__name__,
|
196
|
+
'type': SINGLETON
|
197
|
+
}
|
166
198
|
|
167
|
-
|
168
|
-
"""Checks if a service is registered in the container.
|
199
|
+
return key
|
169
200
|
|
170
|
-
|
171
|
-
abstract (str): Name or key of the service to check.
|
172
|
-
|
173
|
-
Returns:
|
174
|
-
bool: True if the service is registered, False otherwise.
|
175
|
-
|
176
|
-
Raises:
|
177
|
-
ValueError: If the service name (abstract) is not a valid string.
|
201
|
+
def scoped(self, concrete: Callable[..., Any]) -> str:
|
178
202
|
"""
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
# Efficient check if the service is in any of the containers
|
184
|
-
return any(abstract in container for container in [
|
185
|
-
self._bindings,
|
186
|
-
self._transients,
|
187
|
-
self._singletons,
|
188
|
-
self._scoped_services,
|
189
|
-
self._instances
|
190
|
-
])
|
191
|
-
|
192
|
-
def alias(self, abstract: str, alias: str) -> None:
|
193
|
-
"""Creates an alias for a registered service, allowing access to the service using an alternative name.
|
194
|
-
|
203
|
+
Registers a callable as a scoped service.
|
204
|
+
This method ensures that the provided callable is not the main service,
|
205
|
+
is unique, and is indeed callable. It then registers the callable in the
|
206
|
+
scoped services dictionary with relevant metadata.
|
195
207
|
Args:
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
Raises:
|
200
|
-
OrionisContainerException: If the original service is not registered.
|
201
|
-
ValueError: If the alias is not a valid string or is already in use.
|
208
|
+
concrete (Callable[..., Any]): The callable to be registered as a scoped service.
|
209
|
+
Returns:
|
210
|
+
str: The key under which the callable is registered in the scoped services dictionary.
|
202
211
|
"""
|
212
|
+
self._ensureNotMain(concrete)
|
213
|
+
self._ensureUniqueService(concrete)
|
214
|
+
self._ensureIsCallable(concrete)
|
203
215
|
|
204
|
-
|
205
|
-
self.
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
# Check if the original service is registered
|
212
|
-
if not self.has(abstract):
|
213
|
-
raise OrionisContainerException(f"The service '{abstract}' is not registered in the container.")
|
214
|
-
|
215
|
-
# Check if the alias is already in use
|
216
|
-
if alias in self._aliases:
|
217
|
-
raise ValueError(f"The alias '{alias}' is already in use.")
|
216
|
+
key = f"{concrete.__module__}.{concrete.__name__}"
|
217
|
+
self._scoped_services[key] = {
|
218
|
+
'callback': concrete,
|
219
|
+
'module': concrete.__module__,
|
220
|
+
'name': concrete.__name__,
|
221
|
+
'type': SCOPED
|
222
|
+
}
|
218
223
|
|
219
|
-
|
220
|
-
|
221
|
-
def make(self, abstract: str):
|
222
|
-
"""Automatically resolves a dependency, handling instances, singletons, scoped, transients, and aliases.
|
223
|
-
|
224
|
-
This method resolves the dependencies of a service and handles the following service types:
|
225
|
-
1. **Instances**: Returns a specific instance.
|
226
|
-
2. **Singletons**: Returns the same unique instance each time.
|
227
|
-
3. **Scoped**: Returns a shared instance within the same request.
|
228
|
-
4. **Transients**: Creates a new instance each time.
|
229
|
-
5. **Aliases**: Resolves an alias to the original service.
|
224
|
+
return key
|
230
225
|
|
226
|
+
def instance(self, instance: Any) -> str:
|
227
|
+
"""
|
228
|
+
Registers an instance as a singleton in the container.
|
231
229
|
Args:
|
232
|
-
|
233
|
-
|
230
|
+
instance (Any): The instance to be registered as a singleton.
|
234
231
|
Returns:
|
235
|
-
|
236
|
-
|
232
|
+
str: The key under which the instance is registered in the container.
|
233
|
+
"""
|
234
|
+
self._ensureNotMain(instance.__class__)
|
235
|
+
self._ensureUniqueService(instance)
|
236
|
+
self._ensureIsInstance(instance)
|
237
|
+
|
238
|
+
concrete = instance.__class__
|
239
|
+
key = f"{concrete.__module__}.{concrete.__name__}"
|
240
|
+
self._instances[key] = {
|
241
|
+
'instance': instance,
|
242
|
+
'module': concrete.__module__,
|
243
|
+
'name': concrete.__name__,
|
244
|
+
'type': INSTANCE
|
245
|
+
}
|
246
|
+
|
247
|
+
return key
|
248
|
+
|
249
|
+
def alias(self, alias: str, concrete: Any) -> None:
|
250
|
+
"""
|
251
|
+
Creates an alias for a registered service.
|
252
|
+
Args:
|
253
|
+
alias (str): The alias name to be used for the service.
|
254
|
+
concrete (Any): The actual service instance or callable to be aliased.
|
237
255
|
Raises:
|
238
|
-
OrionisContainerException: If the
|
256
|
+
OrionisContainerException: If the concrete instance is not a valid object or if the alias is a primitive type.
|
239
257
|
"""
|
240
|
-
|
241
|
-
|
242
|
-
return self._instances[abstract]
|
258
|
+
if not callable(concrete) and not isinstance(concrete, object):
|
259
|
+
raise OrionisContainerException(f"The instance '{str(concrete)}' must be a valid object.")
|
243
260
|
|
244
|
-
|
245
|
-
|
246
|
-
if abstract not in self._instances:
|
247
|
-
self._instances[abstract] = self._resolve(self._singletons[abstract])
|
248
|
-
return self._instances[abstract]
|
261
|
+
if self._instance._validate_types.isPrimitive(alias):
|
262
|
+
raise OrionisContainerException(f"Cannot use primitive type '{alias}' as an alias.")
|
249
263
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
264
|
+
if isinstance(concrete, object) and concrete.__class__.__module__ not in ['builtins', 'abc']:
|
265
|
+
cls_concrete = concrete.__class__
|
266
|
+
current_key = f"{cls_concrete.__module__}.{cls_concrete.__name__}"
|
267
|
+
elif callable(concrete):
|
268
|
+
current_key = f"{concrete.__module__}.{concrete.__name__}"
|
255
269
|
|
256
|
-
|
257
|
-
if abstract in self._transients:
|
258
|
-
return self._resolve(self._transients[abstract])
|
270
|
+
self._aliases[alias] = current_key
|
259
271
|
|
260
|
-
|
261
|
-
|
262
|
-
|
272
|
+
def has(self, obj: Any) -> bool:
|
273
|
+
"""
|
274
|
+
Checks if a service is registered in the container.
|
263
275
|
|
264
|
-
|
265
|
-
|
266
|
-
|
276
|
+
Parameters
|
277
|
+
----------
|
278
|
+
obj : Any
|
279
|
+
The service class, instance, or alias to check.
|
267
280
|
|
268
|
-
|
281
|
+
Returns
|
282
|
+
-------
|
283
|
+
bool
|
284
|
+
True if the service is registered, False otherwise.
|
285
|
+
"""
|
286
|
+
if isinstance(obj, str):
|
287
|
+
return obj in self._aliases or obj in (
|
288
|
+
self._bindings | self._transients | self._singletons | self._scoped_services | self._instances
|
289
|
+
)
|
269
290
|
|
270
|
-
|
271
|
-
|
291
|
+
if isinstance(obj, object) and obj.__class__.__module__ not in {'builtins', 'abc'}:
|
292
|
+
key = f"{obj.__class__.__module__}.{obj.__class__.__name__}"
|
293
|
+
return key in self._instances
|
272
294
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
295
|
+
if callable(obj):
|
296
|
+
key = f"{obj.__module__}.{obj.__name__}"
|
297
|
+
return key in (
|
298
|
+
self._bindings | self._transients | self._singletons | self._scoped_services | self._aliases
|
299
|
+
)
|
277
300
|
|
278
|
-
|
279
|
-
Any: El resultado de ejecutar el método con las dependencias resueltas.
|
301
|
+
return False
|
280
302
|
|
281
|
-
|
282
|
-
AttributeError: Si el método no existe en la instancia.
|
303
|
+
def make(self, abstract: Any) -> Any:
|
283
304
|
"""
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
dependencies[name] = param.default
|
303
|
-
else:
|
304
|
-
raise OrionisContainerValueError(f"Cannot resolve parameter '{name}' in method '{method_name}'")
|
305
|
-
|
306
|
-
return method(**dependencies)
|
307
|
-
|
305
|
+
Create and return an instance of a registered service.
|
306
|
+
|
307
|
+
Parameters
|
308
|
+
----------
|
309
|
+
abstract : Any
|
310
|
+
The service class or alias to instantiate.
|
311
|
+
|
312
|
+
Returns
|
313
|
+
-------
|
314
|
+
Any
|
315
|
+
An instance of the requested service.
|
316
|
+
|
317
|
+
Raises
|
318
|
+
------
|
319
|
+
OrionisContainerException
|
320
|
+
If the service is not found in the container.
|
321
|
+
"""
|
322
|
+
key = self._aliases.get(abstract, abstract)
|
308
323
|
|
309
|
-
|
310
|
-
|
324
|
+
if key in self._instances:
|
325
|
+
return self._instances[key]['instance']
|
311
326
|
|
312
|
-
|
313
|
-
|
327
|
+
if key in self._singletons:
|
328
|
+
self._instances[key] = {'instance': self._resolve(self._singletons[key]['callback'])}
|
329
|
+
return self._instances[key]['instance']
|
314
330
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
raise OrionisContainerException(f"Cannot register a service with a name equal to a primitive type: '{abstract}'.")
|
331
|
+
if key in self._scoped_services:
|
332
|
+
if key not in self._scoped_instances:
|
333
|
+
self._scoped_instances[key] = self._resolve(self._scoped_services[key]['callback'])
|
334
|
+
return self._scoped_instances[key]
|
320
335
|
|
321
|
-
|
322
|
-
|
336
|
+
if key in self._transients:
|
337
|
+
return self._resolve(self._transients[key]['callback'])
|
323
338
|
|
324
|
-
|
339
|
+
if key in self._bindings:
|
340
|
+
return self._resolve(self._bindings[key]['callback'])
|
325
341
|
|
326
|
-
|
327
|
-
concrete (Callable[..., Any]): Concrete implementation of the service.
|
342
|
+
raise OrionisContainerException(f"Service '{abstract}' is not registered in the container.")
|
328
343
|
|
329
|
-
|
330
|
-
Any: The resolved service instance.
|
331
|
-
|
332
|
-
Raises:
|
333
|
-
ValueError: If there is a constructor parameter whose type cannot be resolved.
|
344
|
+
def _resolve(self, concrete: Callable[..., Any]) -> Any:
|
334
345
|
"""
|
335
|
-
|
336
|
-
constructor = inspect.signature(concrete.__init__)
|
337
|
-
parameters = constructor.parameters
|
338
|
-
|
339
|
-
# If the class has no parameters in its constructor, instantiate it directly
|
340
|
-
if len(parameters) == 0 or (len(parameters) == 1 and "self" in parameters):
|
341
|
-
return concrete()
|
346
|
+
Resolve and instantiate a given service class or function.
|
342
347
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
continue
|
348
|
+
This method analyzes the constructor of the given class (or callable),
|
349
|
+
retrieves its dependencies, and resolves them recursively, while respecting
|
350
|
+
the service lifecycle.
|
351
|
+
"""
|
348
352
|
|
353
|
+
# Step 1: Retrieve the constructor signature of the class or callable.
|
354
|
+
try:
|
355
|
+
signature = inspect.signature(concrete)
|
356
|
+
except ValueError as e:
|
357
|
+
raise OrionisContainerException(f"Unable to inspect signature of {concrete}: {str(e)}")
|
358
|
+
|
359
|
+
# Step 2: Prepare a dictionary for resolved dependencies and a queue for unresolved ones.
|
360
|
+
resolved_dependencies: Dict[str, Any] = {}
|
361
|
+
unresolved_dependencies = deque()
|
362
|
+
|
363
|
+
# Step 3: Iterate through the parameters of the constructor.
|
364
|
+
for param_name, param in signature.parameters.items():
|
365
|
+
if param_name == 'self':
|
366
|
+
# Skip 'self' in methods
|
367
|
+
continue
|
368
|
+
|
369
|
+
# If parameter has no annotation and no default value, it's unresolved
|
370
|
+
if param.annotation is param.empty and param.default is param.empty:
|
371
|
+
unresolved_dependencies.append(param_name)
|
372
|
+
continue
|
373
|
+
|
374
|
+
# Resolve dependencies based on annotations (excluding primitive types)
|
375
|
+
if param.annotation is not param.empty:
|
349
376
|
param_type = param.annotation
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
if
|
354
|
-
|
355
|
-
dependencies[name] = param.default
|
356
|
-
continue
|
377
|
+
# Check if it's a registered service, if so, resolve it through the container
|
378
|
+
if isinstance(param_type, type) and not isinstance(param_type, (int, str, bool, float)) and not issubclass(param_type, (int, str, bool, float)):
|
379
|
+
# Check if the service is registered in the container
|
380
|
+
if self.has(param_type):
|
381
|
+
resolved_dependencies[param_name] = self.make(f"{param_type.__module__}.{param_type.__name__}")
|
357
382
|
else:
|
358
|
-
|
359
|
-
|
360
|
-
# If the parameter type is a primitive (str, int, etc.), pass it as is
|
361
|
-
if isinstance(param_type, type):
|
362
|
-
if param.default != param.empty:
|
363
|
-
dependencies[name] = param.default if param.default != param.empty else param_type()
|
364
|
-
else:
|
365
|
-
dependencies[name] = param_type() # Provide default value for primitive types if not specified
|
383
|
+
resolved_dependencies[param_name] = self._resolve_dependency(param_type)
|
366
384
|
else:
|
367
|
-
|
385
|
+
resolved_dependencies[param_name] = param_type # It's a primitive, use as-is
|
368
386
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
else:
|
373
|
-
dependencies[name] = self.make(dep_name)
|
387
|
+
# Resolve parameters with default values (without annotations)
|
388
|
+
elif param.default is not param.empty:
|
389
|
+
resolved_dependencies[param_name] = param.default
|
374
390
|
|
375
|
-
|
391
|
+
# Step 4: Resolve any remaining unresolved dependencies.
|
392
|
+
while unresolved_dependencies:
|
393
|
+
dep_name = unresolved_dependencies.popleft()
|
394
|
+
if dep_name not in resolved_dependencies:
|
395
|
+
resolved_dependencies[dep_name] = self._resolve_dependency(dep_name)
|
376
396
|
|
377
|
-
|
397
|
+
# Step 5: Instantiate the class with resolved dependencies.
|
398
|
+
try:
|
399
|
+
return concrete(**resolved_dependencies)
|
400
|
+
except Exception as e:
|
401
|
+
raise OrionisContainerException(f"Failed to instantiate {concrete}: {str(e)}")
|
378
402
|
|
379
|
-
def
|
380
|
-
"""
|
403
|
+
def _resolve_dependency(self, dep_type: Any) -> Any:
|
404
|
+
"""
|
405
|
+
Resolves a dependency based on the provided type.
|
381
406
|
|
382
|
-
This method
|
383
|
-
|
407
|
+
This method looks for the type in the container and returns the instance,
|
408
|
+
respecting the lifecycle of the service (transient, singleton, etc.).
|
384
409
|
"""
|
385
|
-
|
410
|
+
# Check if the dependency exists in the container or create it if necessary
|
411
|
+
# If it's a class type
|
412
|
+
if isinstance(dep_type, type):
|
413
|
+
if self.has(dep_type):
|
414
|
+
# Resolves the service through the container
|
415
|
+
return self.make(f"{dep_type.__module__}.{dep_type.__name__}")
|
416
|
+
else:
|
417
|
+
# Instantiate the class if not found in the container
|
418
|
+
return self._resolve(dep_type)
|
419
|
+
|
420
|
+
raise OrionisContainerException(f"Cannot resolve dependency of type {dep_type}")
|
@@ -34,3 +34,21 @@ class OrionisContainerValueError(ValueError):
|
|
34
34
|
def __str__(self) -> str:
|
35
35
|
"""Retorna una representación en cadena de la excepción."""
|
36
36
|
return f"[OrionisContainerValueError] {self.args[0]}"
|
37
|
+
|
38
|
+
class OrionisContainerTypeError(TypeError):
|
39
|
+
"""
|
40
|
+
Custom exception for TypeError related to the Orionis container.
|
41
|
+
"""
|
42
|
+
|
43
|
+
def __init__(self, message: str) -> None:
|
44
|
+
"""
|
45
|
+
Initializes the exception with an error message.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
message (str): Descriptive error message.
|
49
|
+
"""
|
50
|
+
super().__init__(message)
|
51
|
+
|
52
|
+
def __str__(self) -> str:
|
53
|
+
"""Returns a string representation of the exception."""
|
54
|
+
return f"[OrionisContainerTypeError] {self.args[0]}"
|
@@ -2,151 +2,221 @@ from abc import ABC, abstractmethod
|
|
2
2
|
from typing import Any, Callable
|
3
3
|
|
4
4
|
class IContainer(ABC):
|
5
|
-
"""Service container and dependency injection."""
|
6
5
|
|
7
6
|
@abstractmethod
|
8
|
-
def
|
9
|
-
"""
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
concrete (Callable[..., Any]): Concrete implementation of the service.
|
7
|
+
def _newRequest(self) -> None:
|
8
|
+
"""
|
9
|
+
Reset scoped instances at the beginning of a new request.
|
10
|
+
"""
|
11
|
+
pass
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
@abstractmethod
|
14
|
+
def _ensureNotMain(self, concrete: Callable[..., Any]) -> str:
|
15
|
+
"""
|
16
|
+
Ensure that a class is not defined in the main script.
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
----------
|
20
|
+
concrete : Callable[..., Any]
|
21
|
+
The class or function to check.
|
22
|
+
|
23
|
+
Returns
|
24
|
+
-------
|
25
|
+
str
|
26
|
+
The fully qualified name of the class.
|
27
|
+
|
28
|
+
Raises
|
29
|
+
------
|
30
|
+
OrionisContainerValueError
|
31
|
+
If the class is defined in the main module.
|
18
32
|
"""
|
19
33
|
pass
|
20
34
|
|
21
35
|
@abstractmethod
|
22
|
-
def
|
23
|
-
"""
|
36
|
+
def _ensureUniqueService(self, obj: Any) -> None:
|
37
|
+
"""
|
38
|
+
Ensure that a service is not already registered.
|
24
39
|
|
25
|
-
|
26
|
-
|
27
|
-
|
40
|
+
Parameters
|
41
|
+
----------
|
42
|
+
obj : Any
|
43
|
+
The service to check.
|
28
44
|
|
29
|
-
Raises
|
30
|
-
|
31
|
-
|
45
|
+
Raises
|
46
|
+
------
|
47
|
+
OrionisContainerValueError
|
48
|
+
If the service is already registered.
|
32
49
|
"""
|
33
50
|
pass
|
34
51
|
|
35
52
|
@abstractmethod
|
36
|
-
def
|
37
|
-
"""
|
53
|
+
def _ensureIsCallable(self, concrete: Callable[..., Any]) -> None:
|
54
|
+
"""
|
55
|
+
Ensure that the given implementation is callable or instantiable.
|
38
56
|
|
39
|
-
|
40
|
-
|
41
|
-
|
57
|
+
Parameters
|
58
|
+
----------
|
59
|
+
concrete : Callable[..., Any]
|
60
|
+
The implementation to check.
|
42
61
|
|
43
|
-
Raises
|
44
|
-
|
45
|
-
|
62
|
+
Raises
|
63
|
+
------
|
64
|
+
OrionisContainerTypeError
|
65
|
+
If the implementation is not callable.
|
46
66
|
"""
|
47
67
|
pass
|
48
68
|
|
49
69
|
@abstractmethod
|
50
|
-
def
|
51
|
-
"""
|
70
|
+
def _ensureIsInstance(self, instance: Any) -> None:
|
71
|
+
"""
|
72
|
+
Ensure that the given instance is a valid object.
|
52
73
|
|
53
|
-
|
54
|
-
|
55
|
-
|
74
|
+
Parameters
|
75
|
+
----------
|
76
|
+
instance : Any
|
77
|
+
The instance to check.
|
56
78
|
|
57
|
-
Raises
|
58
|
-
|
59
|
-
|
79
|
+
Raises
|
80
|
+
------
|
81
|
+
OrionisContainerValueError
|
82
|
+
If the instance is not a valid object.
|
60
83
|
"""
|
61
84
|
pass
|
62
85
|
|
63
86
|
@abstractmethod
|
64
|
-
def
|
65
|
-
"""
|
66
|
-
|
87
|
+
def bind(self, concrete: Callable[..., Any]) -> str:
|
88
|
+
"""
|
89
|
+
Bind a callable to the container.
|
90
|
+
This method ensures that the provided callable is not the main function,
|
91
|
+
is unique within the container, and is indeed callable. It then creates
|
92
|
+
a unique key for the callable based on its module and name, and stores
|
93
|
+
the callable in the container's bindings.
|
67
94
|
Args:
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
Raises:
|
72
|
-
OrionisContainerException: If the instance is already registered.
|
73
|
-
ValueError: If the provided instance is of an unexpected or invalid type.
|
95
|
+
concrete (Callable[..., Any]): The callable to be bound to the container.
|
96
|
+
Returns:
|
97
|
+
str: The unique key generated for the callable.
|
74
98
|
"""
|
75
99
|
pass
|
76
100
|
|
77
101
|
@abstractmethod
|
78
|
-
def
|
79
|
-
"""
|
80
|
-
|
102
|
+
def transient(self, concrete: Callable[..., Any]) -> str:
|
103
|
+
"""
|
104
|
+
Registers a transient service in the container.
|
105
|
+
A transient service is created each time it is requested.
|
81
106
|
Args:
|
82
|
-
|
83
|
-
|
107
|
+
concrete (Callable[..., Any]): The callable that defines the service.
|
84
108
|
Returns:
|
85
|
-
|
86
|
-
|
87
|
-
Raises:
|
88
|
-
ValueError: If the service name (abstract) is not a valid string.
|
109
|
+
str: The unique key generated for the callable.
|
89
110
|
"""
|
90
111
|
pass
|
91
112
|
|
92
113
|
@abstractmethod
|
93
|
-
def
|
94
|
-
"""
|
95
|
-
|
114
|
+
def singleton(self, concrete: Callable[..., Any]) -> str:
|
115
|
+
"""
|
116
|
+
Registers a callable as a singleton in the container.
|
117
|
+
This method ensures that the provided callable is not the main module,
|
118
|
+
is unique within the container, and is indeed callable. It then registers
|
119
|
+
the callable as a singleton, storing it in the container's singleton registry.
|
96
120
|
Args:
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
Raises:
|
101
|
-
OrionisContainerException: If the original service is not registered.
|
102
|
-
ValueError: If the alias is not a valid string or is already in use.
|
121
|
+
concrete (Callable[..., Any]): The callable to be registered as a singleton.
|
122
|
+
Returns:
|
123
|
+
str: The key under which the singleton is registered in the container.
|
103
124
|
"""
|
104
125
|
pass
|
105
126
|
|
106
127
|
@abstractmethod
|
107
|
-
def
|
108
|
-
"""
|
109
|
-
|
110
|
-
This method
|
111
|
-
|
112
|
-
|
113
|
-
3. **Scoped**: Returns a shared instance within the same request.
|
114
|
-
4. **Transients**: Creates a new instance each time.
|
115
|
-
5. **Aliases**: Resolves an alias to the original service.
|
116
|
-
|
128
|
+
def scoped(self, concrete: Callable[..., Any]) -> str:
|
129
|
+
"""
|
130
|
+
Registers a callable as a scoped service.
|
131
|
+
This method ensures that the provided callable is not the main service,
|
132
|
+
is unique, and is indeed callable. It then registers the callable in the
|
133
|
+
scoped services dictionary with relevant metadata.
|
117
134
|
Args:
|
118
|
-
|
135
|
+
concrete (Callable[..., Any]): The callable to be registered as a scoped service.
|
136
|
+
Returns:
|
137
|
+
str: The key under which the callable is registered in the scoped services dictionary.
|
138
|
+
"""
|
139
|
+
pass
|
119
140
|
|
141
|
+
@abstractmethod
|
142
|
+
def instance(self, instance: Any) -> str:
|
143
|
+
"""
|
144
|
+
Registers an instance as a singleton in the container.
|
145
|
+
Args:
|
146
|
+
instance (Any): The instance to be registered as a singleton.
|
120
147
|
Returns:
|
121
|
-
|
148
|
+
str: The key under which the instance is registered in the container.
|
149
|
+
"""
|
150
|
+
pass
|
122
151
|
|
152
|
+
@abstractmethod
|
153
|
+
def alias(self, alias: str, concrete: Any) -> None:
|
154
|
+
"""
|
155
|
+
Creates an alias for a registered service.
|
156
|
+
Args:
|
157
|
+
alias (str): The alias name to be used for the service.
|
158
|
+
concrete (Any): The actual service instance or callable to be aliased.
|
123
159
|
Raises:
|
124
|
-
OrionisContainerException: If the
|
160
|
+
OrionisContainerException: If the concrete instance is not a valid object or if the alias is a primitive type.
|
125
161
|
"""
|
126
162
|
pass
|
127
163
|
|
128
164
|
@abstractmethod
|
129
|
-
def
|
130
|
-
"""
|
165
|
+
def has(self, obj: Any) -> bool:
|
166
|
+
"""
|
167
|
+
Checks if a service is registered in the container.
|
131
168
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
169
|
+
Parameters
|
170
|
+
----------
|
171
|
+
obj : Any
|
172
|
+
The service class, instance, or alias to check.
|
136
173
|
|
137
|
-
Returns
|
138
|
-
|
174
|
+
Returns
|
175
|
+
-------
|
176
|
+
bool
|
177
|
+
True if the service is registered, False otherwise.
|
178
|
+
"""
|
179
|
+
pass
|
139
180
|
|
140
|
-
|
141
|
-
|
181
|
+
@abstractmethod
|
182
|
+
def make(self, abstract: Any) -> Any:
|
183
|
+
"""
|
184
|
+
Create and return an instance of a registered service.
|
185
|
+
|
186
|
+
Parameters
|
187
|
+
----------
|
188
|
+
abstract : Any
|
189
|
+
The service class or alias to instantiate.
|
190
|
+
|
191
|
+
Returns
|
192
|
+
-------
|
193
|
+
Any
|
194
|
+
An instance of the requested service.
|
195
|
+
|
196
|
+
Raises
|
197
|
+
------
|
198
|
+
OrionisContainerException
|
199
|
+
If the service is not found in the container.
|
142
200
|
"""
|
143
201
|
pass
|
144
202
|
|
145
203
|
@abstractmethod
|
146
|
-
def
|
147
|
-
"""
|
204
|
+
def _resolve(self, concrete: Callable[..., Any]) -> Any:
|
205
|
+
"""
|
206
|
+
Resolve and instantiate a given service class or function.
|
207
|
+
|
208
|
+
This method analyzes the constructor of the given class (or callable),
|
209
|
+
retrieves its dependencies, and resolves them recursively, while respecting
|
210
|
+
the service lifecycle.
|
211
|
+
"""
|
212
|
+
pass
|
213
|
+
|
214
|
+
@abstractmethod
|
215
|
+
def _resolve_dependency(self, dep_type: Any) -> Any:
|
216
|
+
"""
|
217
|
+
Resolves a dependency based on the provided type.
|
148
218
|
|
149
|
-
This method
|
150
|
-
|
219
|
+
This method looks for the type in the container and returns the instance,
|
220
|
+
respecting the lifecycle of the service (transient, singleton, etc.).
|
151
221
|
"""
|
152
222
|
pass
|
@@ -1,6 +1,6 @@
|
|
1
1
|
orionis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
orionis/cli_manager.py,sha256=9wNVJxB0HyqUbNesUvkwlsqTyUbZwK6R46iVLE5WVBQ,1715
|
3
|
-
orionis/framework.py,sha256=
|
3
|
+
orionis/framework.py,sha256=erUuf0Lfq0_tWOuxBX6r_arQFnqBRgk7te6RPxymQRI,1386
|
4
4
|
orionis/luminate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
orionis/luminate/app.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
orionis/luminate/bootstrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -53,8 +53,8 @@ orionis/luminate/console/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
53
53
|
orionis/luminate/console/scripts/management.py,sha256=KT6Bg8kyuUw63SNAtZo6tLH6syOEkxM9J70tCpKgrUw,2860
|
54
54
|
orionis/luminate/console/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
55
|
orionis/luminate/console/tasks/scheduler.py,sha256=h3fRVTx6TuZVcY7zZ6oOzGDnlDAWaRwkj92pFTGUm6E,22686
|
56
|
-
orionis/luminate/container/container.py,sha256=
|
57
|
-
orionis/luminate/container/exception.py,sha256=
|
56
|
+
orionis/luminate/container/container.py,sha256=O4AKaRQK2XfN0es70lIxUGkP0mxxemSsHOOuvtJVcUU,16212
|
57
|
+
orionis/luminate/container/exception.py,sha256=ap1SqYEjQEEHXJJTNmL7V1jrmRjgT5_7geZ95MYkhMA,1691
|
58
58
|
orionis/luminate/container/types.py,sha256=PbPNOJ8e4SGzCmu-zOmCQmDzt1b9I73v3fw_xzLq9RU,932
|
59
59
|
orionis/luminate/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
60
|
orionis/luminate/contracts/bootstrap/config/bootstrapper_interface.py,sha256=De6nKQcZpN1dWsubXWUO229ZH4IbCoUaGtP5HOUw2Ps,2251
|
@@ -80,7 +80,7 @@ orionis/luminate/contracts/console/register_interface.py,sha256=rhHI_as_3yNWtdgQ
|
|
80
80
|
orionis/luminate/contracts/console/runner_interface.py,sha256=vWLtMhl0m1T6rfCUHZbxGQJl9ZWTXlp3HjcTfCAztGk,1644
|
81
81
|
orionis/luminate/contracts/console/schedule_interface.py,sha256=_dsR0gCvJ7_67lwPUAzBwQFHNvWM6jVjcg1EdPqDIIo,10117
|
82
82
|
orionis/luminate/contracts/console/task_manager_interface.py,sha256=sOmeifoncpWCG2WYh4q3QZ7M7w7P9Onb3Jxw9X2lpXE,1197
|
83
|
-
orionis/luminate/contracts/container/container_interface.py,sha256=
|
83
|
+
orionis/luminate/contracts/container/container_interface.py,sha256=c_QRQHXGIujiDnYXyt--3J2LKXngVtoB3O-qchPmFDQ,6899
|
84
84
|
orionis/luminate/contracts/container/types_interface.py,sha256=GCH7x3PjpXKPET3l84GcXbcM8cpne8AGrmTw-uFaT24,526
|
85
85
|
orionis/luminate/contracts/facades/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
86
86
|
orionis/luminate/contracts/facades/env_interface.py,sha256=aN9dZdsuCpy3meZqMxyxLCYlKEIF7XkRNXmP7aS7eCw,1664
|
@@ -140,9 +140,9 @@ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
140
140
|
tests/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
141
141
|
tests/tools/class_example.py,sha256=dIPD997Y15n6WmKhWoOFSwEldRm9MdOHTZZ49eF1p3c,1056
|
142
142
|
tests/tools/test_reflection.py,sha256=dNN5p_xAosyEf0ddAElmmmTfhcTtBd4zBNl7qzgnsc0,5242
|
143
|
-
orionis-0.
|
144
|
-
orionis-0.
|
145
|
-
orionis-0.
|
146
|
-
orionis-0.
|
147
|
-
orionis-0.
|
148
|
-
orionis-0.
|
143
|
+
orionis-0.23.0.dist-info/LICENCE,sha256=-_4cF2EBKuYVS_SQpy1uapq0oJPUU1vl_RUWSy2jJTo,1111
|
144
|
+
orionis-0.23.0.dist-info/METADATA,sha256=IBV6Pic2odjpNmHFEEZUIDIGrkjVPg48VsrQApR669c,2978
|
145
|
+
orionis-0.23.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
146
|
+
orionis-0.23.0.dist-info/entry_points.txt,sha256=eef1_CVewfokKjrGBynXa06KabSJYo7LlDKKIKvs1cM,53
|
147
|
+
orionis-0.23.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
|
148
|
+
orionis-0.23.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|