engin 0.1.0rc1__tar.gz → 0.1.0rc3__tar.gz
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-0.1.0rc1 → engin-0.1.0rc3}/.gitignore +2 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/CHANGELOG.md +6 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/PKG-INFO +28 -15
- {engin-0.1.0rc1 → engin-0.1.0rc3}/README.md +27 -14
- engin-0.1.0rc3/docs/cli.md +149 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/concepts/blocks.md +5 -5
- engin-0.1.0rc3/docs/concepts/engin.md +62 -0
- engin-0.1.0rc3/docs/concepts/supervisor.md +64 -0
- engin-0.1.0rc3/docs/engin-graph-output.png +0 -0
- engin-0.1.0rc3/docs/index.md +98 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/integrations/fastapi.md +7 -11
- engin-0.1.0rc3/docs/tutorial/1_empty_application.md +37 -0
- engin-0.1.0rc3/docs/tutorial/2_create_a_publisher.md +91 -0
- engin-0.1.0rc3/docs/tutorial/3_run_the_application.md +26 -0
- engin-0.1.0rc3/docs/tutorial/4_create_the_consumer.md +37 -0
- engin-0.1.0rc3/docs/tutorial/5_refactor_valkey_client.md +52 -0
- engin-0.1.0rc3/docs/tutorial/index.md +10 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/main.py +3 -1
- engin-0.1.0rc3/examples/tutorial/app.py +21 -0
- engin-0.1.0rc3/examples/tutorial/consumer.py +36 -0
- engin-0.1.0rc3/examples/tutorial/publisher.py +28 -0
- engin-0.1.0rc3/examples/tutorial/valkey_client.py +23 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/mkdocs.yaml +12 -2
- {engin-0.1.0rc1 → engin-0.1.0rc3}/pyproject.toml +3 -2
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_assembler.py +5 -7
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_cli/_check.py +2 -17
- engin-0.1.0rc3/src/engin/_cli/_graph.html +883 -0
- engin-0.1.0rc3/src/engin/_cli/_graph.py +223 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_cli/_inspect.py +6 -3
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_engin.py +6 -3
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_supervisor.py +18 -4
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/exceptions.py +21 -6
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/cli/test_check.py +0 -1
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/cli/test_graph.py +3 -5
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/test_assembler.py +5 -5
- {engin-0.1.0rc1 → engin-0.1.0rc3}/uv.lock +184 -135
- engin-0.1.0rc1/docs/concepts/engin.md +0 -12
- engin-0.1.0rc1/docs/index.md +0 -86
- engin-0.1.0rc1/docs/tutorial.md +0 -15
- engin-0.1.0rc1/src/engin/_cli/_graph.html +0 -78
- engin-0.1.0rc1/src/engin/_cli/_graph.py +0 -164
- {engin-0.1.0rc1 → engin-0.1.0rc3}/.github/workflows/benchmark.yaml +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/.github/workflows/check.yaml +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/.github/workflows/publish.yaml +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/.readthedocs.yaml +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/LICENSE +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/concepts/invocations.md +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/concepts/lifecycle.md +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/concepts/providers.md +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/integrations/fastapi-graph.png +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/js/readthedocs.js +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/overrides/main.html +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/docs/reference.md +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/app.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/common/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/common/db/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/common/db/adapaters/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/common/db/adapaters/memory.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/common/db/block.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/common/db/ports.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/common/starlette/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/common/starlette/endpoint.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/features/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/features/cats/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/features/cats/api/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/features/cats/api/get.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/features/cats/api/post.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/features/cats/block.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/asgi/features/cats/domain.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/app.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/main.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/routes/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/routes/cats/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/routes/cats/adapters/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/routes/cats/adapters/repository.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/routes/cats/api.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/routes/cats/block.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/routes/cats/domain.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/fastapi/routes/cats/ports.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/simple/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/examples/simple/main.py +0 -0
- {engin-0.1.0rc1/src/engin/extensions → engin-0.1.0rc3/examples/tutorial}/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_block.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_cli/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_cli/_common.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_dependency.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_graph.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_introspect.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_lifecycle.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_option.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/_type_utils.py +0 -0
- {engin-0.1.0rc1/tests → engin-0.1.0rc3/src/engin/extensions}/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/extensions/asgi.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/extensions/fastapi.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/src/engin/py.typed +0 -0
- {engin-0.1.0rc1/tests/acceptance → engin-0.1.0rc3/tests}/__init__.py +0 -0
- {engin-0.1.0rc1/tests/benchmarks → engin-0.1.0rc3/tests/acceptance}/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/acceptance/test_engin_signal_handling.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/acceptance/test_error_in_invocation.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/acceptance/test_error_in_lifecycle_shutdown.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/acceptance/test_error_in_lifecycle_startup.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/acceptance/test_error_in_provider.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/acceptance/test_error_in_supervisor_task.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/acceptance/test_fastapi.py +0 -0
- {engin-0.1.0rc1/tests/cli → engin-0.1.0rc3/tests/benchmarks}/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/benchmarks/conftest.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/benchmarks/test_bench_assembler.py +0 -0
- /engin-0.1.0rc1/docs/concepts/supervisor.md → /engin-0.1.0rc3/tests/cli/__init__.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/cli/test_get_engin_instance.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/cli/test_inspect.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/conftest.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/deps.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/test_block.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/test_dependencies.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/test_engin.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/test_graph.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/test_lifecycle.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/test_supervisor.py +0 -0
- {engin-0.1.0rc1 → engin-0.1.0rc3}/tests/test_type_id.py +0 -0
@@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
13
13
|
- A new cli option `engin check` that validates whether you have any missing providers.
|
14
14
|
- Support for specifying `default-instance` in your `pyproject.toml` under `[tool.engin]`
|
15
15
|
which is used as a default value for the `app` parameter when using the cli.
|
16
|
+
- A new exception class: `TypeNotProvidedError`.
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- If a Provider is missing during Assembly, the Assembler now raises `TypeNotProvidedError`
|
21
|
+
instead of a `LookupError`.
|
16
22
|
|
17
23
|
|
18
24
|
## [0.0.20] - 2025-06-18
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: engin
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.0rc3
|
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/
|
@@ -33,13 +33,13 @@ Engin is a lightweight application framework powered by dependency injection, it
|
|
33
33
|
you build and maintain large monoliths and many microservices.
|
34
34
|
|
35
35
|
|
36
|
-
##
|
36
|
+
## Feature
|
37
37
|
|
38
|
-
The Engin framework
|
38
|
+
The Engin framework gives you:
|
39
39
|
|
40
40
|
- A fully-featured dependency injection system.
|
41
41
|
- A robust application runtime with lifecycle hooks and supervised background tasks.
|
42
|
-
- Zero boiler-plate code reuse across
|
42
|
+
- Zero boiler-plate code reuse across applications.
|
43
43
|
- Integrations for other frameworks such as FastAPI.
|
44
44
|
- Full async support.
|
45
45
|
- CLI commands to aid local development.
|
@@ -47,7 +47,7 @@ The Engin framework includes:
|
|
47
47
|
|
48
48
|
## Installation
|
49
49
|
|
50
|
-
Engin is available on PyPI, install using your favourite dependency manager:
|
50
|
+
Engin is available on PyPI, install it using your favourite dependency manager:
|
51
51
|
|
52
52
|
- `pip install engin`
|
53
53
|
- `poetry add engin`
|
@@ -55,13 +55,13 @@ Engin is available on PyPI, install using your favourite dependency manager:
|
|
55
55
|
|
56
56
|
## Example
|
57
57
|
|
58
|
-
A small example which shows some of the
|
59
|
-
makes
|
58
|
+
A small example which shows some of the features of Engin. This application
|
59
|
+
makes 3 http requests and shuts itself down.
|
60
60
|
|
61
61
|
```python
|
62
62
|
import asyncio
|
63
63
|
from httpx import AsyncClient
|
64
|
-
from engin import Engin, Invoke, Lifecycle, Provide, Supervisor
|
64
|
+
from engin import Engin, Invoke, Lifecycle, OnException, Provide, Supervisor
|
65
65
|
|
66
66
|
|
67
67
|
def httpx_client_factory(lifecycle: Lifecycle) -> AsyncClient:
|
@@ -76,13 +76,17 @@ async def main(
|
|
76
76
|
httpx_client: AsyncClient,
|
77
77
|
supervisor: Supervisor,
|
78
78
|
) -> None:
|
79
|
-
async def
|
80
|
-
|
81
|
-
|
82
|
-
|
79
|
+
async def http_requests_task():
|
80
|
+
# simulate a background task
|
81
|
+
for x in range(3):
|
82
|
+
await httpx_client.get("https://httpbin.org/get")
|
83
|
+
await asyncio.sleep(1.0)
|
84
|
+
# raise an error to shutdown the application, normally you wouldn't do this!
|
85
|
+
raise RuntimeError("Forcing shutdown")
|
86
|
+
|
87
|
+
# supervise the http requests as part of the application's lifecycle
|
88
|
+
supervisor.supervise(http_requests_task, on_exception=OnException.SHUTDOWN)
|
83
89
|
|
84
|
-
# supervise the http request as part of the application's lifecycle
|
85
|
-
supervisor.supervise(http_request)
|
86
90
|
|
87
91
|
# define our modular application
|
88
92
|
engin = Engin(Provide(httpx_client_factory), Invoke(main))
|
@@ -97,6 +101,15 @@ With logs enabled this will output:
|
|
97
101
|
INFO:engin:starting engin
|
98
102
|
INFO:engin:startup complete
|
99
103
|
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
104
|
+
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
105
|
+
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
106
|
+
ERROR:engin:supervisor task 'http_requests_task' raised RuntimeError, starting shutdown
|
107
|
+
Traceback (most recent call last):
|
108
|
+
File "C:\dev\python\engin\src\engin\_supervisor.py", line 58, in __call__
|
109
|
+
await self.factory()
|
110
|
+
File "C:\dev\python\engin\readme_example.py", line 29, in http_requests_task
|
111
|
+
raise RuntimeError("Forcing shutdown")
|
112
|
+
RuntimeError: Forcing shutdown
|
100
113
|
INFO:engin:stopping engin
|
101
114
|
INFO:engin:shutdown complete
|
102
115
|
```
|
@@ -106,4 +119,4 @@ INFO:engin:shutdown complete
|
|
106
119
|
Engin is heavily inspired by [Uber's Fx framework for Go](https://github.com/uber-go/fx)
|
107
120
|
and the [Injector framework for Python](https://github.com/python-injector/injector).
|
108
121
|
|
109
|
-
They are both great projects, check them out.
|
122
|
+
They are both great projects, go check them out.
|
@@ -14,13 +14,13 @@ Engin is a lightweight application framework powered by dependency injection, it
|
|
14
14
|
you build and maintain large monoliths and many microservices.
|
15
15
|
|
16
16
|
|
17
|
-
##
|
17
|
+
## Feature
|
18
18
|
|
19
|
-
The Engin framework
|
19
|
+
The Engin framework gives you:
|
20
20
|
|
21
21
|
- A fully-featured dependency injection system.
|
22
22
|
- A robust application runtime with lifecycle hooks and supervised background tasks.
|
23
|
-
- Zero boiler-plate code reuse across
|
23
|
+
- Zero boiler-plate code reuse across applications.
|
24
24
|
- Integrations for other frameworks such as FastAPI.
|
25
25
|
- Full async support.
|
26
26
|
- CLI commands to aid local development.
|
@@ -28,7 +28,7 @@ The Engin framework includes:
|
|
28
28
|
|
29
29
|
## Installation
|
30
30
|
|
31
|
-
Engin is available on PyPI, install using your favourite dependency manager:
|
31
|
+
Engin is available on PyPI, install it using your favourite dependency manager:
|
32
32
|
|
33
33
|
- `pip install engin`
|
34
34
|
- `poetry add engin`
|
@@ -36,13 +36,13 @@ Engin is available on PyPI, install using your favourite dependency manager:
|
|
36
36
|
|
37
37
|
## Example
|
38
38
|
|
39
|
-
A small example which shows some of the
|
40
|
-
makes
|
39
|
+
A small example which shows some of the features of Engin. This application
|
40
|
+
makes 3 http requests and shuts itself down.
|
41
41
|
|
42
42
|
```python
|
43
43
|
import asyncio
|
44
44
|
from httpx import AsyncClient
|
45
|
-
from engin import Engin, Invoke, Lifecycle, Provide, Supervisor
|
45
|
+
from engin import Engin, Invoke, Lifecycle, OnException, Provide, Supervisor
|
46
46
|
|
47
47
|
|
48
48
|
def httpx_client_factory(lifecycle: Lifecycle) -> AsyncClient:
|
@@ -57,13 +57,17 @@ async def main(
|
|
57
57
|
httpx_client: AsyncClient,
|
58
58
|
supervisor: Supervisor,
|
59
59
|
) -> None:
|
60
|
-
async def
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
async def http_requests_task():
|
61
|
+
# simulate a background task
|
62
|
+
for x in range(3):
|
63
|
+
await httpx_client.get("https://httpbin.org/get")
|
64
|
+
await asyncio.sleep(1.0)
|
65
|
+
# raise an error to shutdown the application, normally you wouldn't do this!
|
66
|
+
raise RuntimeError("Forcing shutdown")
|
67
|
+
|
68
|
+
# supervise the http requests as part of the application's lifecycle
|
69
|
+
supervisor.supervise(http_requests_task, on_exception=OnException.SHUTDOWN)
|
64
70
|
|
65
|
-
# supervise the http request as part of the application's lifecycle
|
66
|
-
supervisor.supervise(http_request)
|
67
71
|
|
68
72
|
# define our modular application
|
69
73
|
engin = Engin(Provide(httpx_client_factory), Invoke(main))
|
@@ -78,6 +82,15 @@ With logs enabled this will output:
|
|
78
82
|
INFO:engin:starting engin
|
79
83
|
INFO:engin:startup complete
|
80
84
|
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
85
|
+
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
86
|
+
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
87
|
+
ERROR:engin:supervisor task 'http_requests_task' raised RuntimeError, starting shutdown
|
88
|
+
Traceback (most recent call last):
|
89
|
+
File "C:\dev\python\engin\src\engin\_supervisor.py", line 58, in __call__
|
90
|
+
await self.factory()
|
91
|
+
File "C:\dev\python\engin\readme_example.py", line 29, in http_requests_task
|
92
|
+
raise RuntimeError("Forcing shutdown")
|
93
|
+
RuntimeError: Forcing shutdown
|
81
94
|
INFO:engin:stopping engin
|
82
95
|
INFO:engin:shutdown complete
|
83
96
|
```
|
@@ -87,4 +100,4 @@ INFO:engin:shutdown complete
|
|
87
100
|
Engin is heavily inspired by [Uber's Fx framework for Go](https://github.com/uber-go/fx)
|
88
101
|
and the [Injector framework for Python](https://github.com/python-injector/injector).
|
89
102
|
|
90
|
-
They are both great projects, check them out.
|
103
|
+
They are both great projects, go check them out.
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# CLI Commands
|
2
|
+
|
3
|
+
Engin provides a set of CLI commands to aid with application development. To use these commands,
|
4
|
+
you need to install Engin with the `cli` extra, which can safely be treated as a
|
5
|
+
development only dependency.
|
6
|
+
|
7
|
+
```shell
|
8
|
+
uv add engin[cli]
|
9
|
+
```
|
10
|
+
|
11
|
+
!!! tip
|
12
|
+
|
13
|
+
It is recommended to configure a default instance in your `pyproject.toml`.
|
14
|
+
|
15
|
+
```toml
|
16
|
+
[tool.engin]
|
17
|
+
default-instance = "myapp.main:engin"
|
18
|
+
```
|
19
|
+
|
20
|
+
When configured, you can run any CLI command without the app argument:
|
21
|
+
|
22
|
+
```shell
|
23
|
+
# Uses the default instance from pyproject.toml
|
24
|
+
engin check
|
25
|
+
|
26
|
+
# Passing an explicit instance always overrides the default instance
|
27
|
+
engin check myapp.main:engin
|
28
|
+
```
|
29
|
+
|
30
|
+
## Commands
|
31
|
+
|
32
|
+
- `engin check`: checks for missing providers.
|
33
|
+
- `engin inspect`: show metadata about providers.
|
34
|
+
- `engin graph`: visualise your dependency graph.
|
35
|
+
|
36
|
+
### engin check
|
37
|
+
|
38
|
+
Checks that all dependencies in your Engin instance are satisfied.
|
39
|
+
|
40
|
+
If there are any missing providers it will return with exit code 1.
|
41
|
+
|
42
|
+
Note, this command only validates there are providers for all dependencies required by
|
43
|
+
invocations, any dependencies built dynamically at runtime via the Assembler will not be
|
44
|
+
checked for as these cannot be statically analysed.
|
45
|
+
|
46
|
+
#### Usage
|
47
|
+
```shell
|
48
|
+
engin check [OPTIONS]
|
49
|
+
```
|
50
|
+
|
51
|
+
#### Options
|
52
|
+
|
53
|
+
- `--app`: the path to your application in the format `<module>:<attribute>`, e.g. `myapp.main:engin`. Not
|
54
|
+
required if you set a `default-instance` in your `pyproject.toml`.
|
55
|
+
|
56
|
+
#### Example
|
57
|
+
|
58
|
+
```shell
|
59
|
+
engin check myapp.main:engin
|
60
|
+
```
|
61
|
+
|
62
|
+
=== "Success"
|
63
|
+
|
64
|
+
```
|
65
|
+
✅ All dependencies are satisfied!
|
66
|
+
```
|
67
|
+
|
68
|
+
=== "Missing Dependencies"
|
69
|
+
|
70
|
+
```
|
71
|
+
❌ Missing providers found:
|
72
|
+
• httpx.AsyncClient
|
73
|
+
• DatabaseConfig
|
74
|
+
|
75
|
+
Available providers:
|
76
|
+
• str
|
77
|
+
• engin._assembler.Assembler
|
78
|
+
• engin._lifecycle.Lifecycle
|
79
|
+
```
|
80
|
+
|
81
|
+
### engin inspect
|
82
|
+
|
83
|
+
Shows detailed metadata for providers in your Engin instance. You can filter providers by type
|
84
|
+
or module.
|
85
|
+
|
86
|
+
#### Usage
|
87
|
+
```shell
|
88
|
+
engin inspect [OPTIONS]
|
89
|
+
```
|
90
|
+
|
91
|
+
#### Options
|
92
|
+
|
93
|
+
- `--app`: the path to your application in the format `<module>:<attribute>`, e.g. `myapp.main:engin`. Not
|
94
|
+
required if you set a `default-instance` in your `pyproject.toml`.
|
95
|
+
- `--type`: filter providers by return type name. Note that multiproviders take the form of `type[]`.
|
96
|
+
- `--module`: filter providers by the return type's module.
|
97
|
+
- `--verbose`: enable verbose output.
|
98
|
+
|
99
|
+
#### Example
|
100
|
+
|
101
|
+
```shell
|
102
|
+
engin inspect myapp.main:engin --module httpx
|
103
|
+
```
|
104
|
+
|
105
|
+
=== "Output"
|
106
|
+
|
107
|
+
```
|
108
|
+
Found 1 matching provider
|
109
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
110
|
+
│ name │ Provide(factory=httpx_client, type=AsyncClient) │
|
111
|
+
│ scope │ N/A │
|
112
|
+
│ func │ httpx_client │
|
113
|
+
│ block │ N/A │
|
114
|
+
│ source module │ myapp.main │
|
115
|
+
│ source package │ myapp │
|
116
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
117
|
+
```
|
118
|
+
|
119
|
+
### engin graph
|
120
|
+
|
121
|
+
Creates a visual representation of your application's dependency graph.
|
122
|
+
|
123
|
+
This starts a local web server which displays an interactive graph of your dependencies.
|
124
|
+
|
125
|
+
#### Usage
|
126
|
+
```shell
|
127
|
+
engin graph [OPTIONS]
|
128
|
+
```
|
129
|
+
|
130
|
+
#### Options
|
131
|
+
|
132
|
+
- `--app`: the path to your application in the format `<module>:<attribute>`, e.g. `myapp.main:engin`. Not
|
133
|
+
required if you set a `default-instance` in your `pyproject.toml`.
|
134
|
+
|
135
|
+
#### Example
|
136
|
+
|
137
|
+
```shell
|
138
|
+
engin graph myapp.main:engin
|
139
|
+
```
|
140
|
+
|
141
|
+
=== "Visualisation"
|
142
|
+
|
143
|
+

|
144
|
+
|
145
|
+
=== "Console"
|
146
|
+
|
147
|
+
```
|
148
|
+
Serving dependency graph on http://localhost:8123
|
149
|
+
```
|
@@ -49,8 +49,8 @@ await engin.run() # prints 'hello'
|
|
49
49
|
|
50
50
|
!!!tip
|
51
51
|
|
52
|
-
Blocks are themselves valid options, so Blocks can include other Blocks as options. This
|
53
|
-
compisitional approach can help you build and manage larger applications.
|
52
|
+
Blocks are themselves valid options, so Blocks can include other Blocks as options. This
|
53
|
+
compisitional approach can help you build and manage larger applications.
|
54
54
|
|
55
55
|
|
56
56
|
## Defining Providers & Invocations in the Block
|
@@ -77,6 +77,6 @@ class ExampleBlock(Block):
|
|
77
77
|
|
78
78
|
!!!note
|
79
79
|
|
80
|
-
The `self` parameter in these methods is replaced with an empty object at runtime so
|
81
|
-
should not be used. Blocks do not need to be instantiated to be passed to Engin as an
|
82
|
-
option.
|
80
|
+
The `self` parameter in these methods is replaced with an empty object at runtime so
|
81
|
+
should not be used. Blocks do not need to be instantiated to be passed to Engin as an
|
82
|
+
option.
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# The Engin
|
2
|
+
|
3
|
+
An Engin instance is a self-contained application.
|
4
|
+
|
5
|
+
The Engin class manages your application's complete lifecycle, when ran it will:
|
6
|
+
|
7
|
+
1. Assemble the dependencies required by your invocations.
|
8
|
+
2. Runs all given invocations sequentially in the order they were passed in to the Engin.
|
9
|
+
3. Run all lifecycle startup tasks that were registered by assembled dependencies sequentially.
|
10
|
+
4. Start any supervised background tasks.
|
11
|
+
5. Wait for a shutdown signal, SIGINT or SIGTERM, or for a supervised task to cause a shutdown.
|
12
|
+
6. Run all corresponding lifecycle shutdown tasks in the reverse order to the startup order.
|
13
|
+
|
14
|
+
|
15
|
+
## Creating an Engin
|
16
|
+
|
17
|
+
Instantiate an Engin with any combination of options, i.e. providers, invocations, and blocks:
|
18
|
+
|
19
|
+
```python
|
20
|
+
from engin import Engin, Provide, Invoke, Supervisor
|
21
|
+
|
22
|
+
|
23
|
+
def my_service_factory(supervisor: Supervisor) -> MyService:
|
24
|
+
my_service = MyService()
|
25
|
+
supervisor.supervise(my_service.run)
|
26
|
+
return my_service
|
27
|
+
|
28
|
+
engin = Engin(Provide(build_service), Entrypoint(MyService))
|
29
|
+
```
|
30
|
+
|
31
|
+
## Running your application
|
32
|
+
|
33
|
+
### `engin.run()`
|
34
|
+
|
35
|
+
The recommended way to run your application.
|
36
|
+
|
37
|
+
This will not return until the application is stopped and shutdown has been performed. As it
|
38
|
+
listens for signals and handles cancellation this should be the top-most function called in
|
39
|
+
your application:
|
40
|
+
|
41
|
+
```python
|
42
|
+
import asyncio
|
43
|
+
|
44
|
+
await asyncio.run(engin.run())
|
45
|
+
```
|
46
|
+
|
47
|
+
|
48
|
+
### `engin.start()` and `engin.stop()`
|
49
|
+
|
50
|
+
For advanced scenarios where you need more control over the application lifecycle:
|
51
|
+
|
52
|
+
```python
|
53
|
+
# Start the application in the background
|
54
|
+
await engin.start()
|
55
|
+
|
56
|
+
# Do other work...
|
57
|
+
|
58
|
+
# Gracefully stop the application
|
59
|
+
await engin.stop()
|
60
|
+
```
|
61
|
+
|
62
|
+
This approach can be useful when writing tests for an Engin application.
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Supervisor
|
2
|
+
|
3
|
+
The Supervisor manages background tasks for your application.
|
4
|
+
|
5
|
+
Background tasks are long-running coroutines that need to run concurrently while your
|
6
|
+
application is running. The Supervisor handles starting these tasks, monitoring them for errors,
|
7
|
+
and cancelling them when the application is shutting down.
|
8
|
+
|
9
|
+
## Using the Supervisor
|
10
|
+
|
11
|
+
The Supervisor is automatically provided by the Engin and can be injected into any provider or invocation:
|
12
|
+
|
13
|
+
```python
|
14
|
+
from engin import Engin, Provide, Supervisor, OnException
|
15
|
+
|
16
|
+
|
17
|
+
def background_worker(supervisor: Supervisor) -> WorkerService:
|
18
|
+
worker = WorkerService()
|
19
|
+
|
20
|
+
# Register the worker's run method as a supervised task
|
21
|
+
supervisor.supervise(worker.run)
|
22
|
+
|
23
|
+
return worker
|
24
|
+
|
25
|
+
|
26
|
+
engin = Engin(Provide(background_worker))
|
27
|
+
```
|
28
|
+
|
29
|
+
|
30
|
+
## Error Handling
|
31
|
+
|
32
|
+
The Supervisor provides three error handling strategies via the `OnException` enum:
|
33
|
+
|
34
|
+
### `OnException.SHUTDOWN`
|
35
|
+
|
36
|
+
Stops the entire application when the task fails:
|
37
|
+
|
38
|
+
```python
|
39
|
+
supervisor.supervise(critical_task) # Will shutdown app on error
|
40
|
+
```
|
41
|
+
|
42
|
+
This is the default behaviour.
|
43
|
+
|
44
|
+
### `OnException.RETRY`
|
45
|
+
|
46
|
+
Automatically restarts the task when it fails:
|
47
|
+
|
48
|
+
```python
|
49
|
+
supervisor.supervise(
|
50
|
+
flaky_network_task,
|
51
|
+
on_exception=OnException.RETRY
|
52
|
+
)
|
53
|
+
```
|
54
|
+
|
55
|
+
### `OnException.IGNORE`
|
56
|
+
|
57
|
+
Logs the error but continues running other tasks:
|
58
|
+
|
59
|
+
```python
|
60
|
+
supervisor.supervise(
|
61
|
+
optional_monitoring_task,
|
62
|
+
on_exception=OnException.IGNORE
|
63
|
+
)
|
64
|
+
```
|
Binary file
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Engin 🏎️
|
2
|
+
|
3
|
+
Engin is a lightweight application framework powered by dependency injection, it helps
|
4
|
+
you build and maintain large monoliths and microservices.
|
5
|
+
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
The Engin framework gives you:
|
10
|
+
|
11
|
+
- A fully-featured dependency injection system.
|
12
|
+
- A robust application runtime with lifecycle hooks and supervised background tasks.
|
13
|
+
- Zero boiler-plate code reuse across applications.
|
14
|
+
- Integrations for other frameworks such as FastAPI.
|
15
|
+
- Full async support.
|
16
|
+
- CLI commands to aid local development.
|
17
|
+
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
=== "uv"
|
22
|
+
|
23
|
+
```shell
|
24
|
+
uv add engin
|
25
|
+
```
|
26
|
+
|
27
|
+
=== "poetry"
|
28
|
+
|
29
|
+
```shell
|
30
|
+
poetry add engin
|
31
|
+
```
|
32
|
+
|
33
|
+
=== "pip"
|
34
|
+
|
35
|
+
```shell
|
36
|
+
pip install engin
|
37
|
+
```
|
38
|
+
|
39
|
+
## Example
|
40
|
+
|
41
|
+
A small example which shows some of the features of Engin. This application
|
42
|
+
makes 3 http requests and shuts itself down.
|
43
|
+
|
44
|
+
```python
|
45
|
+
import asyncio
|
46
|
+
from httpx import AsyncClient
|
47
|
+
from engin import Engin, Invoke, Lifecycle, OnException, Provide, Supervisor
|
48
|
+
|
49
|
+
|
50
|
+
def httpx_client_factory(lifecycle: Lifecycle) -> AsyncClient:
|
51
|
+
# create our http client
|
52
|
+
client = AsyncClient()
|
53
|
+
# this will open and close the AsyncClient as part of the application's lifecycle
|
54
|
+
lifecycle.append(client)
|
55
|
+
return client
|
56
|
+
|
57
|
+
|
58
|
+
async def main(
|
59
|
+
httpx_client: AsyncClient,
|
60
|
+
supervisor: Supervisor,
|
61
|
+
) -> None:
|
62
|
+
async def http_requests_task():
|
63
|
+
# simulate a background task
|
64
|
+
for x in range(3):
|
65
|
+
await httpx_client.get("https://httpbin.org/get")
|
66
|
+
await asyncio.sleep(1.0)
|
67
|
+
# raise an error to shutdown the application, normally you wouldn't do this!
|
68
|
+
raise RuntimeError("Forcing shutdown")
|
69
|
+
|
70
|
+
# supervise the http requests as part of the application's lifecycle
|
71
|
+
supervisor.supervise(http_requests_task, on_exception=OnException.SHUTDOWN)
|
72
|
+
|
73
|
+
|
74
|
+
# define our modular application
|
75
|
+
engin = Engin(Provide(httpx_client_factory), Invoke(main))
|
76
|
+
|
77
|
+
# run it!
|
78
|
+
asyncio.run(engin.run())
|
79
|
+
```
|
80
|
+
|
81
|
+
With logs enabled this will output:
|
82
|
+
|
83
|
+
```shell
|
84
|
+
INFO:engin:starting engin
|
85
|
+
INFO:engin:startup complete
|
86
|
+
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
87
|
+
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
88
|
+
INFO:httpx:HTTP Request: GET https://httpbin.org/get "HTTP/1.1 200 OK"
|
89
|
+
ERROR:engin:supervisor task 'http_requests_task' raised RuntimeError, starting shutdown
|
90
|
+
Traceback (most recent call last):
|
91
|
+
File "C:\dev\python\engin\src\engin\_supervisor.py", line 58, in __call__
|
92
|
+
await self.factory()
|
93
|
+
File "C:\dev\python\engin\readme_example.py", line 29, in http_requests_task
|
94
|
+
raise RuntimeError("Forcing shutdown")
|
95
|
+
RuntimeError: Forcing shutdown
|
96
|
+
INFO:engin:stopping engin
|
97
|
+
INFO:engin:shutdown complete
|
98
|
+
```
|
@@ -75,28 +75,24 @@ reusable SQL session per request, we could use a nested dependency:
|
|
75
75
|
from typing import Annotated, AsyncIterable
|
76
76
|
|
77
77
|
from engin.extensions.fastapi import Inject
|
78
|
-
from fastapi import Depends
|
79
78
|
|
80
79
|
|
81
80
|
async def database_session(
|
82
|
-
|
83
|
-
|
84
|
-
) -> AsyncIterable[Session]:
|
81
|
+
database: Annotated[Database, Inject(Database)]
|
82
|
+
) -> AsyncIterable[Session]:
|
85
83
|
with database.new_session() as session:
|
86
84
|
yield session
|
87
|
-
|
88
|
-
|
89
|
-
@ app.post("/{id}")
|
90
|
-
async
|
85
|
+
session.commit()
|
91
86
|
|
92
|
-
|
93
|
-
|
87
|
+
@app.post("/{id}")
|
88
|
+
async def add_item(session: Annotated[Session, Inject(database_session)]):
|
89
|
+
session.add(MyORMModel(...))
|
94
90
|
```
|
95
91
|
|
96
92
|
|
97
93
|
## Attaching Routers to Engin
|
98
94
|
|
99
|
-
The
|
95
|
+
The idiomatic way to declare an `APIRouter` is as a module level variable, for example:
|
100
96
|
|
101
97
|
```python title="api.py"
|
102
98
|
from fastapi import APIRouter
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Let's start by creating a minimal application using Engin. We do this by instantiating the
|
2
|
+
Engin class which will serve as our application runtime.
|
3
|
+
|
4
|
+
```python title="app.py"
|
5
|
+
import asyncio
|
6
|
+
import logging
|
7
|
+
|
8
|
+
from engin import Engin
|
9
|
+
|
10
|
+
engin = Engin()
|
11
|
+
|
12
|
+
if __name__ == "__main__":
|
13
|
+
logging.basicConfig(level=logging.INFO)
|
14
|
+
asyncio.run(engin.run())
|
15
|
+
```
|
16
|
+
|
17
|
+
If we run the application using `python app.py` we will see the following output:
|
18
|
+
|
19
|
+
```
|
20
|
+
INFO:engin:starting engin
|
21
|
+
INFO:engin:startup complete
|
22
|
+
```
|
23
|
+
|
24
|
+
And if we stop the application using `ctrl + c` or equivalent we will see:
|
25
|
+
|
26
|
+
```
|
27
|
+
INFO:engin:stopping engin
|
28
|
+
INFO:engin:shutdown complete
|
29
|
+
```
|
30
|
+
|
31
|
+
Now that we have an empty application, let's give it something to do.
|
32
|
+
|
33
|
+
!!! note
|
34
|
+
|
35
|
+
Engin is typically used for long running application such as Web Servers or an Event
|
36
|
+
Consumers. In these scenarios the engin would run until it receives a SIGINT signal (for
|
37
|
+
example when a new deployment happens) at which point it would shutdown.
|