python-injection 0.15.0__py3-none-any.whl → 0.17.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.
- injection/__init__.py +6 -3
- injection/__init__.pyi +25 -7
- injection/_core/injectables.py +38 -9
- injection/_core/module.py +9 -17
- injection/_core/scope.py +87 -15
- injection/_core/slots.py +1 -23
- {python_injection-0.15.0.dist-info → python_injection-0.17.0.dist-info}/METADATA +1 -1
- {python_injection-0.15.0.dist-info → python_injection-0.17.0.dist-info}/RECORD +11 -11
- /injection/{integrations → ext}/__init__.py +0 -0
- /injection/{integrations → ext}/fastapi.py +0 -0
- {python_injection-0.15.0.dist-info → python_injection-0.17.0.dist-info}/WHEEL +0 -0
injection/__init__.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
from ._core.descriptors import LazyInstance
|
2
2
|
from ._core.injectables import Injectable
|
3
3
|
from ._core.module import Mode, Module, Priority, mod
|
4
|
-
from ._core.scope import
|
5
|
-
from ._core.
|
4
|
+
from ._core.scope import ScopeFacade as Scope
|
5
|
+
from ._core.scope import ScopeKind, adefine_scope, define_scope
|
6
|
+
from ._core.slots import SlotKey
|
6
7
|
|
7
8
|
__all__ = (
|
8
9
|
"Injectable",
|
@@ -10,7 +11,9 @@ __all__ = (
|
|
10
11
|
"Mode",
|
11
12
|
"Module",
|
12
13
|
"Priority",
|
13
|
-
"
|
14
|
+
"Scope",
|
15
|
+
"ScopeKind",
|
16
|
+
"SlotKey",
|
14
17
|
"adefine_scope",
|
15
18
|
"afind_instance",
|
16
19
|
"aget_instance",
|
injection/__init__.pyi
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from abc import abstractmethod
|
2
|
-
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
2
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping
|
3
3
|
from contextlib import asynccontextmanager, contextmanager
|
4
4
|
from enum import Enum
|
5
5
|
from logging import Logger
|
@@ -11,6 +11,7 @@ from ._core.common.type import TypeInfo as _TypeInfo
|
|
11
11
|
from ._core.module import InjectableFactory as _InjectableFactory
|
12
12
|
from ._core.module import ModeStr, PriorityStr
|
13
13
|
from ._core.module import Recipe as _Recipe
|
14
|
+
from ._core.scope import ScopeKindStr
|
14
15
|
|
15
16
|
__MODULE: Final[Module] = ...
|
16
17
|
|
@@ -30,9 +31,17 @@ should_be_injectable = __MODULE.should_be_injectable
|
|
30
31
|
singleton = __MODULE.singleton
|
31
32
|
|
32
33
|
@asynccontextmanager
|
33
|
-
def adefine_scope(
|
34
|
+
def adefine_scope(
|
35
|
+
name: str,
|
36
|
+
/,
|
37
|
+
kind: ScopeKind | ScopeKindStr = ...,
|
38
|
+
) -> AsyncIterator[Scope]: ...
|
34
39
|
@contextmanager
|
35
|
-
def define_scope(
|
40
|
+
def define_scope(
|
41
|
+
name: str,
|
42
|
+
/,
|
43
|
+
kind: ScopeKind | ScopeKindStr = ...,
|
44
|
+
) -> Iterator[Scope]: ...
|
36
45
|
def mod(name: str = ..., /) -> Module:
|
37
46
|
"""
|
38
47
|
Short syntax for `Module.from_name`.
|
@@ -47,10 +56,19 @@ class Injectable[T](Protocol):
|
|
47
56
|
@abstractmethod
|
48
57
|
def get_instance(self) -> T: ...
|
49
58
|
|
59
|
+
@final
|
60
|
+
class ScopeKind(Enum):
|
61
|
+
CONTEXTUAL = ...
|
62
|
+
SHARED = ...
|
63
|
+
|
50
64
|
@runtime_checkable
|
51
|
-
class
|
65
|
+
class Scope(Protocol):
|
52
66
|
@abstractmethod
|
53
|
-
def
|
67
|
+
def set_slot[T](self, key: SlotKey[T], value: T) -> Self: ...
|
68
|
+
@abstractmethod
|
69
|
+
def slot_map(self, mapping: Mapping[SlotKey[Any], Any], /) -> Self: ...
|
70
|
+
|
71
|
+
class SlotKey[T]: ...
|
54
72
|
|
55
73
|
class LazyInstance[T]:
|
56
74
|
def __init__(
|
@@ -179,12 +197,12 @@ class Module:
|
|
179
197
|
|
180
198
|
def reserve_scoped_slot[T](
|
181
199
|
self,
|
182
|
-
|
200
|
+
cls: type[T],
|
183
201
|
/,
|
184
202
|
scope_name: str,
|
185
203
|
*,
|
186
204
|
mode: Mode | ModeStr = ...,
|
187
|
-
) ->
|
205
|
+
) -> SlotKey[T]: ...
|
188
206
|
def make_injected_function[**P, T](
|
189
207
|
self,
|
190
208
|
wrapped: Callable[P, T],
|
injection/_core/injectables.py
CHANGED
@@ -17,8 +17,14 @@ from injection._core.common.asynchronous import Caller
|
|
17
17
|
from injection._core.common.asynchronous import (
|
18
18
|
create_semaphore as _create_async_semaphore,
|
19
19
|
)
|
20
|
-
from injection._core.scope import
|
21
|
-
|
20
|
+
from injection._core.scope import (
|
21
|
+
Scope,
|
22
|
+
get_scope,
|
23
|
+
in_scope_cache,
|
24
|
+
remove_scoped_values,
|
25
|
+
)
|
26
|
+
from injection._core.slots import SlotKey
|
27
|
+
from injection.exceptions import EmptySlotError, InjectionError
|
22
28
|
|
23
29
|
|
24
30
|
@runtime_checkable
|
@@ -123,7 +129,7 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
123
129
|
|
124
130
|
@property
|
125
131
|
def is_locked(self) -> bool:
|
126
|
-
return
|
132
|
+
return in_scope_cache(self, self.scope_name)
|
127
133
|
|
128
134
|
@abstractmethod
|
129
135
|
async def abuild(self, scope: Scope) -> T:
|
@@ -143,10 +149,6 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
143
149
|
factory = partial(self.build, scope)
|
144
150
|
return self.logic.get_or_create(scope.cache, self, factory)
|
145
151
|
|
146
|
-
def setdefault(self, instance: T) -> T:
|
147
|
-
scope = self.__get_scope()
|
148
|
-
return self.logic.get_or_create(scope.cache, self, lambda: instance)
|
149
|
-
|
150
152
|
def unlock(self) -> None:
|
151
153
|
if self.is_locked:
|
152
154
|
raise RuntimeError(f"To unlock, close the `{self.scope_name}` scope.")
|
@@ -188,8 +190,35 @@ class SimpleScopedInjectable[T](ScopedInjectable[T, T]):
|
|
188
190
|
return self.factory.call()
|
189
191
|
|
190
192
|
def unlock(self) -> None:
|
191
|
-
|
192
|
-
|
193
|
+
remove_scoped_values(self, self.scope_name)
|
194
|
+
|
195
|
+
|
196
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
197
|
+
class ScopedSlotInjectable[T](Injectable[T]):
|
198
|
+
cls: type[T]
|
199
|
+
scope_name: str
|
200
|
+
key: SlotKey[T] = field(default_factory=SlotKey)
|
201
|
+
|
202
|
+
@property
|
203
|
+
def is_locked(self) -> bool:
|
204
|
+
return in_scope_cache(self.key, self.scope_name)
|
205
|
+
|
206
|
+
async def aget_instance(self) -> T:
|
207
|
+
return self.get_instance()
|
208
|
+
|
209
|
+
def get_instance(self) -> T:
|
210
|
+
scope_name = self.scope_name
|
211
|
+
scope = get_scope(scope_name)
|
212
|
+
|
213
|
+
try:
|
214
|
+
return scope.cache[self.key]
|
215
|
+
except KeyError as exc:
|
216
|
+
raise EmptySlotError(
|
217
|
+
f"The slot for `{self.cls}` isn't set in the current `{scope_name}` scope."
|
218
|
+
) from exc
|
219
|
+
|
220
|
+
def unlock(self) -> None:
|
221
|
+
remove_scoped_values(self.key, self.scope_name)
|
193
222
|
|
194
223
|
|
195
224
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
injection/_core/module.py
CHANGED
@@ -64,14 +64,14 @@ from injection._core.injectables import (
|
|
64
64
|
CMScopedInjectable,
|
65
65
|
Injectable,
|
66
66
|
ScopedInjectable,
|
67
|
+
ScopedSlotInjectable,
|
67
68
|
ShouldBeInjectable,
|
68
69
|
SimpleInjectable,
|
69
70
|
SimpleScopedInjectable,
|
70
71
|
SingletonInjectable,
|
71
72
|
)
|
72
|
-
from injection._core.slots import
|
73
|
+
from injection._core.slots import SlotKey
|
73
74
|
from injection.exceptions import (
|
74
|
-
EmptySlotError,
|
75
75
|
ModuleError,
|
76
76
|
ModuleLockError,
|
77
77
|
ModuleNotUsedError,
|
@@ -492,11 +492,8 @@ class Module(Broker, EventListener):
|
|
492
492
|
|
493
493
|
def should_be_injectable[T](self, wrapped: type[T] | None = None, /) -> Any:
|
494
494
|
def decorator(wp: type[T]) -> type[T]:
|
495
|
-
|
496
|
-
|
497
|
-
injectable=ShouldBeInjectable(wp),
|
498
|
-
mode=Mode.FALLBACK,
|
499
|
-
)
|
495
|
+
injectable = ShouldBeInjectable(wp)
|
496
|
+
updater = Updater.with_basics(wp, injectable, Mode.FALLBACK)
|
500
497
|
self.update(updater)
|
501
498
|
return wp
|
502
499
|
|
@@ -543,21 +540,16 @@ class Module(Broker, EventListener):
|
|
543
540
|
|
544
541
|
def reserve_scoped_slot[T](
|
545
542
|
self,
|
546
|
-
|
543
|
+
cls: type[T],
|
547
544
|
/,
|
548
545
|
scope_name: str,
|
549
546
|
*,
|
550
547
|
mode: Mode | ModeStr = Mode.get_default(),
|
551
|
-
) ->
|
552
|
-
|
553
|
-
|
554
|
-
f"The slot for `{on}` isn't set in the current `{scope_name}` scope."
|
555
|
-
)
|
556
|
-
|
557
|
-
injectable = SimpleScopedInjectable(SyncCaller(when_empty), scope_name)
|
558
|
-
updater = Updater.with_basics(on, injectable, mode)
|
548
|
+
) -> SlotKey[T]:
|
549
|
+
injectable = ScopedSlotInjectable(cls, scope_name)
|
550
|
+
updater = Updater.with_basics(cls, injectable, mode)
|
559
551
|
self.update(updater)
|
560
|
-
return
|
552
|
+
return injectable.key
|
561
553
|
|
562
554
|
def inject[**P, T](
|
563
555
|
self,
|
injection/_core/scope.py
CHANGED
@@ -7,12 +7,14 @@ from collections.abc import AsyncIterator, Iterator, Mapping, MutableMapping
|
|
7
7
|
from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager
|
8
8
|
from contextvars import ContextVar
|
9
9
|
from dataclasses import dataclass, field
|
10
|
+
from enum import StrEnum
|
10
11
|
from types import EllipsisType, TracebackType
|
11
12
|
from typing import (
|
12
13
|
Any,
|
13
14
|
AsyncContextManager,
|
14
15
|
ContextManager,
|
15
16
|
Final,
|
17
|
+
Literal,
|
16
18
|
NoReturn,
|
17
19
|
Protocol,
|
18
20
|
Self,
|
@@ -21,13 +23,27 @@ from typing import (
|
|
21
23
|
)
|
22
24
|
|
23
25
|
from injection._core.common.key import new_short_key
|
26
|
+
from injection._core.slots import SlotKey
|
24
27
|
from injection.exceptions import (
|
28
|
+
InjectionError,
|
25
29
|
ScopeAlreadyDefinedError,
|
26
30
|
ScopeError,
|
27
31
|
ScopeUndefinedError,
|
28
32
|
)
|
29
33
|
|
30
34
|
|
35
|
+
class ScopeKind(StrEnum):
|
36
|
+
CONTEXTUAL = "contextual"
|
37
|
+
SHARED = "shared"
|
38
|
+
|
39
|
+
@classmethod
|
40
|
+
def get_default(cls) -> ScopeKind:
|
41
|
+
return cls.CONTEXTUAL
|
42
|
+
|
43
|
+
|
44
|
+
type ScopeKindStr = Literal["contextual", "shared"]
|
45
|
+
|
46
|
+
|
31
47
|
@runtime_checkable
|
32
48
|
class ScopeState(Protocol):
|
33
49
|
__slots__ = ()
|
@@ -109,17 +125,25 @@ __SHARED_SCOPES: Final[Mapping[str, ScopeState]] = defaultdict(
|
|
109
125
|
|
110
126
|
|
111
127
|
@asynccontextmanager
|
112
|
-
async def adefine_scope(
|
128
|
+
async def adefine_scope(
|
129
|
+
name: str,
|
130
|
+
/,
|
131
|
+
kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
|
132
|
+
) -> AsyncIterator[ScopeFacade]:
|
113
133
|
async with AsyncScope() as scope:
|
114
|
-
|
115
|
-
|
134
|
+
with _bind_scope(name, scope, kind) as facade:
|
135
|
+
yield facade
|
116
136
|
|
117
137
|
|
118
138
|
@contextmanager
|
119
|
-
def define_scope(
|
139
|
+
def define_scope(
|
140
|
+
name: str,
|
141
|
+
/,
|
142
|
+
kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
|
143
|
+
) -> Iterator[ScopeFacade]:
|
120
144
|
with SyncScope() as scope:
|
121
|
-
|
122
|
-
|
145
|
+
with _bind_scope(name, scope, kind) as facade:
|
146
|
+
yield facade
|
123
147
|
|
124
148
|
|
125
149
|
def get_active_scopes(name: str) -> tuple[Scope, ...]:
|
@@ -153,15 +177,32 @@ def get_scope(name, default=...): # type: ignore[no-untyped-def]
|
|
153
177
|
return default
|
154
178
|
|
155
179
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
is_already_defined = bool(get_active_scopes(name))
|
160
|
-
states = __SHARED_SCOPES
|
180
|
+
def in_scope_cache(key: Any, scope_name: str) -> bool:
|
181
|
+
return any(key in scope.cache for scope in get_active_scopes(scope_name))
|
182
|
+
|
161
183
|
|
162
|
-
|
163
|
-
|
164
|
-
|
184
|
+
def remove_scoped_values(key: Any, scope_name: str) -> None:
|
185
|
+
for scope in get_active_scopes(scope_name):
|
186
|
+
scope.cache.pop(key, None)
|
187
|
+
|
188
|
+
|
189
|
+
@contextmanager
|
190
|
+
def _bind_scope(
|
191
|
+
name: str,
|
192
|
+
scope: Scope,
|
193
|
+
kind: ScopeKind | ScopeKindStr,
|
194
|
+
) -> Iterator[ScopeFacade]:
|
195
|
+
match ScopeKind(kind):
|
196
|
+
case ScopeKind.CONTEXTUAL:
|
197
|
+
is_already_defined = bool(get_scope(name, default=None))
|
198
|
+
states = __CONTEXTUAL_SCOPES
|
199
|
+
|
200
|
+
case ScopeKind.SHARED:
|
201
|
+
is_already_defined = bool(get_active_scopes(name))
|
202
|
+
states = __SHARED_SCOPES
|
203
|
+
|
204
|
+
case _:
|
205
|
+
raise NotImplementedError
|
165
206
|
|
166
207
|
if is_already_defined:
|
167
208
|
raise ScopeAlreadyDefinedError(
|
@@ -169,7 +210,7 @@ def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
|
|
169
210
|
)
|
170
211
|
|
171
212
|
with states[name].bind(scope):
|
172
|
-
yield
|
213
|
+
yield _UserScope(scope)
|
173
214
|
|
174
215
|
|
175
216
|
@runtime_checkable
|
@@ -245,3 +286,34 @@ class SyncScope(BaseScope[ExitStack]):
|
|
245
286
|
|
246
287
|
def enter[T](self, context_manager: ContextManager[T]) -> T:
|
247
288
|
return self.delegate.enter_context(context_manager)
|
289
|
+
|
290
|
+
|
291
|
+
@runtime_checkable
|
292
|
+
class ScopeFacade(Protocol):
|
293
|
+
__slots__ = ()
|
294
|
+
|
295
|
+
@abstractmethod
|
296
|
+
def set_slot[T](self, key: SlotKey[T], value: T) -> Self:
|
297
|
+
raise NotImplementedError
|
298
|
+
|
299
|
+
@abstractmethod
|
300
|
+
def slot_map(self, mapping: Mapping[SlotKey[Any], Any], /) -> Self:
|
301
|
+
raise NotImplementedError
|
302
|
+
|
303
|
+
|
304
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
305
|
+
class _UserScope(ScopeFacade):
|
306
|
+
scope: Scope
|
307
|
+
|
308
|
+
def set_slot[T](self, key: SlotKey[T], value: T) -> Self:
|
309
|
+
return self.slot_map({key: value})
|
310
|
+
|
311
|
+
def slot_map(self, mapping: Mapping[SlotKey[Any], Any], /) -> Self:
|
312
|
+
cache = self.scope.cache
|
313
|
+
|
314
|
+
for slot_key in mapping:
|
315
|
+
if slot_key in cache:
|
316
|
+
raise InjectionError("Slot already set.")
|
317
|
+
|
318
|
+
cache.update(mapping)
|
319
|
+
return self
|
injection/_core/slots.py
CHANGED
@@ -1,24 +1,2 @@
|
|
1
|
-
|
2
|
-
from dataclasses import dataclass
|
3
|
-
from typing import Any, Protocol, runtime_checkable
|
4
|
-
|
5
|
-
from injection._core.injectables import ScopedInjectable
|
6
|
-
from injection.exceptions import InjectionError
|
7
|
-
|
8
|
-
|
9
|
-
@runtime_checkable
|
10
|
-
class Slot[T](Protocol):
|
1
|
+
class SlotKey[T]:
|
11
2
|
__slots__ = ()
|
12
|
-
|
13
|
-
@abstractmethod
|
14
|
-
def set(self, instance: T, /) -> None:
|
15
|
-
raise NotImplementedError
|
16
|
-
|
17
|
-
|
18
|
-
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
19
|
-
class ScopedSlot[T](Slot[T]):
|
20
|
-
injectable: ScopedInjectable[Any, T]
|
21
|
-
|
22
|
-
def set(self, instance: T, /) -> None:
|
23
|
-
if self.injectable.setdefault(instance) is not instance:
|
24
|
-
raise InjectionError("Slot already set.")
|
@@ -1,14 +1,14 @@
|
|
1
|
-
injection/__init__.py,sha256=
|
2
|
-
injection/__init__.pyi,sha256=
|
1
|
+
injection/__init__.py,sha256=7ZRUlO5EEPWO7IlbYHD-8DOX-cg4Np4nYq5fpw-U56o,1259
|
2
|
+
injection/__init__.pyi,sha256=O2W6ZWcwH58JnDmJNkJ6cdoaO0D2NE7PEVFgfJNTwB0,10717
|
3
3
|
injection/exceptions.py,sha256=v57yMujiq6H_zwwn30A8UYEZX9R9k-bY8FnsdaimPM4,1025
|
4
4
|
injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
injection/utils.py,sha256=bLIVA_3N3mTEQ3kGV4YzrWEnokHxUGqWYNKPggOOnpg,4065
|
6
6
|
injection/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
injection/_core/descriptors.py,sha256=7fSHlgAqmgR_Uta8KocBapOt1Xyj2dI7RY9ZdoStTzw,726
|
8
|
-
injection/_core/injectables.py,sha256=
|
9
|
-
injection/_core/module.py,sha256=
|
10
|
-
injection/_core/scope.py,sha256=
|
11
|
-
injection/_core/slots.py,sha256=
|
8
|
+
injection/_core/injectables.py,sha256=yeW_AHCZSsxyiN1oT_E9MLc1DcYA37XlmQGAkUc4eR8,6189
|
9
|
+
injection/_core/module.py,sha256=GNDnYbD7oXVCS_CH_w7Io-rbWZxMConoOMNy1XJKotY,31575
|
10
|
+
injection/_core/scope.py,sha256=J6O0fgaz7IstYDx1hjRPaZUNIzIS_cHc29fp-qIrUA0,8291
|
11
|
+
injection/_core/slots.py,sha256=g9TG6CbqRzCsjg01iPyfRtTTUCJnnJOwcj9mJabH0dc,37
|
12
12
|
injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
injection/_core/common/asynchronous.py,sha256=QeS2Lc4gEBFvTA_snOWfme5mTL4BFZWqZ8EzJwOdVos,1816
|
14
14
|
injection/_core/common/event.py,sha256=XjzV8gxtGlGvzZs_ykvoC60qmdpd3RN08Eiqz5QUwes,1236
|
@@ -16,10 +16,10 @@ injection/_core/common/invertible.py,sha256=gA_vw5nBjgp_w9MrDK5jMO8lhuOQWON8BbPp
|
|
16
16
|
injection/_core/common/key.py,sha256=ghkZD-Y8Moz6SEPNgMh3xgsZUjDVq-XYAmXaCu5VuCA,80
|
17
17
|
injection/_core/common/lazy.py,sha256=6xh5h0lmaNvl32V0WoX4VCTsNJ3zUJdWVqpLJ_YeIIU,1363
|
18
18
|
injection/_core/common/type.py,sha256=SCDtmBv9qFvEf5o5tTgCuwMDfuo1fgjSW0bUqA8ACis,2251
|
19
|
-
injection/
|
20
|
-
injection/
|
19
|
+
injection/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
+
injection/ext/fastapi.py,sha256=YHSs85_3m6TUVtOwUcV157b3UZJQIw_aXWAg199a-YE,594
|
21
21
|
injection/testing/__init__.py,sha256=SiImXDd0-DO1a8S5nbUQRtgDX8iaU_nHcp8DdqwtD2M,896
|
22
22
|
injection/testing/__init__.pyi,sha256=iOii0i9F5n7znltGeGQYI2KXC_if9SAogLh1h03yx-0,540
|
23
|
-
python_injection-0.
|
24
|
-
python_injection-0.
|
25
|
-
python_injection-0.
|
23
|
+
python_injection-0.17.0.dist-info/METADATA,sha256=I1L9Clj5aiursJyGnauGg8HmLfFg6ybWnnLU5FZ3ssE,3199
|
24
|
+
python_injection-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
25
|
+
python_injection-0.17.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|