engin 0.0.19__py3-none-any.whl → 0.1a1__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.
- engin/__init__.py +5 -0
- engin/_dependency.py +18 -11
- engin/_engin.py +40 -15
- engin/_shutdown.py +4 -0
- engin/_supervisor.py +141 -0
- engin/_type_utils.py +2 -2
- engin/extensions/fastapi.py +2 -2
- {engin-0.0.19.dist-info → engin-0.1a1.dist-info}/METADATA +3 -1
- {engin-0.0.19.dist-info → engin-0.1a1.dist-info}/RECORD +12 -10
- {engin-0.0.19.dist-info → engin-0.1a1.dist-info}/WHEEL +0 -0
- {engin-0.0.19.dist-info → engin-0.1a1.dist-info}/entry_points.txt +0 -0
- {engin-0.0.19.dist-info → engin-0.1a1.dist-info}/licenses/LICENSE +0 -0
engin/__init__.py
CHANGED
@@ -4,6 +4,8 @@ from engin._dependency import Entrypoint, Invoke, Provide, Supply
|
|
4
4
|
from engin._engin import Engin
|
5
5
|
from engin._lifecycle import Lifecycle
|
6
6
|
from engin._option import Option
|
7
|
+
from engin._shutdown import ShutdownSwitch
|
8
|
+
from engin._supervisor import OnException, Supervisor
|
7
9
|
from engin._type_utils import TypeId
|
8
10
|
|
9
11
|
__all__ = [
|
@@ -13,8 +15,11 @@ __all__ = [
|
|
13
15
|
"Entrypoint",
|
14
16
|
"Invoke",
|
15
17
|
"Lifecycle",
|
18
|
+
"OnException",
|
16
19
|
"Option",
|
17
20
|
"Provide",
|
21
|
+
"ShutdownSwitch",
|
22
|
+
"Supervisor",
|
18
23
|
"Supply",
|
19
24
|
"TypeId",
|
20
25
|
"invoke",
|
engin/_dependency.py
CHANGED
@@ -33,7 +33,7 @@ class Dependency(ABC, Option, Generic[P, T]):
|
|
33
33
|
def __init__(self, func: Func[P, T]) -> None:
|
34
34
|
self._func = func
|
35
35
|
self._is_async = iscoroutinefunction(func)
|
36
|
-
self._signature = inspect.signature(self._func)
|
36
|
+
self._signature = inspect.signature(self._func, eval_str=True)
|
37
37
|
self._block_name: str | None = None
|
38
38
|
|
39
39
|
source_frame = get_first_external_frame()
|
@@ -154,24 +154,24 @@ class Entrypoint(Invoke):
|
|
154
154
|
class Provide(Dependency[Any, T]):
|
155
155
|
def __init__(
|
156
156
|
self,
|
157
|
-
|
157
|
+
factory: Func[P, T],
|
158
158
|
*,
|
159
159
|
scope: str | None = None,
|
160
160
|
as_type: type | None = None,
|
161
161
|
override: bool = False,
|
162
162
|
) -> None:
|
163
163
|
"""
|
164
|
-
Provide a type via a
|
164
|
+
Provide a type via a factory function.
|
165
165
|
|
166
166
|
Args:
|
167
|
-
|
167
|
+
factory: the factory function that returns the type.
|
168
168
|
scope: (optional) associate this provider with a specific scope.
|
169
169
|
as_type: (optional) allows you to explicitly specify the provided type, e.g.
|
170
170
|
to type erase a concrete type, or to provide a mock implementation.
|
171
171
|
override: (optional) allow this provider to override other providers for the
|
172
172
|
same type from the same package.
|
173
173
|
"""
|
174
|
-
super().__init__(func=
|
174
|
+
super().__init__(func=factory)
|
175
175
|
self._scope = scope
|
176
176
|
self._override = override
|
177
177
|
self._explicit_type = as_type
|
@@ -231,9 +231,9 @@ class Provide(Dependency[Any, T]):
|
|
231
231
|
# overwriting a dependency from the same package must be explicit
|
232
232
|
if is_same_package and not self._override:
|
233
233
|
msg = (
|
234
|
-
f"
|
235
|
-
f"'{existing_provider.
|
236
|
-
"`override=True` for the overriding Provider"
|
234
|
+
f"{self} from '{self._source_frame}' is implicitly overriding "
|
235
|
+
f"{existing_provider} from '{existing_provider.source_module}', if this "
|
236
|
+
"is intentional specify `override=True` for the overriding Provider"
|
237
237
|
)
|
238
238
|
raise RuntimeError(msg)
|
239
239
|
|
@@ -243,7 +243,7 @@ class Provide(Dependency[Any, T]):
|
|
243
243
|
return hash(self.return_type_id)
|
244
244
|
|
245
245
|
def __str__(self) -> str:
|
246
|
-
return f"Provide({self.
|
246
|
+
return f"Provide(factory={self.func_name}, type={self._return_type_id})"
|
247
247
|
|
248
248
|
def _resolve_return_type(self) -> type[T]:
|
249
249
|
if self._explicit_type is not None:
|
@@ -279,7 +279,14 @@ class Supply(Provide, Generic[T]):
|
|
279
279
|
same type from the same package.
|
280
280
|
"""
|
281
281
|
self._value = value
|
282
|
-
super().__init__(
|
282
|
+
super().__init__(factory=self._get_val, as_type=as_type, override=override)
|
283
|
+
|
284
|
+
@property
|
285
|
+
def name(self) -> str:
|
286
|
+
if self._block_name:
|
287
|
+
return f"{self._block_name}.supply"
|
288
|
+
else:
|
289
|
+
return f"{self._source_frame}.supply"
|
283
290
|
|
284
291
|
def _resolve_return_type(self) -> type[T]:
|
285
292
|
if self._explicit_type is not None:
|
@@ -292,4 +299,4 @@ class Supply(Provide, Generic[T]):
|
|
292
299
|
return self._value
|
293
300
|
|
294
301
|
def __str__(self) -> str:
|
295
|
-
return f"Supply({self.return_type_id})"
|
302
|
+
return f"Supply(value={self._value}, type={self.return_type_id})"
|
engin/_engin.py
CHANGED
@@ -9,11 +9,15 @@ from itertools import chain
|
|
9
9
|
from types import FrameType
|
10
10
|
from typing import ClassVar
|
11
11
|
|
12
|
+
from anyio import CancelScope, create_task_group, get_cancelled_exc_class
|
13
|
+
|
12
14
|
from engin._assembler import AssembledDependency, Assembler
|
13
15
|
from engin._dependency import Invoke, Provide, Supply
|
14
16
|
from engin._graph import DependencyGrapher, Node
|
15
17
|
from engin._lifecycle import Lifecycle
|
16
18
|
from engin._option import Option
|
19
|
+
from engin._shutdown import ShutdownSwitch
|
20
|
+
from engin._supervisor import Supervisor
|
17
21
|
from engin._type_utils import TypeId
|
18
22
|
|
19
23
|
_OS_IS_WINDOWS = os.name == "nt"
|
@@ -38,10 +42,10 @@ class Engin:
|
|
38
42
|
1. The Engin assembles all Invocations. Only Providers that are required to satisfy
|
39
43
|
the Invoke options parameters are assembled.
|
40
44
|
2. All Invocations are run sequentially in the order they were passed in to the Engin.
|
41
|
-
3.
|
42
|
-
|
43
|
-
|
44
|
-
5.
|
45
|
+
3. Lifecycle Startup tasks registered by assembled dependencies are run sequentially.
|
46
|
+
4. The Engin waits for a stop signal, i.e. SIGINT or SIGTERM, or for something to
|
47
|
+
set the ShutdownSwitch event.
|
48
|
+
5. Lifecyce Shutdown tasks are run in the reverse order to the Startup order.
|
45
49
|
|
46
50
|
Examples:
|
47
51
|
```python
|
@@ -49,11 +53,13 @@ class Engin:
|
|
49
53
|
|
50
54
|
from httpx import AsyncClient
|
51
55
|
|
52
|
-
from engin import Engin, Invoke, Provide
|
56
|
+
from engin import Engin, Invoke, Lifecycle, Provide
|
53
57
|
|
54
58
|
|
55
|
-
def httpx_client() -> AsyncClient:
|
56
|
-
|
59
|
+
def httpx_client(lifecycle: Lifecycle) -> AsyncClient:
|
60
|
+
client = AsyncClient()
|
61
|
+
lifecycle.append(client)
|
62
|
+
return client
|
57
63
|
|
58
64
|
|
59
65
|
async def main(http_client: AsyncClient) -> None:
|
@@ -65,7 +71,7 @@ class Engin:
|
|
65
71
|
```
|
66
72
|
"""
|
67
73
|
|
68
|
-
_LIB_OPTIONS: ClassVar[list[Option]] = [Provide(Lifecycle)]
|
74
|
+
_LIB_OPTIONS: ClassVar[list[Option]] = [Provide(Lifecycle), Provide(Supervisor)]
|
69
75
|
|
70
76
|
def __init__(self, *options: Option) -> None:
|
71
77
|
"""
|
@@ -77,14 +83,16 @@ class Engin:
|
|
77
83
|
Args:
|
78
84
|
*options: an instance of Provide, Supply, Invoke, Entrypoint or a Block.
|
79
85
|
"""
|
80
|
-
self._stop_requested_event =
|
86
|
+
self._stop_requested_event = ShutdownSwitch()
|
81
87
|
self._stop_complete_event = Event()
|
82
88
|
self._exit_stack: AsyncExitStack = AsyncExitStack()
|
83
89
|
self._shutdown_task: Task | None = None
|
84
90
|
self._run_task: Task | None = None
|
91
|
+
self._assembler = Assembler([])
|
85
92
|
|
86
93
|
self._providers: dict[TypeId, Provide] = {
|
87
|
-
TypeId.from_type(
|
94
|
+
TypeId.from_type(Assembler): Supply(self._assembler),
|
95
|
+
TypeId.from_type(ShutdownSwitch): Supply(self._stop_requested_event),
|
88
96
|
}
|
89
97
|
self._multiproviders: dict[TypeId, list[Provide]] = defaultdict(list)
|
90
98
|
self._invocations: list[Invoke] = []
|
@@ -94,7 +102,9 @@ class Engin:
|
|
94
102
|
option.apply(self)
|
95
103
|
|
96
104
|
multi_providers = [p for multi in self._multiproviders.values() for p in multi]
|
97
|
-
|
105
|
+
|
106
|
+
for provider in chain(self._providers.values(), multi_providers):
|
107
|
+
self._assembler.add(provider)
|
98
108
|
|
99
109
|
@property
|
100
110
|
def assembler(self) -> Assembler:
|
@@ -105,11 +115,19 @@ class Engin:
|
|
105
115
|
Run the engin.
|
106
116
|
|
107
117
|
The engin will run until it is stopped via an external signal (i.e. SIGTERM or
|
108
|
-
SIGINT)
|
118
|
+
SIGINT), the `stop` method is called on the engin, or a lifecycle task errors.
|
109
119
|
"""
|
110
120
|
await self.start()
|
111
|
-
|
112
|
-
|
121
|
+
if self._shutdown_task:
|
122
|
+
self._shutdown_task.cancel("redundant")
|
123
|
+
async with create_task_group() as tg:
|
124
|
+
tg.start_soon(_stop_engin_on_signal, self._stop_requested_event)
|
125
|
+
try:
|
126
|
+
await self._stop_requested_event.wait()
|
127
|
+
await self._shutdown()
|
128
|
+
except get_cancelled_exc_class():
|
129
|
+
with CancelScope(shield=True):
|
130
|
+
await self._shutdown()
|
113
131
|
|
114
132
|
async def start(self) -> None:
|
115
133
|
"""
|
@@ -133,6 +151,10 @@ class Engin:
|
|
133
151
|
return
|
134
152
|
|
135
153
|
lifecycle = await self._assembler.build(Lifecycle)
|
154
|
+
supervisor = await self._assembler.build(Supervisor)
|
155
|
+
|
156
|
+
if not supervisor.empty:
|
157
|
+
lifecycle.append(supervisor)
|
136
158
|
|
137
159
|
try:
|
138
160
|
for hook in lifecycle.list():
|
@@ -178,7 +200,10 @@ class Engin:
|
|
178
200
|
await self._shutdown()
|
179
201
|
|
180
202
|
|
181
|
-
async def
|
203
|
+
async def _stop_engin_on_signal(stop_requested_event: Event) -> None:
|
204
|
+
"""
|
205
|
+
A task that waits for a stop signal (SIGINT/SIGTERM) and notifies the given event.
|
206
|
+
"""
|
182
207
|
try:
|
183
208
|
# try to gracefully handle sigint/sigterm
|
184
209
|
if not _OS_IS_WINDOWS:
|
engin/_shutdown.py
ADDED
engin/_supervisor.py
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
import inspect
|
2
|
+
import logging
|
3
|
+
import typing
|
4
|
+
from collections.abc import Awaitable, Callable
|
5
|
+
from contextlib import AsyncExitStack
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from enum import Enum
|
8
|
+
from types import TracebackType
|
9
|
+
from typing import TypeAlias
|
10
|
+
|
11
|
+
import anyio
|
12
|
+
from anyio import get_cancelled_exc_class
|
13
|
+
from exceptiongroup import BaseExceptionGroup, catch
|
14
|
+
|
15
|
+
from engin._shutdown import ShutdownSwitch
|
16
|
+
|
17
|
+
if typing.TYPE_CHECKING:
|
18
|
+
from anyio.abc import TaskGroup
|
19
|
+
|
20
|
+
LOG = logging.getLogger("engin")
|
21
|
+
|
22
|
+
TaskFactory: TypeAlias = Callable[[], Awaitable[None]]
|
23
|
+
|
24
|
+
|
25
|
+
class OnException(Enum):
|
26
|
+
SHUTDOWN = 0
|
27
|
+
"""
|
28
|
+
Cancel all other supervised tasks and shutdown the Engin.
|
29
|
+
"""
|
30
|
+
|
31
|
+
RETRY = 1
|
32
|
+
"""
|
33
|
+
Retry the task.
|
34
|
+
"""
|
35
|
+
|
36
|
+
IGNORE = 2
|
37
|
+
"""
|
38
|
+
The task will be not be retried and the engin will not be stopped, other tasks will
|
39
|
+
continue to run.
|
40
|
+
"""
|
41
|
+
|
42
|
+
|
43
|
+
@dataclass(kw_only=True, slots=True, eq=False)
|
44
|
+
class _SupervisorTask:
|
45
|
+
"""
|
46
|
+
Attributes:
|
47
|
+
- factory: a coroutine function that can create the task.
|
48
|
+
- on_exception: determines the behaviour when task raises an exception.
|
49
|
+
- complete: will be set to true if task stops for any reason except cancellation.
|
50
|
+
- last_exception: the last exception raised by the task.
|
51
|
+
"""
|
52
|
+
|
53
|
+
factory: TaskFactory
|
54
|
+
on_exception: OnException
|
55
|
+
complete: bool = False
|
56
|
+
last_exception: Exception | None = None
|
57
|
+
|
58
|
+
async def __call__(self) -> None:
|
59
|
+
# loop to allow for restarting erroring tasks
|
60
|
+
while True:
|
61
|
+
try:
|
62
|
+
await self.factory()
|
63
|
+
self.complete = True
|
64
|
+
return
|
65
|
+
except get_cancelled_exc_class():
|
66
|
+
LOG.info(f"{self.name} cancelled")
|
67
|
+
raise
|
68
|
+
except Exception as err:
|
69
|
+
LOG.error(f"Supervisor: {self.name} raised {type(err).__name__}", exc_info=err)
|
70
|
+
self.last_exception = err
|
71
|
+
|
72
|
+
if self.on_exception == OnException.IGNORE:
|
73
|
+
self.complete = True
|
74
|
+
return
|
75
|
+
|
76
|
+
if self.on_exception == OnException.RETRY:
|
77
|
+
continue
|
78
|
+
|
79
|
+
if self.on_exception == OnException.SHUTDOWN:
|
80
|
+
self.complete = True
|
81
|
+
raise
|
82
|
+
|
83
|
+
@property
|
84
|
+
def name(self) -> str:
|
85
|
+
factory = self.factory
|
86
|
+
if inspect.ismethod(factory):
|
87
|
+
return f"{factory.__self__.__class__.__name__}.{factory.__func__.__name__}"
|
88
|
+
if inspect.isclass(factory):
|
89
|
+
return type(factory).__name__
|
90
|
+
if inspect.isfunction(factory):
|
91
|
+
return factory.__name__
|
92
|
+
return str(factory)
|
93
|
+
|
94
|
+
|
95
|
+
class Supervisor:
|
96
|
+
def __init__(self, shutdown: ShutdownSwitch) -> None:
|
97
|
+
self._tasks: list[_SupervisorTask] = []
|
98
|
+
self._shutdown = shutdown
|
99
|
+
self._is_complete: bool = False
|
100
|
+
|
101
|
+
self._exit_stack: AsyncExitStack | None = None
|
102
|
+
self._task_group: TaskGroup | None = None
|
103
|
+
|
104
|
+
def supervise(
|
105
|
+
self, func: TaskFactory, *, on_exception: OnException = OnException.SHUTDOWN
|
106
|
+
) -> None:
|
107
|
+
self._tasks.append(_SupervisorTask(factory=func, on_exception=on_exception))
|
108
|
+
|
109
|
+
@property
|
110
|
+
def empty(self) -> bool:
|
111
|
+
return not self._tasks
|
112
|
+
|
113
|
+
async def __aenter__(self) -> None:
|
114
|
+
if not self._tasks:
|
115
|
+
return
|
116
|
+
|
117
|
+
def _handler(_: BaseExceptionGroup) -> None:
|
118
|
+
self._shutdown.set()
|
119
|
+
|
120
|
+
self._exit_stack = AsyncExitStack()
|
121
|
+
await self._exit_stack.__aenter__()
|
122
|
+
self._exit_stack.enter_context(catch({Exception: _handler}))
|
123
|
+
self._task_group = await self._exit_stack.enter_async_context(
|
124
|
+
anyio.create_task_group()
|
125
|
+
)
|
126
|
+
|
127
|
+
for task in self._tasks:
|
128
|
+
self._task_group.start_soon(task, name=task.name)
|
129
|
+
|
130
|
+
async def __aexit__(
|
131
|
+
self,
|
132
|
+
exc_type: type[BaseException] | None,
|
133
|
+
exc_value: BaseException | None,
|
134
|
+
traceback: TracebackType | None,
|
135
|
+
/,
|
136
|
+
) -> None:
|
137
|
+
if not self._tasks:
|
138
|
+
return
|
139
|
+
|
140
|
+
if self._exit_stack:
|
141
|
+
await self._exit_stack.__aexit__(exc_type, exc_value, traceback)
|
engin/_type_utils.py
CHANGED
@@ -33,8 +33,8 @@ class TypeId:
|
|
33
33
|
return TypeId(type=type_, multi=False)
|
34
34
|
|
35
35
|
def __str__(self) -> str:
|
36
|
-
module = self.type
|
37
|
-
out = f"{module}." if module not in _implict_modules else ""
|
36
|
+
module = getattr(self.type, "__module__", None)
|
37
|
+
out = f"{module}." if module and module not in _implict_modules else ""
|
38
38
|
out += _args_to_str(self.type)
|
39
39
|
if self.multi:
|
40
40
|
out += "[]"
|
engin/extensions/fastapi.py
CHANGED
@@ -24,12 +24,12 @@ except ImportError as err:
|
|
24
24
|
__all__ = ["APIRouteDependency", "FastAPIEngin", "Inject"]
|
25
25
|
|
26
26
|
|
27
|
-
def _attach_assembler(app: FastAPI,
|
27
|
+
def _attach_assembler(app: FastAPI, assembler: Assembler) -> None:
|
28
28
|
"""
|
29
29
|
An invocation that attaches the Engin's Assembler to the FastAPI application, enabling
|
30
30
|
the Inject marker.
|
31
31
|
"""
|
32
|
-
app.state.assembler =
|
32
|
+
app.state.assembler = assembler
|
33
33
|
|
34
34
|
|
35
35
|
class FastAPIEngin(ASGIEngin):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: engin
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.1a1
|
4
4
|
Summary: An async-first modular application framework
|
5
5
|
Project-URL: Homepage, https://github.com/invokermain/engin
|
6
6
|
Project-URL: Documentation, https://engin.readthedocs.io/en/latest/
|
@@ -10,6 +10,8 @@ License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
11
11
|
Keywords: Application Framework,Dependency Injection
|
12
12
|
Requires-Python: >=3.10
|
13
|
+
Requires-Dist: anyio>=4
|
14
|
+
Requires-Dist: exceptiongroup>=1
|
13
15
|
Provides-Extra: cli
|
14
16
|
Requires-Dist: typer>=0.15; extra == 'cli'
|
15
17
|
Description-Content-Type: text/markdown
|
@@ -1,13 +1,15 @@
|
|
1
|
-
engin/__init__.py,sha256=
|
1
|
+
engin/__init__.py,sha256=v5OWwQoxTtqh2sB2E5iSMg3gATJoXKOuJLq28aZX6C8,642
|
2
2
|
engin/_assembler.py,sha256=-ENSrXPMWacionIYrTSQO7th9DDBOPyAT8ybPbBRtQw,11318
|
3
3
|
engin/_block.py,sha256=IacP4PoJKRhSQCbQSdoyCtmu362a4vj6qoUQKyaJwzI,3062
|
4
|
-
engin/_dependency.py,sha256=
|
5
|
-
engin/_engin.py,sha256=
|
4
|
+
engin/_dependency.py,sha256=xINk3sudxzsTmkUkNAKQwzBc0G0DfhpnrZli4z3ALBY,9459
|
5
|
+
engin/_engin.py,sha256=UGS-dIMGcdhRWvsQBuwIH6I7XFxYMlgSZeuvl3ur3Eo,8417
|
6
6
|
engin/_graph.py,sha256=y1g7Lm_Zy5GPEgRsggCKV5DDaDzcwUl8v3IZCK8jyGI,1631
|
7
7
|
engin/_introspect.py,sha256=VdREX6Lhhga5SnEP9G7mjHkgJR4mpqk_SMnmL2zTcqY,966
|
8
8
|
engin/_lifecycle.py,sha256=cSWe3euZkmpxmUPFvph2lsTtvuZbxttEfBL-RnOI7lo,5325
|
9
9
|
engin/_option.py,sha256=nZcdrehp1QwgxMUoIpsM0PJuu1q1pbXzhcVsetbsHpc,223
|
10
|
-
engin/
|
10
|
+
engin/_shutdown.py,sha256=G85Cz-wG06ZxQz6QVPcpIcNaGY44aZp6SL8H9J4YvfU,61
|
11
|
+
engin/_supervisor.py,sha256=wKfGPjz8q1SH8ZP84USO-SxY3uTEXWy2NkTlhZQGRDE,4131
|
12
|
+
engin/_type_utils.py,sha256=H3Tl-kJr2wY2RhaTXP9GrMqa2RsXMijHbjHKe1AxGmc,2276
|
11
13
|
engin/exceptions.py,sha256=-VPwPReZb9YEIkrWMR9TW2K5HEwmHHgEO7QWH6wfV8c,1946
|
12
14
|
engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
15
|
engin/_cli/__init__.py,sha256=koD5WTkZXb8QQIiVU5bJiSR1wwPGb5rv2iwd-v-BA7A,564
|
@@ -17,9 +19,9 @@ engin/_cli/_graph.py,sha256=HMC91nWvTOr6_czPBNx1RU55Ib3qesJRCmbnL2DsdDk,4659
|
|
17
19
|
engin/_cli/_inspect.py,sha256=0jm25d4wcbXVNJkyaeECSKY-irsxd-EIYBH1GDW_Yjc,3163
|
18
20
|
engin/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
21
|
engin/extensions/asgi.py,sha256=d5Z6gtMVWDZdAlvrTaMt987sKyiq__A0X4gJQ7IETmA,3247
|
20
|
-
engin/extensions/fastapi.py,sha256=
|
21
|
-
engin-0.
|
22
|
-
engin-0.
|
23
|
-
engin-0.
|
24
|
-
engin-0.
|
25
|
-
engin-0.
|
22
|
+
engin/extensions/fastapi.py,sha256=7N6i-eZUEZRPo7kcvjS7kbRSY5QAPyKJXSeongSQ-OA,6371
|
23
|
+
engin-0.1a1.dist-info/METADATA,sha256=f5atVNZHxnofJLnnf2upFC_MYjAJv3lVkQIxe62z1TA,2410
|
24
|
+
engin-0.1a1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
25
|
+
engin-0.1a1.dist-info/entry_points.txt,sha256=sW247zZUMxm0b5UKYvPuqQQljYDtU-j2zK3cu7gHwM0,41
|
26
|
+
engin-0.1a1.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
|
27
|
+
engin-0.1a1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|