engin 0.0.13__tar.gz → 0.0.15__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 (94) hide show
  1. {engin-0.0.13 → engin-0.0.15}/.readthedocs.yaml +1 -1
  2. {engin-0.0.13 → engin-0.0.15}/CHANGELOG.md +38 -1
  3. {engin-0.0.13 → engin-0.0.15}/PKG-INFO +3 -1
  4. {engin-0.0.13 → engin-0.0.15}/docs/concepts/engin.md +1 -1
  5. {engin-0.0.13 → engin-0.0.15}/docs/concepts/invocations.md +5 -8
  6. engin-0.0.15/docs/concepts/lifecycle.md +100 -0
  7. {engin-0.0.13 → engin-0.0.15}/docs/concepts/providers.md +36 -23
  8. {engin-0.0.13 → engin-0.0.15}/docs/getting-started.md +1 -1
  9. {engin-0.0.13 → engin-0.0.15}/docs/guides/fastapi.md +1 -1
  10. engin-0.0.15/docs/js/readthedocs.js +7 -0
  11. {engin-0.0.13 → engin-0.0.15}/mkdocs.yaml +0 -1
  12. {engin-0.0.13 → engin-0.0.15}/pyproject.toml +16 -12
  13. {engin-0.0.13 → engin-0.0.15}/src/engin/__init__.py +4 -1
  14. {engin-0.0.13 → engin-0.0.15}/src/engin/_assembler.py +63 -55
  15. engin-0.0.15/src/engin/_block.py +89 -0
  16. engin-0.0.15/src/engin/_cli/__init__.py +13 -0
  17. engin-0.0.13/src/engin/scripts/graph.py → engin-0.0.15/src/engin/_cli/_graph.py +48 -31
  18. engin-0.0.15/src/engin/_cli/_utils.py +18 -0
  19. {engin-0.0.13 → engin-0.0.15}/src/engin/_dependency.py +81 -36
  20. {engin-0.0.13 → engin-0.0.15}/src/engin/_engin.py +21 -59
  21. engin-0.0.15/src/engin/_introspect.py +34 -0
  22. {engin-0.0.13 → engin-0.0.15}/src/engin/_lifecycle.py +68 -2
  23. engin-0.0.15/src/engin/_option.py +10 -0
  24. {engin-0.0.13 → engin-0.0.15}/src/engin/_type_utils.py +8 -10
  25. {engin-0.0.13 → engin-0.0.15}/src/engin/ext/asgi.py +2 -1
  26. {engin-0.0.13 → engin-0.0.15}/src/engin/ext/fastapi.py +12 -5
  27. engin-0.0.15/tests/cli/test_graph.py +39 -0
  28. {engin-0.0.13 → engin-0.0.15}/tests/test_assembler.py +49 -11
  29. engin-0.0.15/tests/test_block.py +25 -0
  30. {engin-0.0.13 → engin-0.0.15}/tests/test_dependencies.py +38 -0
  31. engin-0.0.15/tests/test_graph.py +33 -0
  32. engin-0.0.15/tests/test_lifecycle.py +82 -0
  33. engin-0.0.15/tests/test_utils.py +81 -0
  34. {engin-0.0.13 → engin-0.0.15}/uv.lock +202 -121
  35. engin-0.0.13/docs/concepts/lifecycle.md +0 -0
  36. engin-0.0.13/docs/guides/dependency_injection.md +0 -4
  37. engin-0.0.13/docs/js/readthedocs.js +0 -32
  38. engin-0.0.13/src/engin/_block.py +0 -70
  39. engin-0.0.13/tests/test_modules.py +0 -19
  40. engin-0.0.13/tests/test_utils.py +0 -69
  41. {engin-0.0.13 → engin-0.0.15}/.github/workflows/check.yaml +0 -0
  42. {engin-0.0.13 → engin-0.0.15}/.github/workflows/publish.yaml +0 -0
  43. {engin-0.0.13 → engin-0.0.15}/.gitignore +0 -0
  44. {engin-0.0.13 → engin-0.0.15}/LICENSE +0 -0
  45. {engin-0.0.13 → engin-0.0.15}/README.md +0 -0
  46. {engin-0.0.13 → engin-0.0.15}/docs/guides/fastapi-graph.png +0 -0
  47. {engin-0.0.13 → engin-0.0.15}/docs/index.md +0 -0
  48. {engin-0.0.13 → engin-0.0.15}/docs/overrides/main.html +0 -0
  49. {engin-0.0.13 → engin-0.0.15}/docs/reference.md +0 -0
  50. {engin-0.0.13 → engin-0.0.15}/examples/__init__.py +0 -0
  51. {engin-0.0.13 → engin-0.0.15}/examples/asgi/__init__.py +0 -0
  52. {engin-0.0.13 → engin-0.0.15}/examples/asgi/app.py +0 -0
  53. {engin-0.0.13 → engin-0.0.15}/examples/asgi/common/__init__.py +0 -0
  54. {engin-0.0.13 → engin-0.0.15}/examples/asgi/common/db/__init__.py +0 -0
  55. {engin-0.0.13 → engin-0.0.15}/examples/asgi/common/db/adapaters/__init__.py +0 -0
  56. {engin-0.0.13 → engin-0.0.15}/examples/asgi/common/db/adapaters/memory.py +0 -0
  57. {engin-0.0.13 → engin-0.0.15}/examples/asgi/common/db/block.py +0 -0
  58. {engin-0.0.13 → engin-0.0.15}/examples/asgi/common/db/ports.py +0 -0
  59. {engin-0.0.13 → engin-0.0.15}/examples/asgi/common/starlette/__init__.py +0 -0
  60. {engin-0.0.13 → engin-0.0.15}/examples/asgi/common/starlette/endpoint.py +0 -0
  61. {engin-0.0.13 → engin-0.0.15}/examples/asgi/features/__init__.py +0 -0
  62. {engin-0.0.13 → engin-0.0.15}/examples/asgi/features/cats/__init__.py +0 -0
  63. {engin-0.0.13 → engin-0.0.15}/examples/asgi/features/cats/api/__init__.py +0 -0
  64. {engin-0.0.13 → engin-0.0.15}/examples/asgi/features/cats/api/get.py +0 -0
  65. {engin-0.0.13 → engin-0.0.15}/examples/asgi/features/cats/api/post.py +0 -0
  66. {engin-0.0.13 → engin-0.0.15}/examples/asgi/features/cats/block.py +0 -0
  67. {engin-0.0.13 → engin-0.0.15}/examples/asgi/features/cats/domain.py +0 -0
  68. {engin-0.0.13 → engin-0.0.15}/examples/asgi/main.py +0 -0
  69. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/__init__.py +0 -0
  70. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/app.py +0 -0
  71. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/main.py +0 -0
  72. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/routes/__init__.py +0 -0
  73. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/routes/cats/__init__.py +0 -0
  74. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/routes/cats/adapters/__init__.py +0 -0
  75. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/routes/cats/adapters/repository.py +0 -0
  76. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/routes/cats/api.py +0 -0
  77. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/routes/cats/block.py +0 -0
  78. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/routes/cats/domain.py +0 -0
  79. {engin-0.0.13 → engin-0.0.15}/examples/fastapi/routes/cats/ports.py +0 -0
  80. {engin-0.0.13 → engin-0.0.15}/examples/simple/__init__.py +0 -0
  81. {engin-0.0.13 → engin-0.0.15}/examples/simple/main.py +0 -0
  82. {engin-0.0.13 → engin-0.0.15}/src/engin/_exceptions.py +0 -0
  83. {engin-0.0.13 → engin-0.0.15}/src/engin/_graph.py +0 -0
  84. {engin-0.0.13 → engin-0.0.15}/src/engin/ext/__init__.py +0 -0
  85. {engin-0.0.13 → engin-0.0.15}/src/engin/py.typed +0 -0
  86. {engin-0.0.13/src/engin/scripts → engin-0.0.15/tests}/__init__.py +0 -0
  87. {engin-0.0.13/tests → engin-0.0.15/tests/acceptance}/__init__.py +0 -0
  88. {engin-0.0.13 → engin-0.0.15}/tests/acceptance/test_error_in_shutdown.py +0 -0
  89. {engin-0.0.13 → engin-0.0.15}/tests/acceptance/test_error_in_start_up.py +0 -0
  90. {engin-0.0.13 → engin-0.0.15}/tests/acceptance/test_fastapi.py +0 -0
  91. {engin-0.0.13/tests/acceptance → engin-0.0.15/tests/cli}/__init__.py +0 -0
  92. {engin-0.0.13 → engin-0.0.15}/tests/conftest.py +0 -0
  93. {engin-0.0.13 → engin-0.0.15}/tests/deps.py +0 -0
  94. {engin-0.0.13 → engin-0.0.15}/tests/test_engin.py +0 -0
@@ -22,4 +22,4 @@ build:
22
22
  - NO_COLOR=1 uv run mkdocs build -f mkdocs.yaml --strict --site-dir $READTHEDOCS_OUTPUT/html
23
23
 
24
24
  mkdocs:
25
- configuration: mkdocs.yaml
25
+ configuration: mkdocs.yaml
@@ -6,7 +6,44 @@ 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
8
 
9
- ## [0.0.13] - 2025-03-12
9
+ ## [0.0.15] - 2025-03-25
10
+
11
+ ### Changed
12
+
13
+ - `Provide` & `Supply` will now raise an error if overriding an existing provider from the
14
+ same package. This is to prevent accidental overrides. Users can explicitly allow
15
+ overrides by specifying the `override` parameter when defining the provider
16
+ `Provide(..., override=True)` or `@provide(override=True)`.
17
+ - Lifecycle startup tasks will now timeout after 15 seconds and raise an error.
18
+ - Assembler's `get` method has been renamed to `build`.
19
+ - Supply's `type_hint` parameter has been renamed to `as_type`.
20
+
21
+ ### Fixed
22
+
23
+ - `Assembler` would occasionally fail to call all multiproviders due to inconsistent
24
+ ordering.
25
+
26
+
27
+ ## [0.0.14] - 2025-03-23
28
+
29
+ ### Added
30
+
31
+ - `LifecycleHook` class to help build simple lifecycles with a start and stop call.
32
+
33
+ ### Changed
34
+
35
+ - `engin-graph` has been replaced by `engin graph`.
36
+ - Engin now uses `typer` for an improved cli experience. Note the package now has an extra `cli` which must be installed to use the cli.
37
+ - `Assembler.add(...)` does not error when adding already registered providers.
38
+ - Use a more performant algorithm for inspecting frame stack.
39
+
40
+ ### Fixed
41
+
42
+ - `ASGIEngin` now properly surfaces startup errors.
43
+ - `Engin.run()` doing a double shutdown.
44
+
45
+
46
+ ## [0.0.13] - 2025-03-22
10
47
 
11
48
  ### Changed
12
49
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engin
3
- Version: 0.0.13
3
+ Version: 0.0.15
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
+ Provides-Extra: cli
14
+ Requires-Dist: typer>=0.15; extra == 'cli'
13
15
  Description-Content-Type: text/markdown
14
16
 
15
17
  [![codecov](https://codecov.io/gh/invokermain/engin/graph/badge.svg?token=4PJOIMV6IB)](https://codecov.io/gh/invokermain/engin)
@@ -9,4 +9,4 @@ When ran the Engin takes care of the complete application lifecycle:
9
9
  2. All Invocations are run sequentially in the order they were passed in to the Engin.
10
10
  3. Any Lifecycle Startup defined by Provider that were assembled is ran.
11
11
  4. The Engin waits for a stop signal, i.e. SIGINT or SIGTERM.
12
- 5. Any Lifecyce Shutdown task is ran, in the reverse order to the Startup order.
12
+ 5. Any Lifecyce Shutdown tasks are run, in reverse order to the Startup order.
@@ -23,15 +23,12 @@ imports for brevity):
23
23
 
24
24
  ```python
25
25
  def worker_factory(lifecycle: Lifecycle) -> Worker:
26
- worker = Worker()
26
+ worker = Worker()
27
27
 
28
- @asynccontextmanager
29
- async def worker_lifecycle() -> Iterable[None]:
30
- worker.start()
31
- yield
32
- worker.shutdown()
33
-
34
- lifecycle.append(worker_lifecycle)
28
+ lifecycle.hook(
29
+ on_start=worker.start,
30
+ on_stop=worker.shutdown
31
+ )
35
32
 
36
33
  return worker
37
34
  ```
@@ -0,0 +1,100 @@
1
+ from contextlib import asynccontextmanager
2
+
3
+ # Lifecycle
4
+
5
+ Certain types of object naturally have some form of startup and shutdown behaviour
6
+ associated with them, these steps need to be tied the lifecycle of the application itself
7
+ in order to be useful. For example, a database connection manager might want to fill its
8
+ connection pool on startup, and gracefully release the connections on shutdown.
9
+
10
+ Doing this yourself can be tricky and is application dependent: most will not have any
11
+ special support for this and will expect you to manage your lifecycle concerns in your
12
+ 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
14
+ offer, e.g. an ASGI server would expect you to manage this via its lifespan. In both cases
15
+ you end up managing lifecycle in a completely different place to where you declare your
16
+ objects, which make the codebase more complicated to understand.
17
+
18
+ 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.
20
+
21
+ ## The Lifecycle type
22
+
23
+ Engin automatically provides a special type called `Lifecycle` that can be used like any
24
+ other provided type. This type allows you to register lifecycle tasks with the Engin which
25
+ will automatically be run as part of your application lifecycle.
26
+
27
+ ## Registering lifecycle tasks
28
+
29
+ There are a few different ways to declare and register your lifecycle tasks, they all do
30
+ the same thing, so which one to use depends on whichever is easiest for your specific
31
+ lifecycle tasks.
32
+
33
+ ### 1. Existing context manager
34
+
35
+ If your type exposes a context manager interface to handle its lifecycle, registering it
36
+ is as easy as calling `lifecycle.append(...)`, this works for sync and async context
37
+ managers.
38
+
39
+ Let's look at an example using `httpx.AsyncClient`:
40
+
41
+ ```python
42
+ from engin import Lifecycle
43
+ from httpx import AsyncClient
44
+
45
+
46
+ def httpx_client(lifecycle: Lifecycle) -> AsyncClient:
47
+ client = AsyncClient()
48
+ lifecycle.append(client) # register the lifecycle tasks
49
+ return client
50
+ ```
51
+
52
+ ### 2. Explict startup & shutdown methods
53
+
54
+ If your type exposes meathods that must be called as part of the lifecycle, e.g. `start()`
55
+ & `stop()`, then `lifecycle.hook(on_start=..., on_stop=...)` is the way.
56
+
57
+ Let's look at an example using `piccolo.engine.PostgresEngin`:
58
+
59
+ ```python
60
+ from engin import Lifecycle
61
+ from piccolo.engine import PostgresEngine
62
+
63
+ def postgres_engine(lifecyle: Lifecycle) -> PostgresEngine:
64
+ db_engine = PostgresEngine(...) # fill in actual connection details
65
+
66
+ lifecyle.hook(
67
+ on_start=db_engine.start_connection_pool(),
68
+ on_stop=db_engine.close_connection_pool(),
69
+ )
70
+
71
+ return db_engine
72
+ ```
73
+
74
+ ### 3. Custom context managers
75
+
76
+ For more advanced use cases you can always define your own context manager.
77
+
78
+ In this example assume that `worker.run()` will not return to us when we await it, and
79
+ therefore we want to manage it as a task.
80
+
81
+
82
+ ```python
83
+ import asyncio
84
+ from engin import Lifecycle
85
+ from some_package import BlockingAsyncWorker
86
+
87
+ def blocking_worker(lifecycle: Lifecycle) -> BlockingWorker:
88
+ worker = BlockingAsyncWorker()
89
+
90
+ @asynccontextmanager
91
+ async def worker_lifecycle() -> AsyncIterator[None]:
92
+ task = asyncio.create_task(worker.run())
93
+ yield None
94
+ worker.stop()
95
+ del task
96
+
97
+ lifecycle.append(worker_lifecycle())
98
+
99
+ return worker
100
+ ```
@@ -17,17 +17,19 @@ class: `Provide`.
17
17
  ```python
18
18
  from engin import Engin, Provide
19
19
 
20
+
20
21
  # define our constructor
21
22
  def string_factory() -> str:
22
- return "hello"
23
+ return "hello"
24
+
23
25
 
24
26
  # register it as a provider with the Engin
25
27
  engin = Engin(Provide(string_factory))
26
28
 
27
29
  # construct the string
28
- a_string = await engin.assembler.get(str)
30
+ a_string = await engin.assembler.build(str)
29
31
 
30
- print(a_string) # hello
32
+ print(a_string) # hello
31
33
  ```
32
34
 
33
35
  Providers can be asynchronous as well, this factory function would work exactly the same
@@ -45,27 +47,31 @@ Providers that construct more interesting objects generally require their own pa
45
47
  ```python
46
48
  from engin import Engin, Provide
47
49
 
50
+
48
51
  class Greeter:
49
52
  def __init__(self, greeting: str) -> None:
50
53
  self._greeting = greeting
51
-
54
+
52
55
  def greet(self, name: str) -> None:
53
56
  print(f"{self._greeting}, {name}!")
54
-
57
+
58
+
55
59
  # define our constructors
56
60
  def string_factory() -> str:
57
- return "hello"
61
+ return "hello"
62
+
58
63
 
59
64
  def greeter_factory(greeting: str) -> Greeter:
60
65
  return Greeter(greeting=greeting)
61
66
 
67
+
62
68
  # register them as providers with the Engin
63
69
  engin = Engin(Provide(string_factory), Provide(greeter_factory))
64
70
 
65
71
  # construct the Greeter
66
- greeter = await engin.assembler.get(Greeter)
72
+ greeter = await engin.assembler.build(Greeter)
67
73
 
68
- greeter.greet("Bob") # hello, Bob!
74
+ greeter.greet("Bob") # hello, Bob!
69
75
  ```
70
76
 
71
77
 
@@ -81,19 +87,21 @@ from engin import Engin, Provide
81
87
 
82
88
  # define our constructors
83
89
  def string_factory() -> str:
84
- return "hello"
90
+ return "hello"
91
+
85
92
 
86
93
  def evil_factory() -> int:
87
94
  raise RuntimeError("I have ruined your plans")
88
95
 
96
+
89
97
  # register them as providers with the Engin
90
98
  engin = Engin(Provide(string_factory), Provide(evil_factory))
91
99
 
92
100
  # this will not raise an error
93
- await engin.assembler.get(str)
101
+ await engin.assembler.build(str)
94
102
 
95
103
  # this will raise an error
96
- await engin.assembler.get(int)
104
+ await engin.assembler.build(int)
97
105
  ```
98
106
 
99
107
 
@@ -109,20 +117,23 @@ To turn a factory into a multiprovider, simply return a list:
109
117
  ```python
110
118
  from engin import Engin, Provide
111
119
 
120
+
112
121
  # define our constructors
113
122
  def animal_names_factory() -> list[str]:
114
- return ["cat", "dog"]
123
+ return ["cat", "dog"]
124
+
115
125
 
116
126
  def other_animal_names_factory() -> list[str]:
117
- return ["horse", "cow"]
127
+ return ["horse", "cow"]
128
+
118
129
 
119
130
  # register them as providers with the Engin
120
131
  engin = Engin(Provide(animal_names_factory), Provide(other_animal_names_factory))
121
132
 
122
133
  # construct the list of strings
123
- animal_names = await engin.assembler.get(list[str])
134
+ animal_names = await engin.assembler.build(list[str])
124
135
 
125
- print(animal_names) # ["cat", "dog", "horse", "cow"]
136
+ print(animal_names) # ["cat", "dog", "horse", "cow"]
126
137
  ```
127
138
 
128
139
 
@@ -133,25 +144,28 @@ Providers of the same type can be discriminated using annotations.
133
144
  ```python
134
145
  from engin import Engin, Provide
135
146
  from typing import Annotated
136
-
147
+
148
+
137
149
  # define our constructors
138
150
  def greeting_factory() -> Annotated[str, "greeting"]:
139
- return "hello"
151
+ return "hello"
152
+
140
153
 
141
154
  def name_factory() -> Annotated[str, "name"]:
142
155
  return "Jelena"
143
156
 
157
+
144
158
  # register them as providers with the Engin
145
159
  engin = Engin(Provide(greeting_factory), Provide(name_factory))
146
160
 
147
161
  # this will return "hello"
148
- await engin.assembler.get(Annotated[str, "greeting"])
162
+ await engin.assembler.build(Annotated[str, "greeting"])
149
163
 
150
164
  # this will return "Jelena"
151
- await engin.assembler.get(Annotated[str, "name"])
165
+ await engin.assembler.build(Annotated[str, "name"])
152
166
 
153
167
  # N.B. this will raise an error!
154
- await engin.assembler.get(str)
168
+ await engin.assembler.build(str)
155
169
  ```
156
170
 
157
171
 
@@ -162,7 +176,6 @@ provided type is automatically inferred.
162
176
 
163
177
  For example the first example on this page could be rewritten as:
164
178
 
165
-
166
179
  ```python
167
180
  from engin import Engin, Supply
168
181
 
@@ -170,7 +183,7 @@ from engin import Engin, Supply
170
183
  engin = Engin(Supply("hello"))
171
184
 
172
185
  # construct the string
173
- a_string = await engin.assembler.get(str)
186
+ a_string = await engin.assembler.build(str)
174
187
 
175
- print(a_string) # hello
188
+ print(a_string) # hello
176
189
  ```
@@ -2,6 +2,6 @@
2
2
 
3
3
  Engin is available on PyPI, install using your favourite dependency manager:
4
4
 
5
- - **pip**:`pip install engin`
5
+ - **pip**: `pip install engin`
6
6
  - **poetry**: `poetry add engin`
7
7
  - **uv**: `uv add engin`
@@ -159,7 +159,7 @@ app = FastAPIEngin(AppBlock())
159
159
 
160
160
  ## Graphing Dependencies
161
161
 
162
- Engin provides dependency visualisation functionality via the `engin-graph` script. When
162
+ Engin provides dependency visualisation functionality via the `engin graph` script. When
163
163
  working with a FastAPI application this can be used to visualise API Routes along with
164
164
  their respective dependencies.
165
165
 
@@ -0,0 +1,7 @@
1
+ document.addEventListener("DOMContentLoaded", function(event) {
2
+ // Trigger Read the Docs' search addon instead of Material MkDocs default
3
+ document.querySelector(".md-search__input").addEventListener("focus", (e) => {
4
+ const event = new CustomEvent("readthedocs-search-show");
5
+ document.dispatchEvent(event);
6
+ });
7
+ });
@@ -34,7 +34,6 @@ nav:
34
34
  - Invocations: "concepts/invocations.md"
35
35
  - Lifecycle: "concepts/lifecycle.md"
36
36
  - Guides:
37
- - Dependency Injection: "guides/dependency_injection.md"
38
37
  - FastAPI: "guides/fastapi.md"
39
38
  - Reference: "reference.md"
40
39
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "engin"
3
- version = "0.0.13"
3
+ version = "0.0.15"
4
4
  description = "An async-first modular application framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -8,19 +8,21 @@ license = "MIT"
8
8
  keywords = ["Dependency Injection", "Application Framework"]
9
9
  dependencies = []
10
10
 
11
+ [project.optional-dependencies]
12
+ cli = ["typer>=0.15"]
13
+
14
+ [project.scripts]
15
+ engin = "engin._cli:app"
16
+
11
17
  [project.urls]
12
18
  Homepage = "https://github.com/invokermain/engin"
13
19
  Documentation = "https://engin.readthedocs.io/en/latest/"
14
20
  Repository = "https://github.com/invokermain/engin.git"
15
21
  Changelog = "https://github.com/invokermain/engin/blob/main/CHANGELOG.md"
16
22
 
17
- [build-system]
18
- requires = ["hatchling"]
19
- build-backend = "hatchling.build"
20
-
21
23
 
22
- [tool.uv]
23
- dev-dependencies = [
24
+ [dependency-groups]
25
+ dev = [
24
26
  "fastapi>=0.115.6",
25
27
  "httpx>=0.27.2",
26
28
  "mypy>=1",
@@ -33,18 +35,20 @@ dev-dependencies = [
33
35
  "starlette>=0.39.2",
34
36
  "uvicorn>=0.31.1",
35
37
  "pytest-cov>=6.0.0",
38
+ "typer>=0.15.2",
39
+ "pytest-mock>=3.14.0",
36
40
  ]
37
-
38
-
39
- [dependency-groups]
40
41
  docs = [
41
42
  "mkdocs-material>=9.5.50",
42
43
  "mkdocstrings[python]>=0.27.0",
43
44
  ]
44
45
 
46
+ [tool.uv]
47
+ default-groups = ["dev", "docs"]
45
48
 
46
- [project.scripts]
47
- engin-graph = "engin.scripts.graph:serve_graph"
49
+ [build-system]
50
+ requires = ["hatchling"]
51
+ build-backend = "hatchling.build"
48
52
 
49
53
 
50
54
  [tool.ruff]
@@ -2,9 +2,11 @@ from engin import ext
2
2
  from engin._assembler import Assembler
3
3
  from engin._block import Block, invoke, provide
4
4
  from engin._dependency import Entrypoint, Invoke, Provide, Supply
5
- from engin._engin import Engin, Option
5
+ from engin._engin import Engin
6
6
  from engin._exceptions import ProviderError
7
7
  from engin._lifecycle import Lifecycle
8
+ from engin._option import Option
9
+ from engin._type_utils import TypeId
8
10
 
9
11
  __all__ = [
10
12
  "Assembler",
@@ -17,6 +19,7 @@ __all__ = [
17
19
  "Provide",
18
20
  "ProviderError",
19
21
  "Supply",
22
+ "TypeId",
20
23
  "ext",
21
24
  "invoke",
22
25
  "provide",