engin 0.0.20__tar.gz → 0.1.0__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.
Files changed (125) hide show
  1. {engin-0.0.20 → engin-0.1.0}/.github/workflows/benchmark.yaml +3 -0
  2. {engin-0.0.20 → engin-0.1.0}/.github/workflows/check.yaml +4 -0
  3. {engin-0.0.20 → engin-0.1.0}/.gitignore +2 -0
  4. {engin-0.0.20 → engin-0.1.0}/CHANGELOG.md +17 -0
  5. engin-0.1.0/PKG-INFO +122 -0
  6. engin-0.1.0/README.md +103 -0
  7. engin-0.1.0/docs/cli.md +144 -0
  8. {engin-0.0.20 → engin-0.1.0}/docs/concepts/blocks.md +8 -7
  9. engin-0.1.0/docs/concepts/engin.md +62 -0
  10. {engin-0.0.20 → engin-0.1.0}/docs/concepts/invocations.md +8 -8
  11. {engin-0.0.20 → engin-0.1.0}/docs/concepts/lifecycle.md +13 -10
  12. {engin-0.0.20 → engin-0.1.0}/docs/concepts/providers.md +87 -3
  13. engin-0.1.0/docs/concepts/supervisor.md +64 -0
  14. engin-0.1.0/docs/engin-graph-output.png +0 -0
  15. engin-0.1.0/docs/index.md +98 -0
  16. {engin-0.0.20/docs/guides → engin-0.1.0/docs/integrations}/fastapi.md +9 -13
  17. engin-0.1.0/docs/tutorial/1_empty_application.md +37 -0
  18. engin-0.1.0/docs/tutorial/2_create_a_publisher.md +101 -0
  19. engin-0.1.0/docs/tutorial/3_run_the_application.md +61 -0
  20. engin-0.1.0/docs/tutorial/4_refactor_valkey_client.md +100 -0
  21. engin-0.1.0/docs/tutorial/index.md +8 -0
  22. {engin-0.0.20 → engin-0.1.0}/examples/asgi/main.py +3 -1
  23. engin-0.1.0/examples/tutorial/app.py +18 -0
  24. engin-0.1.0/examples/tutorial/publisher.py +28 -0
  25. engin-0.1.0/examples/tutorial/valkey_client.py +23 -0
  26. {engin-0.0.20 → engin-0.1.0}/mkdocs.yaml +24 -9
  27. {engin-0.0.20 → engin-0.1.0}/pyproject.toml +10 -5
  28. {engin-0.0.20 → engin-0.1.0}/src/engin/__init__.py +3 -0
  29. {engin-0.0.20 → engin-0.1.0}/src/engin/_assembler.py +12 -12
  30. {engin-0.0.20 → engin-0.1.0}/src/engin/_cli/__init__.py +2 -0
  31. engin-0.1.0/src/engin/_cli/_check.py +56 -0
  32. engin-0.1.0/src/engin/_cli/_common.py +121 -0
  33. engin-0.1.0/src/engin/_cli/_graph.html +883 -0
  34. engin-0.1.0/src/engin/_cli/_graph.py +223 -0
  35. {engin-0.0.20 → engin-0.1.0}/src/engin/_cli/_inspect.py +10 -8
  36. engin-0.1.0/src/engin/_engin.py +281 -0
  37. engin-0.1.0/src/engin/_supervisor.py +137 -0
  38. {engin-0.0.20 → engin-0.1.0}/src/engin/exceptions.py +21 -6
  39. {engin-0.0.20 → engin-0.1.0}/src/engin/extensions/asgi.py +2 -0
  40. {engin-0.0.20 → engin-0.1.0}/src/engin/extensions/fastapi.py +2 -2
  41. engin-0.1.0/tests/acceptance/test_engin_signal_handling.py +28 -0
  42. engin-0.1.0/tests/acceptance/test_error_in_invocation.py +28 -0
  43. engin-0.0.20/tests/acceptance/test_error_in_shutdown.py → engin-0.1.0/tests/acceptance/test_error_in_lifecycle_shutdown.py +13 -3
  44. engin-0.0.20/tests/acceptance/test_error_in_start_up.py → engin-0.1.0/tests/acceptance/test_error_in_lifecycle_startup.py +28 -4
  45. engin-0.1.0/tests/acceptance/test_error_in_provider.py +35 -0
  46. engin-0.1.0/tests/acceptance/test_error_in_supervisor_task.py +29 -0
  47. {engin-0.0.20 → engin-0.1.0}/tests/acceptance/test_fastapi.py +1 -0
  48. engin-0.1.0/tests/cli/test_check.py +94 -0
  49. engin-0.1.0/tests/cli/test_get_engin_instance.py +111 -0
  50. {engin-0.0.20 → engin-0.1.0}/tests/cli/test_graph.py +3 -5
  51. engin-0.1.0/tests/conftest.py +0 -0
  52. {engin-0.0.20 → engin-0.1.0}/tests/test_assembler.py +13 -11
  53. {engin-0.0.20 → engin-0.1.0}/tests/test_engin.py +0 -16
  54. engin-0.1.0/tests/test_supervisor.py +113 -0
  55. {engin-0.0.20 → engin-0.1.0}/uv.lock +289 -208
  56. engin-0.0.20/PKG-INFO +0 -71
  57. engin-0.0.20/README.md +0 -55
  58. engin-0.0.20/docs/concepts/engin.md +0 -12
  59. engin-0.0.20/docs/getting-started.md +0 -7
  60. engin-0.0.20/docs/index.md +0 -21
  61. engin-0.0.20/src/engin/_cli/_common.py +0 -51
  62. engin-0.0.20/src/engin/_cli/_graph.html +0 -78
  63. engin-0.0.20/src/engin/_cli/_graph.py +0 -160
  64. engin-0.0.20/src/engin/_engin.py +0 -210
  65. {engin-0.0.20 → engin-0.1.0}/.github/workflows/publish.yaml +0 -0
  66. {engin-0.0.20 → engin-0.1.0}/.readthedocs.yaml +0 -0
  67. {engin-0.0.20 → engin-0.1.0}/LICENSE +0 -0
  68. {engin-0.0.20/docs/guides → engin-0.1.0/docs/integrations}/fastapi-graph.png +0 -0
  69. {engin-0.0.20 → engin-0.1.0}/docs/js/readthedocs.js +0 -0
  70. {engin-0.0.20 → engin-0.1.0}/docs/overrides/main.html +0 -0
  71. {engin-0.0.20 → engin-0.1.0}/docs/reference.md +0 -0
  72. {engin-0.0.20 → engin-0.1.0}/examples/__init__.py +0 -0
  73. {engin-0.0.20 → engin-0.1.0}/examples/asgi/__init__.py +0 -0
  74. {engin-0.0.20 → engin-0.1.0}/examples/asgi/app.py +0 -0
  75. {engin-0.0.20 → engin-0.1.0}/examples/asgi/common/__init__.py +0 -0
  76. {engin-0.0.20 → engin-0.1.0}/examples/asgi/common/db/__init__.py +0 -0
  77. {engin-0.0.20 → engin-0.1.0}/examples/asgi/common/db/adapaters/__init__.py +0 -0
  78. {engin-0.0.20 → engin-0.1.0}/examples/asgi/common/db/adapaters/memory.py +0 -0
  79. {engin-0.0.20 → engin-0.1.0}/examples/asgi/common/db/block.py +0 -0
  80. {engin-0.0.20 → engin-0.1.0}/examples/asgi/common/db/ports.py +0 -0
  81. {engin-0.0.20 → engin-0.1.0}/examples/asgi/common/starlette/__init__.py +0 -0
  82. {engin-0.0.20 → engin-0.1.0}/examples/asgi/common/starlette/endpoint.py +0 -0
  83. {engin-0.0.20 → engin-0.1.0}/examples/asgi/features/__init__.py +0 -0
  84. {engin-0.0.20 → engin-0.1.0}/examples/asgi/features/cats/__init__.py +0 -0
  85. {engin-0.0.20 → engin-0.1.0}/examples/asgi/features/cats/api/__init__.py +0 -0
  86. {engin-0.0.20 → engin-0.1.0}/examples/asgi/features/cats/api/get.py +0 -0
  87. {engin-0.0.20 → engin-0.1.0}/examples/asgi/features/cats/api/post.py +0 -0
  88. {engin-0.0.20 → engin-0.1.0}/examples/asgi/features/cats/block.py +0 -0
  89. {engin-0.0.20 → engin-0.1.0}/examples/asgi/features/cats/domain.py +0 -0
  90. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/__init__.py +0 -0
  91. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/app.py +0 -0
  92. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/main.py +0 -0
  93. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/routes/__init__.py +0 -0
  94. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/routes/cats/__init__.py +0 -0
  95. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/routes/cats/adapters/__init__.py +0 -0
  96. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/routes/cats/adapters/repository.py +0 -0
  97. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/routes/cats/api.py +0 -0
  98. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/routes/cats/block.py +0 -0
  99. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/routes/cats/domain.py +0 -0
  100. {engin-0.0.20 → engin-0.1.0}/examples/fastapi/routes/cats/ports.py +0 -0
  101. {engin-0.0.20 → engin-0.1.0}/examples/simple/__init__.py +0 -0
  102. {engin-0.0.20 → engin-0.1.0}/examples/simple/main.py +0 -0
  103. {engin-0.0.20/src/engin/extensions → engin-0.1.0/examples/tutorial}/__init__.py +0 -0
  104. {engin-0.0.20 → engin-0.1.0}/src/engin/_block.py +0 -0
  105. {engin-0.0.20 → engin-0.1.0}/src/engin/_dependency.py +0 -0
  106. {engin-0.0.20 → engin-0.1.0}/src/engin/_graph.py +0 -0
  107. {engin-0.0.20 → engin-0.1.0}/src/engin/_introspect.py +0 -0
  108. {engin-0.0.20 → engin-0.1.0}/src/engin/_lifecycle.py +0 -0
  109. {engin-0.0.20 → engin-0.1.0}/src/engin/_option.py +0 -0
  110. {engin-0.0.20 → engin-0.1.0}/src/engin/_type_utils.py +0 -0
  111. {engin-0.0.20/tests → engin-0.1.0/src/engin/extensions}/__init__.py +0 -0
  112. {engin-0.0.20 → engin-0.1.0}/src/engin/py.typed +0 -0
  113. {engin-0.0.20/tests/acceptance → engin-0.1.0/tests}/__init__.py +0 -0
  114. {engin-0.0.20/tests/benchmarks → engin-0.1.0/tests/acceptance}/__init__.py +0 -0
  115. {engin-0.0.20/tests/cli → engin-0.1.0/tests/benchmarks}/__init__.py +0 -0
  116. {engin-0.0.20 → engin-0.1.0}/tests/benchmarks/conftest.py +0 -0
  117. {engin-0.0.20 → engin-0.1.0}/tests/benchmarks/test_bench_assembler.py +0 -0
  118. /engin-0.0.20/tests/conftest.py → /engin-0.1.0/tests/cli/__init__.py +0 -0
  119. {engin-0.0.20 → engin-0.1.0}/tests/cli/test_inspect.py +0 -0
  120. {engin-0.0.20 → engin-0.1.0}/tests/deps.py +0 -0
  121. {engin-0.0.20 → engin-0.1.0}/tests/test_block.py +0 -0
  122. {engin-0.0.20 → engin-0.1.0}/tests/test_dependencies.py +0 -0
  123. {engin-0.0.20 → engin-0.1.0}/tests/test_graph.py +0 -0
  124. {engin-0.0.20 → engin-0.1.0}/tests/test_lifecycle.py +0 -0
  125. {engin-0.0.20 → engin-0.1.0}/tests/test_type_id.py +0 -0
@@ -4,6 +4,9 @@ on:
4
4
  push:
5
5
  branches: [main]
6
6
 
7
+ env:
8
+ UV_FROZEN: "1"
9
+
7
10
  jobs:
8
11
  benchmark-main:
9
12
  runs-on: ubuntu-latest
@@ -3,6 +3,9 @@ name: Check
3
3
  on:
4
4
  push:
5
5
 
6
+ env:
7
+ UV_FROZEN: "1"
8
+
6
9
  jobs:
7
10
  check:
8
11
  name: python
@@ -40,6 +43,7 @@ jobs:
40
43
  run: uv run poe ci-test
41
44
 
42
45
  - name: Upload coverage reports to Codecov
46
+ if: matrix.os == 'ubuntu-latest'
43
47
  uses: codecov/codecov-action@v5
44
48
  with:
45
49
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -163,3 +163,5 @@ cython_debug/
163
163
  # and can be added to the global gitignore or merged into this file. For a more nuclear
164
164
  # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165
165
  .idea/
166
+
167
+ scrap/**
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.0] - 2025-08-16
9
+
10
+ ### Added
11
+
12
+ - `Supervisor` class which can safely supervise long running tasks.
13
+ - A new cli option `engin check` that validates whether you have any missing providers.
14
+ - Support for specifying `default-instance` in your `pyproject.toml` under `[tool.engin]`
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`.
22
+ - `engin graph` has improved visualisations and options.
23
+ - `engin check` does not list all available providers anymore.
24
+
8
25
 
9
26
  ## [0.0.20] - 2025-06-18
10
27
 
engin-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: engin
3
+ Version: 0.1.0
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: tomli>=2.0; (python_version < '3.11') and extra == 'cli'
17
+ Requires-Dist: typer>=0.15; extra == 'cli'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Engin 🏎️
21
+
22
+ [![codecov](https://codecov.io/gh/invokermain/engin/graph/badge.svg?token=4PJOIMV6IB)](https://codecov.io/gh/invokermain/engin)
23
+
24
+ ---
25
+
26
+ **Documentation**: [https://engin.readthedocs.io/](https://engin.readthedocs.io/)
27
+
28
+ **Source Code**: [https://github.com/invokermain/engin](https://github.com/invokermain/engin)
29
+
30
+ ---
31
+
32
+ Engin is a lightweight application framework powered by dependency injection, it helps
33
+ you build and maintain large monoliths and many microservices.
34
+
35
+
36
+ ## Feature
37
+
38
+ The Engin framework gives you:
39
+
40
+ - A fully-featured dependency injection system.
41
+ - A robust application runtime with lifecycle hooks and supervised background tasks.
42
+ - Zero boilerplate code reuse across applications.
43
+ - Integrations for other frameworks such as FastAPI.
44
+ - Full async support.
45
+ - CLI commands to aid local development.
46
+
47
+
48
+ ## Installation
49
+
50
+ Engin is available on PyPI, install it using your favourite dependency manager:
51
+
52
+ - `pip install engin`
53
+ - `poetry add engin`
54
+ - `uv add engin`
55
+
56
+ ## Example
57
+
58
+ A small example which shows some of the features of Engin. This application
59
+ makes 3 http requests and shuts itself down.
60
+
61
+ ```python
62
+ import asyncio
63
+ from httpx import AsyncClient
64
+ from engin import Engin, Invoke, Lifecycle, OnException, Provide, Supervisor
65
+
66
+
67
+ def httpx_client_factory(lifecycle: Lifecycle) -> AsyncClient:
68
+ # create our http client
69
+ client = AsyncClient()
70
+ # this will open and close the AsyncClient as part of the application's lifecycle
71
+ lifecycle.append(client)
72
+ return client
73
+
74
+
75
+ async def main(
76
+ httpx_client: AsyncClient,
77
+ supervisor: Supervisor,
78
+ ) -> None:
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)
89
+
90
+
91
+ # define our modular application
92
+ engin = Engin(Provide(httpx_client_factory), Invoke(main))
93
+
94
+ # run it!
95
+ asyncio.run(engin.run())
96
+ ```
97
+
98
+ With logs enabled this will output:
99
+
100
+ ```shell
101
+ INFO:engin:starting engin
102
+ INFO:engin:startup complete
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
113
+ INFO:engin:stopping engin
114
+ INFO:engin:shutdown complete
115
+ ```
116
+
117
+ ## Inspiration
118
+
119
+ Engin is heavily inspired by [Uber's Fx framework for Go](https://github.com/uber-go/fx)
120
+ and the [Injector framework for Python](https://github.com/python-injector/injector).
121
+
122
+ They are both great projects, go check them out.
engin-0.1.0/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Engin 🏎️
2
+
3
+ [![codecov](https://codecov.io/gh/invokermain/engin/graph/badge.svg?token=4PJOIMV6IB)](https://codecov.io/gh/invokermain/engin)
4
+
5
+ ---
6
+
7
+ **Documentation**: [https://engin.readthedocs.io/](https://engin.readthedocs.io/)
8
+
9
+ **Source Code**: [https://github.com/invokermain/engin](https://github.com/invokermain/engin)
10
+
11
+ ---
12
+
13
+ Engin is a lightweight application framework powered by dependency injection, it helps
14
+ you build and maintain large monoliths and many microservices.
15
+
16
+
17
+ ## Feature
18
+
19
+ The Engin framework gives you:
20
+
21
+ - A fully-featured dependency injection system.
22
+ - A robust application runtime with lifecycle hooks and supervised background tasks.
23
+ - Zero boilerplate code reuse across applications.
24
+ - Integrations for other frameworks such as FastAPI.
25
+ - Full async support.
26
+ - CLI commands to aid local development.
27
+
28
+
29
+ ## Installation
30
+
31
+ Engin is available on PyPI, install it using your favourite dependency manager:
32
+
33
+ - `pip install engin`
34
+ - `poetry add engin`
35
+ - `uv add engin`
36
+
37
+ ## Example
38
+
39
+ A small example which shows some of the features of Engin. This application
40
+ makes 3 http requests and shuts itself down.
41
+
42
+ ```python
43
+ import asyncio
44
+ from httpx import AsyncClient
45
+ from engin import Engin, Invoke, Lifecycle, OnException, Provide, Supervisor
46
+
47
+
48
+ def httpx_client_factory(lifecycle: Lifecycle) -> AsyncClient:
49
+ # create our http client
50
+ client = AsyncClient()
51
+ # this will open and close the AsyncClient as part of the application's lifecycle
52
+ lifecycle.append(client)
53
+ return client
54
+
55
+
56
+ async def main(
57
+ httpx_client: AsyncClient,
58
+ supervisor: Supervisor,
59
+ ) -> None:
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)
70
+
71
+
72
+ # define our modular application
73
+ engin = Engin(Provide(httpx_client_factory), Invoke(main))
74
+
75
+ # run it!
76
+ asyncio.run(engin.run())
77
+ ```
78
+
79
+ With logs enabled this will output:
80
+
81
+ ```shell
82
+ INFO:engin:starting engin
83
+ INFO:engin:startup complete
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
94
+ INFO:engin:stopping engin
95
+ INFO:engin:shutdown complete
96
+ ```
97
+
98
+ ## Inspiration
99
+
100
+ Engin is heavily inspired by [Uber's Fx framework for Go](https://github.com/uber-go/fx)
101
+ and the [Injector framework for Python](https://github.com/python-injector/injector).
102
+
103
+ They are both great projects, go check them out.
@@ -0,0 +1,144 @@
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
+
76
+ ### engin inspect
77
+
78
+ Shows detailed metadata for providers in your Engin instance. You can filter providers by type
79
+ or module.
80
+
81
+ #### Usage
82
+ ```shell
83
+ engin inspect [OPTIONS]
84
+ ```
85
+
86
+ #### Options
87
+
88
+ - `--app`: the path to your application in the format `<module>:<attribute>`, e.g. `myapp.main:engin`. Not
89
+ required if you set a `default-instance` in your `pyproject.toml`.
90
+ - `--type`: filter providers by return type name. Note that multiproviders take the form of `type[]`.
91
+ - `--module`: filter providers by the return type's module.
92
+ - `--verbose`: enable verbose output.
93
+
94
+ #### Example
95
+
96
+ ```shell
97
+ engin inspect myapp.main:engin --module httpx
98
+ ```
99
+
100
+ === "Output"
101
+
102
+ ```
103
+ Found 1 matching provider
104
+ ┌─────────────────────────────────────────────────────────────────────────┐
105
+ │ name │ Provide(factory=httpx_client, type=AsyncClient) │
106
+ │ scope │ N/A │
107
+ │ func │ httpx_client │
108
+ │ block │ N/A │
109
+ │ source module │ myapp.main │
110
+ │ source package │ myapp │
111
+ └─────────────────────────────────────────────────────────────────────────┘
112
+ ```
113
+
114
+ ### engin graph
115
+
116
+ Creates a visual representation of your application's dependency graph.
117
+
118
+ This starts a local web server which displays an interactive graph of your dependencies.
119
+
120
+ #### Usage
121
+ ```shell
122
+ engin graph [OPTIONS]
123
+ ```
124
+
125
+ #### Options
126
+
127
+ - `--app`: the path to your application in the format `<module>:<attribute>`, e.g. `myapp.main:engin`. Not
128
+ required if you set a `default-instance` in your `pyproject.toml`.
129
+
130
+ #### Example
131
+
132
+ ```shell
133
+ engin graph myapp.main:engin
134
+ ```
135
+
136
+ === "Visualisation"
137
+
138
+ ![engin-graph-output.png](engin-graph-output.png)
139
+
140
+ === "Console"
141
+
142
+ ```
143
+ Serving dependency graph on http://localhost:8123
144
+ ```
@@ -27,10 +27,11 @@ Blocks have a class attribute named `options` which can be used to include exist
27
27
  options.
28
28
 
29
29
  ```python
30
+ import asyncio
30
31
  from engin import Engin, Block, Invoke, Provide, Supply
31
32
 
32
33
 
33
- def print_string(self, string: str) -> None:
34
+ def print_string(string: str) -> None:
34
35
  print(string)
35
36
 
36
37
 
@@ -44,13 +45,13 @@ class ExampleBlock(Block):
44
45
  # register it as a provider with the Engin
45
46
  engin = Engin(ExampleBlock())
46
47
 
47
- await engin.run() # prints 'hello'
48
+ asyncio.run(engin.run()) # prints 'hello'
48
49
  ```
49
50
 
50
51
  !!!tip
51
52
 
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.
53
+ Blocks are themselves valid options, so Blocks can include other Blocks as options. This
54
+ compositional approach can help you build and manage larger applications.
54
55
 
55
56
 
56
57
  ## Defining Providers & Invocations in the Block
@@ -77,6 +78,6 @@ class ExampleBlock(Block):
77
78
 
78
79
  !!!note
79
80
 
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.
81
+ The `self` parameter in these methods is replaced with an empty object at runtime so
82
+ should not be used. Blocks do not need to be instantiated to be passed to Engin as an
83
+ 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. Stop any supervised background tasks that are still running.
13
+ 7. Run all corresponding lifecycle shutdown tasks in the reverse order to the startup order.
14
+
15
+
16
+ ## Creating an Engin
17
+
18
+ Instantiate an Engin with any combination of options, i.e. providers, invocations, and blocks:
19
+
20
+ ```python
21
+ from engin import Engin, Entrypoint, Provide, Supervisor
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(my_service_factory), 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
+ 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.
@@ -1,19 +1,19 @@
1
1
  # Invocations
2
2
 
3
- Invocations define the behaviour of your application, therefore without any Invocations
3
+ Invocations define the behaviour of your application, without any Invocations
4
4
  your application will not do anything.
5
5
 
6
- Like providers invocations are functions that take one or more dependencies as parameters,
7
- and they should always return None as the return value will not used by the Engin.
6
+ Like providers, invocations are functions that take one or more dependencies as
7
+ parameters, but they should always return None as the return value will not be used by Engin.
8
8
 
9
- As part of the Engin's startup, all declared invocations will be called sequentially in
10
- the order they were registered.
9
+ As part of the Engin's startup sequence, all declared invocations will be called
10
+ sequentially in the order they were registered.
11
11
 
12
12
  Invocations can be used to define behaviour in two ways.
13
13
 
14
14
  **Implicit: Provider Lifecycle**
15
15
 
16
- Invocations are always called therefore their dependencies are always assembled. This
16
+ Invocations are always called and therefore their dependencies are always assembled. This
17
17
  means that any providers with lifecycles will register their lifecycles with the
18
18
  application if directly or indirectly used by an invocation.
19
19
 
@@ -113,7 +113,7 @@ Invocations can use any types as long as they have the matching providers.
113
113
 
114
114
  ```python
115
115
  import asyncio
116
- from engin import Engin, Invoke
116
+ from engin import Engin, Invoke, Provide
117
117
 
118
118
  # define a constructor
119
119
  def name_factory() -> str:
@@ -123,7 +123,7 @@ def print_hello(name: str) -> None:
123
123
  print(f"hello {name}!")
124
124
 
125
125
  # register it as a invocation with the Engin
126
- engin = Engin(Provide(name_factory()), Invoke(hello_world))
126
+ engin = Engin(Provide(name_factory), Invoke(print_hello))
127
127
 
128
128
  # run your application
129
129
  asyncio.run(engin.run()) # hello Dmitrii!
@@ -1,5 +1,3 @@
1
- from contextlib import asynccontextmanager
2
-
3
1
  # Lifecycle
4
2
 
5
3
  Certain types of object naturally have some form of startup and shutdown behaviour
@@ -10,13 +8,13 @@ connection pool on startup, and gracefully release the connections on shutdown.
10
8
  Doing this yourself can be tricky and is application dependent: most will not have any
11
9
  special support for this and will expect you to manage your lifecycle concerns in your
12
10
  entrypoint function, leading to unwieldy code in larger applications, whilst other
13
- types application might expected you to translate the lifecyle tasks into something they
11
+ types application might expected you to translate the lifecycle tasks into something they
14
12
  offer, e.g. an ASGI server would expect you to manage this via its lifespan. In both cases
15
13
  you end up managing lifecycle in a completely different place to where you declare your
16
14
  objects, which make the codebase more complicated to understand.
17
15
 
18
16
  Luckily, engin makes declaring lifecycle tasks a breeze, and it can be done in the same
19
- provider that build your object keeping your code nicely collocated.
17
+ provider that builds your object keeping your code nicely collocated.
20
18
 
21
19
  ## The Lifecycle type
22
20
 
@@ -49,9 +47,9 @@ def httpx_client(lifecycle: Lifecycle) -> AsyncClient:
49
47
  return client
50
48
  ```
51
49
 
52
- ### 2. Explict startup & shutdown methods
50
+ ### 2. Explicit startup & shutdown methods
53
51
 
54
- If your type exposes meathods that must be called as part of the lifecycle, e.g. `start()`
52
+ If your type exposes methods that must be called as part of the lifecycle, e.g. `start()`
55
53
  & `stop()`, then `lifecycle.hook(on_start=..., on_stop=...)` is the way.
56
54
 
57
55
  Let's look at an example using `piccolo.engine.PostgresEngin`:
@@ -60,12 +58,12 @@ Let's look at an example using `piccolo.engine.PostgresEngin`:
60
58
  from engin import Lifecycle
61
59
  from piccolo.engine import PostgresEngine
62
60
 
63
- def postgres_engine(lifecyle: Lifecycle) -> PostgresEngine:
61
+ def postgres_engine(lifecycle: Lifecycle) -> PostgresEngine:
64
62
  db_engine = PostgresEngine(...) # fill in actual connection details
65
63
 
66
- lifecyle.hook(
67
- on_start=db_engine.start_connection_pool(),
68
- on_stop=db_engine.close_connection_pool(),
64
+ lifecycle.hook(
65
+ on_start=db_engine.start_connection_pool,
66
+ on_stop=db_engine.close_connection_pool,
69
67
  )
70
68
 
71
69
  return db_engine
@@ -81,6 +79,7 @@ therefore we want to manage it as a task.
81
79
 
82
80
  ```python
83
81
  import asyncio
82
+ from contextlib import asynccontextmanager
84
83
  from engin import Lifecycle
85
84
  from some_package import BlockingAsyncWorker
86
85
 
@@ -98,3 +97,7 @@ def blocking_worker(lifecycle: Lifecycle) -> BlockingWorker:
98
97
 
99
98
  return worker
100
99
  ```
100
+
101
+ !!! note
102
+
103
+ The above case is only given as a reference, running background tasks should be done via the `Supervisor` depedency.