engin 0.1a1__py3-none-any.whl → 0.1.0b2__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 +0 -2
- engin/_engin.py +89 -67
- engin/_supervisor.py +9 -27
- engin-0.1.0b2.dist-info/METADATA +108 -0
- {engin-0.1a1.dist-info → engin-0.1.0b2.dist-info}/RECORD +8 -9
- engin/_shutdown.py +0 -4
- engin-0.1a1.dist-info/METADATA +0 -73
- {engin-0.1a1.dist-info → engin-0.1.0b2.dist-info}/WHEEL +0 -0
- {engin-0.1a1.dist-info → engin-0.1.0b2.dist-info}/entry_points.txt +0 -0
- {engin-0.1a1.dist-info → engin-0.1.0b2.dist-info}/licenses/LICENSE +0 -0
engin/__init__.py
CHANGED
@@ -4,7 +4,6 @@ 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
7
|
from engin._supervisor import OnException, Supervisor
|
9
8
|
from engin._type_utils import TypeId
|
10
9
|
|
@@ -18,7 +17,6 @@ __all__ = [
|
|
18
17
|
"OnException",
|
19
18
|
"Option",
|
20
19
|
"Provide",
|
21
|
-
"ShutdownSwitch",
|
22
20
|
"Supervisor",
|
23
21
|
"Supply",
|
24
22
|
"TypeId",
|
engin/_engin.py
CHANGED
@@ -2,28 +2,46 @@ import asyncio
|
|
2
2
|
import logging
|
3
3
|
import os
|
4
4
|
import signal
|
5
|
-
from asyncio import Event
|
5
|
+
from asyncio import Event
|
6
6
|
from collections import defaultdict
|
7
7
|
from contextlib import AsyncExitStack
|
8
|
+
from enum import Enum
|
8
9
|
from itertools import chain
|
9
10
|
from types import FrameType
|
10
11
|
from typing import ClassVar
|
11
12
|
|
12
|
-
from anyio import
|
13
|
+
from anyio import create_task_group, get_cancelled_exc_class
|
13
14
|
|
14
15
|
from engin._assembler import AssembledDependency, Assembler
|
15
16
|
from engin._dependency import Invoke, Provide, Supply
|
16
17
|
from engin._graph import DependencyGrapher, Node
|
17
18
|
from engin._lifecycle import Lifecycle
|
18
19
|
from engin._option import Option
|
19
|
-
from engin._shutdown import ShutdownSwitch
|
20
20
|
from engin._supervisor import Supervisor
|
21
21
|
from engin._type_utils import TypeId
|
22
|
+
from engin.exceptions import EnginError
|
22
23
|
|
23
24
|
_OS_IS_WINDOWS = os.name == "nt"
|
24
25
|
LOG = logging.getLogger("engin")
|
25
26
|
|
26
27
|
|
28
|
+
class _EnginState(Enum):
|
29
|
+
IDLE = 0
|
30
|
+
"""
|
31
|
+
Engin is not yet started.
|
32
|
+
"""
|
33
|
+
|
34
|
+
RUNNING = 1
|
35
|
+
"""
|
36
|
+
Engin is currently running.
|
37
|
+
"""
|
38
|
+
|
39
|
+
SHUTDOWN = 2
|
40
|
+
"""
|
41
|
+
Engin has performed shutdown
|
42
|
+
"""
|
43
|
+
|
44
|
+
|
27
45
|
class Engin:
|
28
46
|
"""
|
29
47
|
The Engin is a modular application defined by a collection of options.
|
@@ -83,16 +101,16 @@ class Engin:
|
|
83
101
|
Args:
|
84
102
|
*options: an instance of Provide, Supply, Invoke, Entrypoint or a Block.
|
85
103
|
"""
|
86
|
-
self.
|
104
|
+
self._state = _EnginState.IDLE
|
105
|
+
self._start_complete_event = Event()
|
106
|
+
self._stop_requested_event = Event()
|
87
107
|
self._stop_complete_event = Event()
|
88
|
-
self._exit_stack
|
89
|
-
self._shutdown_task: Task | None = None
|
90
|
-
self._run_task: Task | None = None
|
108
|
+
self._exit_stack = AsyncExitStack()
|
91
109
|
self._assembler = Assembler([])
|
110
|
+
self._async_context_run_task: asyncio.Task | None = None
|
92
111
|
|
93
112
|
self._providers: dict[TypeId, Provide] = {
|
94
113
|
TypeId.from_type(Assembler): Supply(self._assembler),
|
95
|
-
TypeId.from_type(ShutdownSwitch): Supply(self._stop_requested_event),
|
96
114
|
}
|
97
115
|
self._multiproviders: dict[TypeId, list[Provide]] = defaultdict(list)
|
98
116
|
self._invocations: list[Invoke] = []
|
@@ -117,26 +135,9 @@ class Engin:
|
|
117
135
|
The engin will run until it is stopped via an external signal (i.e. SIGTERM or
|
118
136
|
SIGINT), the `stop` method is called on the engin, or a lifecycle task errors.
|
119
137
|
"""
|
120
|
-
|
121
|
-
|
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()
|
131
|
-
|
132
|
-
async def start(self) -> None:
|
133
|
-
"""
|
134
|
-
Start the engin.
|
138
|
+
if self._state != _EnginState.IDLE:
|
139
|
+
raise EnginError("Engin is not idle, unable to start")
|
135
140
|
|
136
|
-
This is an alternative to calling `run`. This method waits for the startup
|
137
|
-
lifecycle to complete and then returns. The caller is then responsible for
|
138
|
-
calling `stop`.
|
139
|
-
"""
|
140
141
|
LOG.info("starting engin")
|
141
142
|
assembled_invocations: list[AssembledDependency] = [
|
142
143
|
await self._assembler.assemble(invocation) for invocation in self._invocations
|
@@ -151,10 +152,6 @@ class Engin:
|
|
151
152
|
return
|
152
153
|
|
153
154
|
lifecycle = await self._assembler.build(Lifecycle)
|
154
|
-
supervisor = await self._assembler.build(Supervisor)
|
155
|
-
|
156
|
-
if not supervisor.empty:
|
157
|
-
lifecycle.append(supervisor)
|
158
155
|
|
159
156
|
try:
|
160
157
|
for hook in lifecycle.list():
|
@@ -168,9 +165,30 @@ class Engin:
|
|
168
165
|
await self._shutdown()
|
169
166
|
return
|
170
167
|
|
168
|
+
supervisor = await self._assembler.build(Supervisor)
|
169
|
+
|
171
170
|
LOG.info("startup complete")
|
171
|
+
self._state = _EnginState.RUNNING
|
172
|
+
self._start_complete_event.set()
|
173
|
+
|
174
|
+
async with create_task_group() as tg:
|
175
|
+
tg.start_soon(_stop_engin_on_signal, self._stop_requested_event)
|
176
|
+
|
177
|
+
try:
|
178
|
+
async with supervisor:
|
179
|
+
await self._stop_requested_event.wait()
|
180
|
+
except get_cancelled_exc_class():
|
181
|
+
pass
|
182
|
+
tg.cancel_scope.cancel()
|
183
|
+
await self._shutdown()
|
172
184
|
|
173
|
-
|
185
|
+
async def start(self) -> None:
|
186
|
+
"""
|
187
|
+
Starts the engin in the background. This method will wait until the engin is fully
|
188
|
+
started to return so it is safe to use immediately after.
|
189
|
+
"""
|
190
|
+
self._async_context_run_task = asyncio.create_task(self.run())
|
191
|
+
await self._start_complete_event.wait()
|
174
192
|
|
175
193
|
async def stop(self) -> None:
|
176
194
|
"""
|
@@ -181,55 +199,59 @@ class Engin:
|
|
181
199
|
started.
|
182
200
|
"""
|
183
201
|
self._stop_requested_event.set()
|
184
|
-
if self._shutdown_task is None:
|
185
|
-
return
|
186
202
|
await self._stop_complete_event.wait()
|
187
203
|
|
188
204
|
def graph(self) -> list[Node]:
|
205
|
+
"""
|
206
|
+
Creates a graph representation of the engin's dependencies which can be used for
|
207
|
+
introspection or visualisations.
|
208
|
+
|
209
|
+
Returns: a list of Node objects.
|
210
|
+
"""
|
189
211
|
grapher = DependencyGrapher({**self._providers, **self._multiproviders})
|
190
212
|
return grapher.resolve(self._invocations)
|
191
213
|
|
214
|
+
def is_running(self) -> bool:
|
215
|
+
return self._state == _EnginState.RUNNING
|
216
|
+
|
217
|
+
def is_stopped(self) -> bool:
|
218
|
+
return self._state == _EnginState.SHUTDOWN
|
219
|
+
|
192
220
|
async def _shutdown(self) -> None:
|
193
221
|
LOG.info("stopping engin")
|
194
222
|
await self._exit_stack.aclose()
|
195
223
|
self._stop_complete_event.set()
|
196
224
|
LOG.info("shutdown complete")
|
197
|
-
|
198
|
-
async def _shutdown_when_stopped(self) -> None:
|
199
|
-
await self._stop_requested_event.wait()
|
200
|
-
await self._shutdown()
|
225
|
+
self._state = _EnginState.SHUTDOWN
|
201
226
|
|
202
227
|
|
203
228
|
async def _stop_engin_on_signal(stop_requested_event: Event) -> None:
|
204
229
|
"""
|
205
230
|
A task that waits for a stop signal (SIGINT/SIGTERM) and notifies the given event.
|
206
231
|
"""
|
207
|
-
try
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
stop_requested_event.set()
|
234
|
-
except asyncio.CancelledError:
|
235
|
-
pass
|
232
|
+
# try to gracefully handle sigint/sigterm
|
233
|
+
if not _OS_IS_WINDOWS:
|
234
|
+
loop = asyncio.get_running_loop()
|
235
|
+
for signame in (signal.SIGINT, signal.SIGTERM):
|
236
|
+
loop.add_signal_handler(signame, stop_requested_event.set)
|
237
|
+
|
238
|
+
await stop_requested_event.wait()
|
239
|
+
else:
|
240
|
+
should_stop = False
|
241
|
+
|
242
|
+
# windows does not support signal_handlers, so this is the workaround
|
243
|
+
def ctrlc_handler(sig: int, frame: FrameType | None) -> None:
|
244
|
+
nonlocal should_stop
|
245
|
+
if should_stop:
|
246
|
+
raise KeyboardInterrupt("Forced keyboard interrupt")
|
247
|
+
should_stop = True
|
248
|
+
|
249
|
+
signal.signal(signal.SIGINT, ctrlc_handler)
|
250
|
+
|
251
|
+
while not should_stop:
|
252
|
+
# In case engin is stopped via external `stop` call.
|
253
|
+
if stop_requested_event.is_set():
|
254
|
+
return
|
255
|
+
await asyncio.sleep(0.1)
|
256
|
+
|
257
|
+
stop_requested_event.set()
|
engin/_supervisor.py
CHANGED
@@ -2,17 +2,13 @@ import inspect
|
|
2
2
|
import logging
|
3
3
|
import typing
|
4
4
|
from collections.abc import Awaitable, Callable
|
5
|
-
from contextlib import AsyncExitStack
|
6
5
|
from dataclasses import dataclass
|
7
6
|
from enum import Enum
|
8
7
|
from types import TracebackType
|
9
|
-
from typing import TypeAlias
|
8
|
+
from typing import TypeAlias, assert_never
|
10
9
|
|
11
10
|
import anyio
|
12
11
|
from anyio import get_cancelled_exc_class
|
13
|
-
from exceptiongroup import BaseExceptionGroup, catch
|
14
|
-
|
15
|
-
from engin._shutdown import ShutdownSwitch
|
16
12
|
|
17
13
|
if typing.TYPE_CHECKING:
|
18
14
|
from anyio.abc import TaskGroup
|
@@ -72,13 +68,12 @@ class _SupervisorTask:
|
|
72
68
|
if self.on_exception == OnException.IGNORE:
|
73
69
|
self.complete = True
|
74
70
|
return
|
75
|
-
|
76
71
|
if self.on_exception == OnException.RETRY:
|
77
72
|
continue
|
78
|
-
|
79
73
|
if self.on_exception == OnException.SHUTDOWN:
|
80
74
|
self.complete = True
|
81
|
-
raise
|
75
|
+
raise get_cancelled_exc_class() from None
|
76
|
+
assert_never(self.on_exception)
|
82
77
|
|
83
78
|
@property
|
84
79
|
def name(self) -> str:
|
@@ -93,12 +88,8 @@ class _SupervisorTask:
|
|
93
88
|
|
94
89
|
|
95
90
|
class Supervisor:
|
96
|
-
def __init__(self
|
91
|
+
def __init__(self) -> None:
|
97
92
|
self._tasks: list[_SupervisorTask] = []
|
98
|
-
self._shutdown = shutdown
|
99
|
-
self._is_complete: bool = False
|
100
|
-
|
101
|
-
self._exit_stack: AsyncExitStack | None = None
|
102
93
|
self._task_group: TaskGroup | None = None
|
103
94
|
|
104
95
|
def supervise(
|
@@ -114,15 +105,7 @@ class Supervisor:
|
|
114
105
|
if not self._tasks:
|
115
106
|
return
|
116
107
|
|
117
|
-
|
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
|
-
)
|
108
|
+
self._task_group = await anyio.create_task_group().__aenter__()
|
126
109
|
|
127
110
|
for task in self._tasks:
|
128
111
|
self._task_group.start_soon(task, name=task.name)
|
@@ -134,8 +117,7 @@ class Supervisor:
|
|
134
117
|
traceback: TracebackType | None,
|
135
118
|
/,
|
136
119
|
) -> None:
|
137
|
-
if
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
await self._exit_stack.__aexit__(exc_type, exc_value, traceback)
|
120
|
+
if self._task_group:
|
121
|
+
if not self._task_group.cancel_scope.cancel_called:
|
122
|
+
self._task_group.cancel_scope.cancel()
|
123
|
+
await self._task_group.__aexit__(exc_type, exc_value, traceback)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: engin
|
3
|
+
Version: 0.1.0b2
|
4
|
+
Summary: An async-first modular application framework
|
5
|
+
Project-URL: Homepage, https://github.com/invokermain/engin
|
6
|
+
Project-URL: Documentation, https://engin.readthedocs.io/en/latest/
|
7
|
+
Project-URL: Repository, https://github.com/invokermain/engin.git
|
8
|
+
Project-URL: Changelog, https://github.com/invokermain/engin/blob/main/CHANGELOG.md
|
9
|
+
License-Expression: MIT
|
10
|
+
License-File: LICENSE
|
11
|
+
Keywords: Application Framework,Dependency Injection
|
12
|
+
Requires-Python: >=3.10
|
13
|
+
Requires-Dist: anyio>=4
|
14
|
+
Requires-Dist: exceptiongroup>=1
|
15
|
+
Provides-Extra: cli
|
16
|
+
Requires-Dist: typer>=0.15; extra == 'cli'
|
17
|
+
Description-Content-Type: text/markdown
|
18
|
+
|
19
|
+
# Engin 🏎️
|
20
|
+
|
21
|
+
[](https://codecov.io/gh/invokermain/engin)
|
22
|
+
|
23
|
+
---
|
24
|
+
|
25
|
+
**Documentation**: [https://engin.readthedocs.io/](https://engin.readthedocs.io/)
|
26
|
+
|
27
|
+
**Source Code**: [https://github.com/invokermain/engin](https://github.com/invokermain/engin)
|
28
|
+
|
29
|
+
---
|
30
|
+
|
31
|
+
Engin is a lightweight application framework powered by dependency injection, it helps
|
32
|
+
you build and maintain large monoliths and many microservices.
|
33
|
+
|
34
|
+
|
35
|
+
## Features
|
36
|
+
|
37
|
+
The Engin framework includes:
|
38
|
+
|
39
|
+
- A fully-featured dependency injection system.
|
40
|
+
- A robust application runtime with lifecycle hooks and supervised background tasks.
|
41
|
+
- Zero boiler-plate code reuse across multiple applications.
|
42
|
+
- Integrations for other frameworks such as FastAPI.
|
43
|
+
- Full async support.
|
44
|
+
- CLI commands to aid local development.
|
45
|
+
|
46
|
+
|
47
|
+
## Installation
|
48
|
+
|
49
|
+
Engin is available on PyPI, install using your favourite dependency manager:
|
50
|
+
|
51
|
+
- `pip install engin`
|
52
|
+
- `poetry add engin`
|
53
|
+
- `uv add engin`
|
54
|
+
|
55
|
+
## Example
|
56
|
+
|
57
|
+
A small example which shows some of the runtime features of Engin. This application
|
58
|
+
makes a http request and then performs a shutdown.
|
59
|
+
|
60
|
+
```python
|
61
|
+
import asyncio
|
62
|
+
from httpx import AsyncClient
|
63
|
+
from engin import Engin, Invoke, Lifecycle, Provide, Supervisor
|
64
|
+
|
65
|
+
|
66
|
+
def httpx_client_factory(lifecycle: Lifecycle) -> AsyncClient:
|
67
|
+
# create our http client
|
68
|
+
client = AsyncClient()
|
69
|
+
# this will open and close the AsyncClient as part of the application's lifecycle
|
70
|
+
lifecycle.append(client)
|
71
|
+
return client
|
72
|
+
|
73
|
+
|
74
|
+
async def main(
|
75
|
+
httpx_client: AsyncClient,
|
76
|
+
supervisor: Supervisor,
|
77
|
+
) -> None:
|
78
|
+
async def http_request():
|
79
|
+
await httpx_client.get("https://httpbin.org/get")
|
80
|
+
# one we've made the http request shutdown the application
|
81
|
+
raise asyncio.CancelledError("Forcing shutdown")
|
82
|
+
|
83
|
+
# supervise the http request as part of the application's lifecycle
|
84
|
+
supervisor.supervise(http_request)
|
85
|
+
|
86
|
+
# define our modular application
|
87
|
+
engin = Engin(Provide(httpx_client_factory), Invoke(main))
|
88
|
+
|
89
|
+
# run it!
|
90
|
+
asyncio.run(engin.run())
|
91
|
+
```
|
92
|
+
|
93
|
+
With logs enabled this will output:
|
94
|
+
|
95
|
+
```shell
|
96
|
+
INFO:engin:starting engin
|
97
|
+
INFO:engin:startup complete
|
98
|
+
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
99
|
+
INFO:engin:stopping engin
|
100
|
+
INFO:engin:shutdown complete
|
101
|
+
```
|
102
|
+
|
103
|
+
## Inspiration
|
104
|
+
|
105
|
+
Engin is heavily inspired by [Uber's Fx framework for Go](https://github.com/uber-go/fx)
|
106
|
+
and the [Injector framework for Python](https://github.com/python-injector/injector).
|
107
|
+
|
108
|
+
They are both great projects, check them out.
|
@@ -1,14 +1,13 @@
|
|
1
|
-
engin/__init__.py,sha256=
|
1
|
+
engin/__init__.py,sha256=O0vS570kZFBq7Kwy4FgeJFIhfo4aIg5mv_Z_9vAQRio,577
|
2
2
|
engin/_assembler.py,sha256=-ENSrXPMWacionIYrTSQO7th9DDBOPyAT8ybPbBRtQw,11318
|
3
3
|
engin/_block.py,sha256=IacP4PoJKRhSQCbQSdoyCtmu362a4vj6qoUQKyaJwzI,3062
|
4
4
|
engin/_dependency.py,sha256=xINk3sudxzsTmkUkNAKQwzBc0G0DfhpnrZli4z3ALBY,9459
|
5
|
-
engin/_engin.py,sha256=
|
5
|
+
engin/_engin.py,sha256=BiibP76Pfq1Wpbu79u0anqqeXbrT2GIEzYMBfgPYoKo,8661
|
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/
|
11
|
-
engin/_supervisor.py,sha256=wKfGPjz8q1SH8ZP84USO-SxY3uTEXWy2NkTlhZQGRDE,4131
|
10
|
+
engin/_supervisor.py,sha256=37h036bPe7ew88WjpIOmhZwCvOdLVcvalyJgbWZr1vU,3716
|
12
11
|
engin/_type_utils.py,sha256=H3Tl-kJr2wY2RhaTXP9GrMqa2RsXMijHbjHKe1AxGmc,2276
|
13
12
|
engin/exceptions.py,sha256=-VPwPReZb9YEIkrWMR9TW2K5HEwmHHgEO7QWH6wfV8c,1946
|
14
13
|
engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -20,8 +19,8 @@ engin/_cli/_inspect.py,sha256=0jm25d4wcbXVNJkyaeECSKY-irsxd-EIYBH1GDW_Yjc,3163
|
|
20
19
|
engin/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
20
|
engin/extensions/asgi.py,sha256=d5Z6gtMVWDZdAlvrTaMt987sKyiq__A0X4gJQ7IETmA,3247
|
22
21
|
engin/extensions/fastapi.py,sha256=7N6i-eZUEZRPo7kcvjS7kbRSY5QAPyKJXSeongSQ-OA,6371
|
23
|
-
engin-0.
|
24
|
-
engin-0.
|
25
|
-
engin-0.
|
26
|
-
engin-0.
|
27
|
-
engin-0.
|
22
|
+
engin-0.1.0b2.dist-info/METADATA,sha256=2_LESFF10wAgmJKpPg0lBFd1Ggf_iZKXOjzIbiK9Khk,3201
|
23
|
+
engin-0.1.0b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
24
|
+
engin-0.1.0b2.dist-info/entry_points.txt,sha256=sW247zZUMxm0b5UKYvPuqQQljYDtU-j2zK3cu7gHwM0,41
|
25
|
+
engin-0.1.0b2.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
|
26
|
+
engin-0.1.0b2.dist-info/RECORD,,
|
engin/_shutdown.py
DELETED
engin-0.1a1.dist-info/METADATA
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: engin
|
3
|
-
Version: 0.1a1
|
4
|
-
Summary: An async-first modular application framework
|
5
|
-
Project-URL: Homepage, https://github.com/invokermain/engin
|
6
|
-
Project-URL: Documentation, https://engin.readthedocs.io/en/latest/
|
7
|
-
Project-URL: Repository, https://github.com/invokermain/engin.git
|
8
|
-
Project-URL: Changelog, https://github.com/invokermain/engin/blob/main/CHANGELOG.md
|
9
|
-
License-Expression: MIT
|
10
|
-
License-File: LICENSE
|
11
|
-
Keywords: Application Framework,Dependency Injection
|
12
|
-
Requires-Python: >=3.10
|
13
|
-
Requires-Dist: anyio>=4
|
14
|
-
Requires-Dist: exceptiongroup>=1
|
15
|
-
Provides-Extra: cli
|
16
|
-
Requires-Dist: typer>=0.15; extra == 'cli'
|
17
|
-
Description-Content-Type: text/markdown
|
18
|
-
|
19
|
-
[](https://codecov.io/gh/invokermain/engin)
|
20
|
-
|
21
|
-
# Engin 🏎️
|
22
|
-
|
23
|
-
Engin is a zero-dependency application framework for modern Python.
|
24
|
-
|
25
|
-
**Documentation**: https://engin.readthedocs.io/
|
26
|
-
|
27
|
-
## Features ✨
|
28
|
-
|
29
|
-
- **Dependency Injection** - Engin includes a fully-featured Dependency Injection system,
|
30
|
-
powered by type hints.
|
31
|
-
- **Lifecycle Management** - Engin provides a simple & portable approach for attaching
|
32
|
-
startup and shutdown tasks to the application's lifecycle.
|
33
|
-
- **Code Reuse** - Engin's modular components, called Blocks, work great as distributed
|
34
|
-
packages allowing zero boiler-plate code reuse across multiple applications. Perfect for
|
35
|
-
maintaining many services across your organisation.
|
36
|
-
- **Ecosystem Compatability** - Engin ships with integrations for popular frameworks that
|
37
|
-
provide their own Dependency Injection, for example FastAPI, allowing you to integrate
|
38
|
-
Engin into existing code bases incrementally.
|
39
|
-
- **Async Native**: Engin is an async framework, meaning first class support for async
|
40
|
-
dependencies. However Engin will happily run synchronous code as well.
|
41
|
-
|
42
|
-
## Installation
|
43
|
-
|
44
|
-
Engin is available on PyPI, install using your favourite dependency manager:
|
45
|
-
|
46
|
-
- **pip**:`pip install engin`
|
47
|
-
- **poetry**: `poetry add engin`
|
48
|
-
- **uv**: `uv add engin`
|
49
|
-
|
50
|
-
## Getting Started
|
51
|
-
|
52
|
-
A minimal example:
|
53
|
-
|
54
|
-
```python
|
55
|
-
import asyncio
|
56
|
-
|
57
|
-
from httpx import AsyncClient
|
58
|
-
|
59
|
-
from engin import Engin, Invoke, Provide
|
60
|
-
|
61
|
-
|
62
|
-
def httpx_client() -> AsyncClient:
|
63
|
-
return AsyncClient()
|
64
|
-
|
65
|
-
|
66
|
-
async def main(http_client: AsyncClient) -> None:
|
67
|
-
print(await http_client.get("https://httpbin.org/get"))
|
68
|
-
|
69
|
-
engin = Engin(Provide(httpx_client), Invoke(main))
|
70
|
-
|
71
|
-
asyncio.run(engin.run())
|
72
|
-
```
|
73
|
-
|
File without changes
|
File without changes
|
File without changes
|