engin 0.1.0a2__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 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,7 +2,7 @@ import asyncio
2
2
  import logging
3
3
  import os
4
4
  import signal
5
- from asyncio import Event, Task
5
+ from asyncio import Event
6
6
  from collections import defaultdict
7
7
  from contextlib import AsyncExitStack
8
8
  from enum import Enum
@@ -10,14 +10,13 @@ from itertools import chain
10
10
  from types import FrameType
11
11
  from typing import ClassVar
12
12
 
13
- from anyio import CancelScope, create_task_group, get_cancelled_exc_class
13
+ from anyio import create_task_group, get_cancelled_exc_class
14
14
 
15
15
  from engin._assembler import AssembledDependency, Assembler
16
16
  from engin._dependency import Invoke, Provide, Supply
17
17
  from engin._graph import DependencyGrapher, Node
18
18
  from engin._lifecycle import Lifecycle
19
19
  from engin._option import Option
20
- from engin._shutdown import ShutdownSwitch
21
20
  from engin._supervisor import Supervisor
22
21
  from engin._type_utils import TypeId
23
22
  from engin.exceptions import EnginError
@@ -29,25 +28,15 @@ LOG = logging.getLogger("engin")
29
28
  class _EnginState(Enum):
30
29
  IDLE = 0
31
30
  """
32
- Not yet started.
31
+ Engin is not yet started.
33
32
  """
34
33
 
35
- STARTED = 1
34
+ RUNNING = 1
36
35
  """
37
- Engin started via .start() call
36
+ Engin is currently running.
38
37
  """
39
38
 
40
- RUNNING = 2
41
- """
42
- Engin running via .run() call
43
- """
44
-
45
- STOPPING = 3
46
- """
47
- Engin stopped via .stop() call
48
- """
49
-
50
- SHUTDOWN = 4
39
+ SHUTDOWN = 2
51
40
  """
52
41
  Engin has performed shutdown
53
42
  """
@@ -113,16 +102,15 @@ class Engin:
113
102
  *options: an instance of Provide, Supply, Invoke, Entrypoint or a Block.
114
103
  """
115
104
  self._state = _EnginState.IDLE
116
- self._stop_requested_event = ShutdownSwitch()
105
+ self._start_complete_event = Event()
106
+ self._stop_requested_event = Event()
117
107
  self._stop_complete_event = Event()
118
- self._exit_stack: AsyncExitStack = AsyncExitStack()
119
- self._shutdown_task: Task | None = None
120
- self._run_task: Task | None = None
108
+ self._exit_stack = AsyncExitStack()
121
109
  self._assembler = Assembler([])
110
+ self._async_context_run_task: asyncio.Task | None = None
122
111
 
123
112
  self._providers: dict[TypeId, Provide] = {
124
113
  TypeId.from_type(Assembler): Supply(self._assembler),
125
- TypeId.from_type(ShutdownSwitch): Supply(self._stop_requested_event),
126
114
  }
127
115
  self._multiproviders: dict[TypeId, list[Provide]] = defaultdict(list)
128
116
  self._invocations: list[Invoke] = []
@@ -147,31 +135,6 @@ class Engin:
147
135
  The engin will run until it is stopped via an external signal (i.e. SIGTERM or
148
136
  SIGINT), the `stop` method is called on the engin, or a lifecycle task errors.
149
137
  """
150
- await self.start()
151
-
152
- # engin failed to start, so exit early
153
- if self._state != _EnginState.STARTED:
154
- return
155
-
156
- self._state = _EnginState.RUNNING
157
-
158
- async with create_task_group() as tg:
159
- tg.start_soon(_stop_engin_on_signal, self._stop_requested_event)
160
- try:
161
- await self._stop_requested_event.wait()
162
- await self._shutdown()
163
- except get_cancelled_exc_class():
164
- with CancelScope(shield=True):
165
- await self._shutdown()
166
-
167
- async def start(self) -> None:
168
- """
169
- Start the engin.
170
-
171
- This is an alternative to calling `run`. This method waits for the startup
172
- lifecycle to complete and then returns. The caller is then responsible for
173
- calling `stop`.
174
- """
175
138
  if self._state != _EnginState.IDLE:
176
139
  raise EnginError("Engin is not idle, unable to start")
177
140
 
@@ -189,10 +152,6 @@ class Engin:
189
152
  return
190
153
 
191
154
  lifecycle = await self._assembler.build(Lifecycle)
192
- supervisor = await self._assembler.build(Supervisor)
193
-
194
- if not supervisor.empty:
195
- lifecycle.append(supervisor)
196
155
 
197
156
  try:
198
157
  for hook in lifecycle.list():
@@ -206,8 +165,30 @@ class Engin:
206
165
  await self._shutdown()
207
166
  return
208
167
 
168
+ supervisor = await self._assembler.build(Supervisor)
169
+
209
170
  LOG.info("startup complete")
210
- self._state = _EnginState.STARTED
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()
184
+
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()
211
192
 
212
193
  async def stop(self) -> None:
213
194
  """
@@ -217,18 +198,25 @@ class Engin:
217
198
  Note this method can be safely called at any point, even before the engin is
218
199
  started.
219
200
  """
220
- # If the Engin was ran via `start()` perform shutdown directly
221
- if self._state == _EnginState.STARTED:
222
- await self._shutdown()
223
- # If the Engin was ran via `run()` notify via event
224
- elif self._state == _EnginState.RUNNING:
225
- self._stop_requested_event.set()
226
- await self._stop_complete_event.wait()
201
+ self._stop_requested_event.set()
202
+ await self._stop_complete_event.wait()
227
203
 
228
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
+ """
229
211
  grapher = DependencyGrapher({**self._providers, **self._multiproviders})
230
212
  return grapher.resolve(self._invocations)
231
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
+
232
220
  async def _shutdown(self) -> None:
233
221
  LOG.info("stopping engin")
234
222
  await self._exit_stack.aclose()
@@ -241,32 +229,29 @@ async def _stop_engin_on_signal(stop_requested_event: Event) -> None:
241
229
  """
242
230
  A task that waits for a stop signal (SIGINT/SIGTERM) and notifies the given event.
243
231
  """
244
- try:
245
- # try to gracefully handle sigint/sigterm
246
- if not _OS_IS_WINDOWS:
247
- loop = asyncio.get_running_loop()
248
- for signame in (signal.SIGINT, signal.SIGTERM):
249
- loop.add_signal_handler(signame, stop_requested_event.set)
250
-
251
- await stop_requested_event.wait()
252
- else:
253
- should_stop = False
254
-
255
- # windows does not support signal_handlers, so this is the workaround
256
- def ctrlc_handler(sig: int, frame: FrameType | None) -> None:
257
- nonlocal should_stop
258
- if should_stop:
259
- raise KeyboardInterrupt("Forced keyboard interrupt")
260
- should_stop = True
261
-
262
- signal.signal(signal.SIGINT, ctrlc_handler)
263
-
264
- while not should_stop:
265
- # In case engin is stopped via external `stop` call.
266
- if stop_requested_event.is_set():
267
- return
268
- await asyncio.sleep(0.1)
269
-
270
- stop_requested_event.set()
271
- except asyncio.CancelledError:
272
- 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, shutdown: ShutdownSwitch) -> None:
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
- def _handler(_: BaseExceptionGroup) -> None:
118
- self._shutdown.set()
119
-
120
- self._exit_stack = AsyncExitStack()
121
- await self._exit_stack.__aenter__()
122
- self._exit_stack.enter_context(catch({Exception: _handler}))
123
- self._task_group = await self._exit_stack.enter_async_context(
124
- anyio.create_task_group()
125
- )
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 not self._tasks:
138
- return
139
-
140
- if self._exit_stack:
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
+ [![codecov](https://codecov.io/gh/invokermain/engin/graph/badge.svg?token=4PJOIMV6IB)](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=v5OWwQoxTtqh2sB2E5iSMg3gATJoXKOuJLq28aZX6C8,642
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=XkGUG3D7z8X31rZhbu02_NFhFwSKB4JPSoma5jv6nq4,9135
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/_shutdown.py,sha256=G85Cz-wG06ZxQz6QVPcpIcNaGY44aZp6SL8H9J4YvfU,61
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.1.0a2.dist-info/METADATA,sha256=eg-rPpaGOV1MDNenGqSGCldPlTHh2yZ3CcZ0b5agakc,2408
24
- engin-0.1.0a2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- engin-0.1.0a2.dist-info/entry_points.txt,sha256=sW247zZUMxm0b5UKYvPuqQQljYDtU-j2zK3cu7gHwM0,41
26
- engin-0.1.0a2.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
27
- engin-0.1.0a2.dist-info/RECORD,,
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
@@ -1,4 +0,0 @@
1
- from asyncio import Event
2
-
3
-
4
- class ShutdownSwitch(Event): ...
@@ -1,73 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: engin
3
- Version: 0.1.0a2
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
- [![codecov](https://codecov.io/gh/invokermain/engin/graph/badge.svg?token=4PJOIMV6IB)](https://codecov.io/gh/invokermain/engin)
20
-
21
- # Engin 🏎️
22
-
23
- Engin is a lightweight 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
-