engin 0.1a1__py3-none-any.whl → 0.1.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
engin/__init__.py CHANGED
@@ -4,7 +4,6 @@ from engin._dependency import Entrypoint, Invoke, Provide, Supply
4
4
  from engin._engin import Engin
5
5
  from engin._lifecycle import Lifecycle
6
6
  from engin._option import Option
7
- from engin._shutdown import ShutdownSwitch
8
7
  from engin._supervisor import OnException, Supervisor
9
8
  from engin._type_utils import TypeId
10
9
 
@@ -18,7 +17,6 @@ __all__ = [
18
17
  "OnException",
19
18
  "Option",
20
19
  "Provide",
21
- "ShutdownSwitch",
22
20
  "Supervisor",
23
21
  "Supply",
24
22
  "TypeId",
engin/_engin.py CHANGED
@@ -2,28 +2,46 @@ import asyncio
2
2
  import logging
3
3
  import os
4
4
  import signal
5
- from asyncio import Event, Task
5
+ from asyncio import Event
6
6
  from collections import defaultdict
7
7
  from contextlib import AsyncExitStack
8
+ from enum import Enum
8
9
  from itertools import chain
9
10
  from types import FrameType
10
11
  from typing import ClassVar
11
12
 
12
- from anyio import CancelScope, create_task_group, get_cancelled_exc_class
13
+ from anyio import create_task_group, get_cancelled_exc_class
13
14
 
14
15
  from engin._assembler import AssembledDependency, Assembler
15
16
  from engin._dependency import Invoke, Provide, Supply
16
17
  from engin._graph import DependencyGrapher, Node
17
18
  from engin._lifecycle import Lifecycle
18
19
  from engin._option import Option
19
- from engin._shutdown import ShutdownSwitch
20
20
  from engin._supervisor import Supervisor
21
21
  from engin._type_utils import TypeId
22
+ from engin.exceptions import EnginError
22
23
 
23
24
  _OS_IS_WINDOWS = os.name == "nt"
24
25
  LOG = logging.getLogger("engin")
25
26
 
26
27
 
28
+ class _EnginState(Enum):
29
+ IDLE = 0
30
+ """
31
+ Engin is not yet started.
32
+ """
33
+
34
+ RUNNING = 1
35
+ """
36
+ Engin is currently running.
37
+ """
38
+
39
+ SHUTDOWN = 2
40
+ """
41
+ Engin has performed shutdown
42
+ """
43
+
44
+
27
45
  class Engin:
28
46
  """
29
47
  The Engin is a modular application defined by a collection of options.
@@ -83,16 +101,16 @@ class Engin:
83
101
  Args:
84
102
  *options: an instance of Provide, Supply, Invoke, Entrypoint or a Block.
85
103
  """
86
- self._stop_requested_event = ShutdownSwitch()
104
+ self._state = _EnginState.IDLE
105
+ self._start_complete_event = Event()
106
+ self._stop_requested_event = Event()
87
107
  self._stop_complete_event = Event()
88
- self._exit_stack: AsyncExitStack = AsyncExitStack()
89
- self._shutdown_task: Task | None = None
90
- self._run_task: Task | None = None
108
+ self._exit_stack = AsyncExitStack()
91
109
  self._assembler = Assembler([])
110
+ self._async_context_run_task: asyncio.Task | None = None
92
111
 
93
112
  self._providers: dict[TypeId, Provide] = {
94
113
  TypeId.from_type(Assembler): Supply(self._assembler),
95
- TypeId.from_type(ShutdownSwitch): Supply(self._stop_requested_event),
96
114
  }
97
115
  self._multiproviders: dict[TypeId, list[Provide]] = defaultdict(list)
98
116
  self._invocations: list[Invoke] = []
@@ -117,26 +135,9 @@ class Engin:
117
135
  The engin will run until it is stopped via an external signal (i.e. SIGTERM or
118
136
  SIGINT), the `stop` method is called on the engin, or a lifecycle task errors.
119
137
  """
120
- await self.start()
121
- if self._shutdown_task:
122
- self._shutdown_task.cancel("redundant")
123
- async with create_task_group() as tg:
124
- tg.start_soon(_stop_engin_on_signal, self._stop_requested_event)
125
- try:
126
- await self._stop_requested_event.wait()
127
- await self._shutdown()
128
- except get_cancelled_exc_class():
129
- with CancelScope(shield=True):
130
- await self._shutdown()
131
-
132
- async def start(self) -> None:
133
- """
134
- Start the engin.
138
+ if self._state != _EnginState.IDLE:
139
+ raise EnginError("Engin is not idle, unable to start")
135
140
 
136
- This is an alternative to calling `run`. This method waits for the startup
137
- lifecycle to complete and then returns. The caller is then responsible for
138
- calling `stop`.
139
- """
140
141
  LOG.info("starting engin")
141
142
  assembled_invocations: list[AssembledDependency] = [
142
143
  await self._assembler.assemble(invocation) for invocation in self._invocations
@@ -151,10 +152,6 @@ class Engin:
151
152
  return
152
153
 
153
154
  lifecycle = await self._assembler.build(Lifecycle)
154
- supervisor = await self._assembler.build(Supervisor)
155
-
156
- if not supervisor.empty:
157
- lifecycle.append(supervisor)
158
155
 
159
156
  try:
160
157
  for hook in lifecycle.list():
@@ -168,9 +165,30 @@ class Engin:
168
165
  await self._shutdown()
169
166
  return
170
167
 
168
+ supervisor = await self._assembler.build(Supervisor)
169
+
171
170
  LOG.info("startup complete")
171
+ self._state = _EnginState.RUNNING
172
+ self._start_complete_event.set()
173
+
174
+ async with create_task_group() as tg:
175
+ tg.start_soon(_stop_engin_on_signal, self._stop_requested_event)
176
+
177
+ try:
178
+ async with supervisor:
179
+ await self._stop_requested_event.wait()
180
+ except get_cancelled_exc_class():
181
+ pass
182
+ tg.cancel_scope.cancel()
183
+ await self._shutdown()
172
184
 
173
- self._shutdown_task = asyncio.create_task(self._shutdown_when_stopped())
185
+ async def start(self) -> None:
186
+ """
187
+ Starts the engin in the background. This method will wait until the engin is fully
188
+ started to return so it is safe to use immediately after.
189
+ """
190
+ self._async_context_run_task = asyncio.create_task(self.run())
191
+ await self._start_complete_event.wait()
174
192
 
175
193
  async def stop(self) -> None:
176
194
  """
@@ -181,55 +199,59 @@ class Engin:
181
199
  started.
182
200
  """
183
201
  self._stop_requested_event.set()
184
- if self._shutdown_task is None:
185
- return
186
202
  await self._stop_complete_event.wait()
187
203
 
188
204
  def graph(self) -> list[Node]:
205
+ """
206
+ Creates a graph representation of the engin's dependencies which can be used for
207
+ introspection or visualisations.
208
+
209
+ Returns: a list of Node objects.
210
+ """
189
211
  grapher = DependencyGrapher({**self._providers, **self._multiproviders})
190
212
  return grapher.resolve(self._invocations)
191
213
 
214
+ def is_running(self) -> bool:
215
+ return self._state == _EnginState.RUNNING
216
+
217
+ def is_stopped(self) -> bool:
218
+ return self._state == _EnginState.SHUTDOWN
219
+
192
220
  async def _shutdown(self) -> None:
193
221
  LOG.info("stopping engin")
194
222
  await self._exit_stack.aclose()
195
223
  self._stop_complete_event.set()
196
224
  LOG.info("shutdown complete")
197
-
198
- async def _shutdown_when_stopped(self) -> None:
199
- await self._stop_requested_event.wait()
200
- await self._shutdown()
225
+ self._state = _EnginState.SHUTDOWN
201
226
 
202
227
 
203
228
  async def _stop_engin_on_signal(stop_requested_event: Event) -> None:
204
229
  """
205
230
  A task that waits for a stop signal (SIGINT/SIGTERM) and notifies the given event.
206
231
  """
207
- try:
208
- # try to gracefully handle sigint/sigterm
209
- if not _OS_IS_WINDOWS:
210
- loop = asyncio.get_running_loop()
211
- for signame in (signal.SIGINT, signal.SIGTERM):
212
- loop.add_signal_handler(signame, stop_requested_event.set)
213
-
214
- await stop_requested_event.wait()
215
- else:
216
- should_stop = False
217
-
218
- # windows does not support signal_handlers, so this is the workaround
219
- def ctrlc_handler(sig: int, frame: FrameType | None) -> None:
220
- nonlocal should_stop
221
- if should_stop:
222
- raise KeyboardInterrupt("Forced keyboard interrupt")
223
- should_stop = True
224
-
225
- signal.signal(signal.SIGINT, ctrlc_handler)
226
-
227
- while not should_stop:
228
- # In case engin is stopped via external `stop` call.
229
- if stop_requested_event.is_set():
230
- return
231
- await asyncio.sleep(0.1)
232
-
233
- stop_requested_event.set()
234
- except asyncio.CancelledError:
235
- pass
232
+ # try to gracefully handle sigint/sigterm
233
+ if not _OS_IS_WINDOWS:
234
+ loop = asyncio.get_running_loop()
235
+ for signame in (signal.SIGINT, signal.SIGTERM):
236
+ loop.add_signal_handler(signame, stop_requested_event.set)
237
+
238
+ await stop_requested_event.wait()
239
+ else:
240
+ should_stop = False
241
+
242
+ # windows does not support signal_handlers, so this is the workaround
243
+ def ctrlc_handler(sig: int, frame: FrameType | None) -> None:
244
+ nonlocal should_stop
245
+ if should_stop:
246
+ raise KeyboardInterrupt("Forced keyboard interrupt")
247
+ should_stop = True
248
+
249
+ signal.signal(signal.SIGINT, ctrlc_handler)
250
+
251
+ while not should_stop:
252
+ # In case engin is stopped via external `stop` call.
253
+ if stop_requested_event.is_set():
254
+ return
255
+ await asyncio.sleep(0.1)
256
+
257
+ stop_requested_event.set()
engin/_supervisor.py CHANGED
@@ -2,17 +2,13 @@ import inspect
2
2
  import logging
3
3
  import typing
4
4
  from collections.abc import Awaitable, Callable
5
- from contextlib import AsyncExitStack
6
5
  from dataclasses import dataclass
7
6
  from enum import Enum
8
7
  from types import TracebackType
9
- from typing import TypeAlias
8
+ from typing import TypeAlias, assert_never
10
9
 
11
10
  import anyio
12
11
  from anyio import get_cancelled_exc_class
13
- from exceptiongroup import BaseExceptionGroup, catch
14
-
15
- from engin._shutdown import ShutdownSwitch
16
12
 
17
13
  if typing.TYPE_CHECKING:
18
14
  from anyio.abc import TaskGroup
@@ -72,13 +68,12 @@ class _SupervisorTask:
72
68
  if self.on_exception == OnException.IGNORE:
73
69
  self.complete = True
74
70
  return
75
-
76
71
  if self.on_exception == OnException.RETRY:
77
72
  continue
78
-
79
73
  if self.on_exception == OnException.SHUTDOWN:
80
74
  self.complete = True
81
- raise
75
+ raise get_cancelled_exc_class() from None
76
+ assert_never(self.on_exception)
82
77
 
83
78
  @property
84
79
  def name(self) -> str:
@@ -93,12 +88,8 @@ class _SupervisorTask:
93
88
 
94
89
 
95
90
  class Supervisor:
96
- def __init__(self, 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=UGS-dIMGcdhRWvsQBuwIH6I7XFxYMlgSZeuvl3ur3Eo,8417
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.1a1.dist-info/METADATA,sha256=f5atVNZHxnofJLnnf2upFC_MYjAJv3lVkQIxe62z1TA,2410
24
- engin-0.1a1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- engin-0.1a1.dist-info/entry_points.txt,sha256=sW247zZUMxm0b5UKYvPuqQQljYDtU-j2zK3cu7gHwM0,41
26
- engin-0.1a1.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
27
- engin-0.1a1.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.1a1
4
- Summary: An async-first modular application framework
5
- Project-URL: Homepage, https://github.com/invokermain/engin
6
- Project-URL: Documentation, https://engin.readthedocs.io/en/latest/
7
- Project-URL: Repository, https://github.com/invokermain/engin.git
8
- Project-URL: Changelog, https://github.com/invokermain/engin/blob/main/CHANGELOG.md
9
- License-Expression: MIT
10
- License-File: LICENSE
11
- Keywords: Application Framework,Dependency Injection
12
- Requires-Python: >=3.10
13
- Requires-Dist: anyio>=4
14
- Requires-Dist: exceptiongroup>=1
15
- Provides-Extra: cli
16
- Requires-Dist: typer>=0.15; extra == 'cli'
17
- Description-Content-Type: text/markdown
18
-
19
- [![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 zero-dependency application framework for modern Python.
24
-
25
- **Documentation**: https://engin.readthedocs.io/
26
-
27
- ## Features ✨
28
-
29
- - **Dependency Injection** - Engin includes a fully-featured Dependency Injection system,
30
- powered by type hints.
31
- - **Lifecycle Management** - Engin provides a simple & portable approach for attaching
32
- startup and shutdown tasks to the application's lifecycle.
33
- - **Code Reuse** - Engin's modular components, called Blocks, work great as distributed
34
- packages allowing zero boiler-plate code reuse across multiple applications. Perfect for
35
- maintaining many services across your organisation.
36
- - **Ecosystem Compatability** - Engin ships with integrations for popular frameworks that
37
- provide their own Dependency Injection, for example FastAPI, allowing you to integrate
38
- Engin into existing code bases incrementally.
39
- - **Async Native**: Engin is an async framework, meaning first class support for async
40
- dependencies. However Engin will happily run synchronous code as well.
41
-
42
- ## Installation
43
-
44
- Engin is available on PyPI, install using your favourite dependency manager:
45
-
46
- - **pip**:`pip install engin`
47
- - **poetry**: `poetry add engin`
48
- - **uv**: `uv add engin`
49
-
50
- ## Getting Started
51
-
52
- A minimal example:
53
-
54
- ```python
55
- import asyncio
56
-
57
- from httpx import AsyncClient
58
-
59
- from engin import Engin, Invoke, Provide
60
-
61
-
62
- def httpx_client() -> AsyncClient:
63
- return AsyncClient()
64
-
65
-
66
- async def main(http_client: AsyncClient) -> None:
67
- print(await http_client.get("https://httpbin.org/get"))
68
-
69
- engin = Engin(Provide(httpx_client), Invoke(main))
70
-
71
- asyncio.run(engin.run())
72
- ```
73
-
File without changes