engin 0.0.2__py3-none-any.whl → 0.0.4__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/_assembler.py CHANGED
@@ -4,7 +4,7 @@ from collections import defaultdict
4
4
  from collections.abc import Collection, Iterable
5
5
  from dataclasses import dataclass
6
6
  from inspect import BoundArguments, Signature
7
- from typing import Any, Generic, TypeVar
7
+ from typing import Any, Generic, TypeVar, cast
8
8
 
9
9
  from engin._dependency import Dependency, Provide, Supply
10
10
  from engin._exceptions import AssemblyError
@@ -113,7 +113,7 @@ class Assembler:
113
113
  async def get(self, type_: type[T]) -> T:
114
114
  type_id = type_id_of(type_)
115
115
  if type_id in self._dependencies:
116
- return self._dependencies[type_id]
116
+ return cast(T, self._dependencies[type_id])
117
117
  if type_id.multi:
118
118
  out = []
119
119
  for provider in self._multiproviders[type_id]:
engin/_block.py CHANGED
@@ -1,29 +1,29 @@
1
1
  import inspect
2
2
  from collections.abc import Iterable, Iterator
3
+ from typing import ClassVar
3
4
 
4
5
  from engin._dependency import Func, Invoke, Provide
5
6
 
6
7
 
7
8
  def provide(func: Func) -> Func:
8
- func._opt = Provide(func) # type: ignore[union-attr]
9
+ func._opt = Provide(func) # type: ignore[attr-defined]
9
10
  return func
10
11
 
11
12
 
12
13
  def invoke(func: Func) -> Func:
13
- func._opt = Invoke(func) # type: ignore[union-attr]
14
+ func._opt = Invoke(func) # type: ignore[attr-defined]
14
15
  return func
15
16
 
16
17
 
17
18
  class Block(Iterable[Provide | Invoke]):
18
- _name: str
19
- _options: list[Provide | Invoke]
19
+ options: ClassVar[list[Provide | Invoke]] = []
20
20
 
21
21
  def __init__(self, /, block_name: str | None = None) -> None:
22
- self._options: list[Provide | Invoke] = []
22
+ self._options: list[Provide | Invoke] = self.options[:]
23
23
  self._name = block_name or f"{type(self).__name__}"
24
24
  for _, method in inspect.getmembers(self):
25
25
  if opt := getattr(method, "_opt", None):
26
- if not isinstance(opt, (Provide, Invoke)):
26
+ if not isinstance(opt, Provide | Invoke):
27
27
  raise RuntimeError("Block option is not an instance of Provide or Invoke")
28
28
  opt.set_block_name(self._name)
29
29
  self._options.append(opt)
engin/_dependency.py CHANGED
@@ -1,14 +1,12 @@
1
1
  import inspect
2
2
  import typing
3
3
  from abc import ABC
4
+ from collections.abc import Awaitable, Callable
4
5
  from inspect import Parameter, Signature, isclass, iscoroutinefunction
5
6
  from typing import (
6
7
  Any,
7
- Awaitable,
8
- Callable,
9
8
  Generic,
10
9
  ParamSpec,
11
- Type,
12
10
  TypeAlias,
13
11
  TypeVar,
14
12
  cast,
@@ -19,13 +17,11 @@ from engin._type_utils import TypeId, type_id_of
19
17
 
20
18
  P = ParamSpec("P")
21
19
  T = TypeVar("T")
22
- Func: TypeAlias = (
23
- Callable[P, T] | Callable[P, Awaitable[T]] | Callable[[], T] | Callable[[], Awaitable[T]]
24
- )
20
+ Func: TypeAlias = Callable[P, T]
25
21
  _SELF = object()
26
22
 
27
23
 
28
- def _noop(*args, **kwargs) -> None: ...
24
+ def _noop(*args: Any, **kwargs: Any) -> None: ...
29
25
 
30
26
 
31
27
  class Dependency(ABC, Generic[P, T]):
@@ -66,11 +62,11 @@ class Dependency(ABC, Generic[P, T]):
66
62
  if self._is_async:
67
63
  return await cast(Awaitable[T], self._func(*args, **kwargs))
68
64
  else:
69
- return cast(T, self._func(*args, **kwargs))
65
+ return self._func(*args, **kwargs)
70
66
 
71
67
 
72
68
  class Invoke(Dependency):
73
- def __init__(self, invocation: Func[P, T], block_name: str | None = None):
69
+ def __init__(self, invocation: Func[P, T], block_name: str | None = None) -> None:
74
70
  super().__init__(func=invocation, block_name=block_name)
75
71
 
76
72
  def __str__(self) -> str:
@@ -78,7 +74,7 @@ class Invoke(Dependency):
78
74
 
79
75
 
80
76
  class Entrypoint(Invoke):
81
- def __init__(self, type_: Type[Any], *, block_name: str | None = None) -> None:
77
+ def __init__(self, type_: type[Any], *, block_name: str | None = None) -> None:
82
78
  self._type = type_
83
79
  super().__init__(invocation=_noop, block_name=block_name)
84
80
 
@@ -99,7 +95,7 @@ class Entrypoint(Invoke):
99
95
 
100
96
 
101
97
  class Provide(Dependency[Any, T]):
102
- def __init__(self, builder: Func[P, T], block_name: str | None = None):
98
+ def __init__(self, builder: Func[P, T], block_name: str | None = None) -> None:
103
99
  super().__init__(func=builder, block_name=block_name)
104
100
  self._is_multi = typing.get_origin(self.return_type) is list
105
101
 
@@ -111,14 +107,16 @@ class Provide(Dependency[Any, T]):
111
107
  )
112
108
 
113
109
  @property
114
- def return_type(self) -> Type[T]:
110
+ def return_type(self) -> type[T]:
115
111
  if isclass(self._func):
116
112
  return_type = self._func # __init__ returns self
117
113
  else:
118
114
  try:
119
115
  return_type = get_type_hints(self._func)["return"]
120
- except KeyError:
121
- raise RuntimeError(f"Dependency '{self.name}' requires a return typehint")
116
+ except KeyError as err:
117
+ raise RuntimeError(
118
+ f"Dependency '{self.name}' requires a return typehint"
119
+ ) from err
122
120
 
123
121
  return return_type
124
122
 
@@ -140,7 +138,7 @@ class Provide(Dependency[Any, T]):
140
138
  class Supply(Provide, Generic[T]):
141
139
  def __init__(
142
140
  self, value: T, *, type_hint: type | None = None, block_name: str | None = None
143
- ):
141
+ ) -> None:
144
142
  self._value = value
145
143
  self._type_hint = type_hint
146
144
  if self._type_hint is not None:
@@ -148,7 +146,7 @@ class Supply(Provide, Generic[T]):
148
146
  super().__init__(builder=self._get_val, block_name=block_name)
149
147
 
150
148
  @property
151
- def return_type(self) -> Type[T]:
149
+ def return_type(self) -> type[T]:
152
150
  if self._type_hint is not None:
153
151
  return self._type_hint
154
152
  if isinstance(self._value, list):
engin/_engin.py CHANGED
@@ -1,7 +1,12 @@
1
+ import asyncio
1
2
  import logging
2
- from asyncio import Event
3
+ import os
4
+ import signal
5
+ from asyncio import Event, Task
3
6
  from collections.abc import Iterable
7
+ from contextlib import AsyncExitStack
4
8
  from itertools import chain
9
+ from types import FrameType
5
10
  from typing import ClassVar, TypeAlias
6
11
 
7
12
  from engin import Entrypoint
@@ -16,14 +21,47 @@ LOG = logging.getLogger("engin")
16
21
  Option: TypeAlias = Invoke | Provide | Supply | Block
17
22
  _Opt: TypeAlias = Invoke | Provide | Supply
18
23
 
24
+ _OS_IS_WINDOWS = os.name == "nt"
25
+
19
26
 
20
27
  class Engin:
28
+ """
29
+ The Engin class runs your application. It assembles the required dependencies, invokes
30
+ any invocations and manages your application's lifecycle.
31
+
32
+ Examples:
33
+ ```python
34
+ import asyncio
35
+
36
+ from httpx import AsyncClient
37
+
38
+ from engin import Engin, Invoke, Provide
39
+
40
+
41
+ def httpx_client() -> AsyncClient:
42
+ return AsyncClient()
43
+
44
+
45
+ async def main(http_client: AsyncClient) -> None:
46
+ print(await http_client.get("https://httpbin.org/get"))
47
+
48
+ engin = Engin(Provide(httpx_client), Invoke(main))
49
+
50
+ asyncio.run(engin.run())
51
+ ```
52
+ """
53
+
21
54
  _LIB_OPTIONS: ClassVar[list[Option]] = [Provide(Lifecycle)]
22
55
 
23
56
  def __init__(self, *options: Option) -> None:
24
57
  self._providers: dict[TypeId, Provide] = {TypeId.from_type(Engin): Provide(self._self)}
25
58
  self._invokables: list[Invoke] = []
26
- self._stop_event = Event()
59
+
60
+ self._stop_requested_event = Event()
61
+ self._stop_complete_event = Event()
62
+ self._exit_stack: AsyncExitStack = AsyncExitStack()
63
+ self._shutdown_task: Task | None = None
64
+ self._run_task: Task | None = None
27
65
 
28
66
  self._destruct_options(chain(self._LIB_OPTIONS, options))
29
67
  self._assembler = Assembler(self._providers.values())
@@ -32,33 +70,69 @@ class Engin:
32
70
  def assembler(self) -> Assembler:
33
71
  return self._assembler
34
72
 
35
- async def run(self):
73
+ async def run(self) -> None:
74
+ """
75
+ Run the Engin and wait for it to be stopped via an external signal or by calling
76
+ the `stop` method.
77
+ """
36
78
  await self.start()
37
-
38
- # wait till stop signal recieved
39
- await self._stop_event.wait()
79
+ self._run_task = asyncio.create_task(_raise_on_stop(self._stop_requested_event))
80
+ await self._stop_requested_event.wait()
81
+ await self._shutdown()
40
82
 
41
83
  async def start(self) -> None:
84
+ """
85
+ Starts the engin, this method waits for the shutdown lifecycle to complete.
86
+ """
42
87
  LOG.info("starting engin")
43
88
  assembled_invocations: list[AssembledDependency] = [
44
89
  await self._assembler.assemble(invocation) for invocation in self._invokables
45
90
  ]
91
+
46
92
  for invocation in assembled_invocations:
47
- await invocation()
93
+ try:
94
+ await invocation()
95
+ except Exception as err:
96
+ name = invocation.dependency.name
97
+ LOG.error(f"invocation '{name}' errored, exiting", exc_info=err)
98
+ return
48
99
 
49
100
  lifecycle = await self._assembler.get(Lifecycle)
50
- await lifecycle.startup()
51
- self._stop_event = Event()
101
+
102
+ try:
103
+ for hook in lifecycle.list():
104
+ await self._exit_stack.enter_async_context(hook)
105
+ except Exception as err:
106
+ LOG.error("lifecycle startup error, exiting", exc_info=err)
107
+ await self._exit_stack.aclose()
108
+ return
109
+
52
110
  LOG.info("startup complete")
53
111
 
54
- async def stop(self) -> None:
55
- self._stop_event.set()
112
+ self._shutdown_task = asyncio.create_task(self._shutdown_when_stopped())
56
113
 
57
- def _destruct_options(self, options: Iterable[Option]):
114
+ async def stop(self) -> None:
115
+ """
116
+ Stops the engin, this method waits for the shutdown lifecycle to complete.
117
+ """
118
+ self._stop_requested_event.set()
119
+ await self._stop_complete_event.wait()
120
+
121
+ async def _shutdown(self) -> None:
122
+ LOG.info("stopping engin")
123
+ await self._exit_stack.aclose()
124
+ self._stop_complete_event.set()
125
+ LOG.info("shutdown complete")
126
+
127
+ async def _shutdown_when_stopped(self) -> None:
128
+ await self._stop_requested_event.wait()
129
+ await self._shutdown()
130
+
131
+ def _destruct_options(self, options: Iterable[Option]) -> None:
58
132
  for opt in options:
59
133
  if isinstance(opt, Block):
60
134
  self._destruct_options(opt)
61
- if isinstance(opt, (Provide, Supply)):
135
+ if isinstance(opt, Provide | Supply):
62
136
  existing = self._providers.get(opt.return_type_id)
63
137
  self._log_option(opt, overwrites=existing)
64
138
  self._providers[opt.return_type_id] = opt
@@ -86,3 +160,44 @@ class Engin:
86
160
 
87
161
  def _self(self) -> "Engin":
88
162
  return self
163
+
164
+
165
+ class _StopRequested(RuntimeError):
166
+ pass
167
+
168
+
169
+ async def _raise_on_stop(stop_requested_event: Event) -> None:
170
+ """
171
+ This method is based off of the Temporal Python SDK's Worker class:
172
+ https://github.com/temporalio/sdk-python/blob/main/temporalio/worker/_worker.py#L488
173
+ """
174
+ try:
175
+ # try to gracefully handle sigint/sigterm
176
+ if not _OS_IS_WINDOWS:
177
+ loop = asyncio.get_running_loop()
178
+ for signame in (signal.SIGINT, signal.SIGTERM):
179
+ loop.add_signal_handler(signame, stop_requested_event.set)
180
+
181
+ await stop_requested_event.wait()
182
+ raise _StopRequested()
183
+ else:
184
+ should_stop = False
185
+
186
+ # windows does not support signal_handlers, so this is the workaround
187
+ def ctrlc_handler(sig: int, frame: FrameType | None) -> None:
188
+ nonlocal should_stop
189
+ if should_stop:
190
+ raise KeyboardInterrupt("Forced keyboard interrupt")
191
+ should_stop = True
192
+
193
+ signal.signal(signal.SIGINT, ctrlc_handler)
194
+
195
+ while not should_stop:
196
+ # In case engin is stopped via external `stop` call.
197
+ if stop_requested_event.is_set():
198
+ return
199
+ await asyncio.sleep(0.1)
200
+
201
+ stop_requested_event.set()
202
+ except asyncio.CancelledError:
203
+ pass
engin/_lifecycle.py CHANGED
@@ -1,21 +1,38 @@
1
- from collections.abc import Callable
2
- from contextlib import AbstractAsyncContextManager, AsyncExitStack
1
+ import logging
2
+ from contextlib import AbstractAsyncContextManager
3
+ from types import TracebackType
4
+
5
+ LOG = logging.getLogger("engin")
3
6
 
4
7
 
5
8
  class Lifecycle:
6
9
  def __init__(self) -> None:
7
- self._on_startup: list[Callable] = []
8
- self._on_shutdown: list[Callable] = []
9
10
  self._context_managers: list[AbstractAsyncContextManager] = []
10
- self._stack: AsyncExitStack = AsyncExitStack()
11
11
 
12
- def register_context(self, cm: AbstractAsyncContextManager):
13
- self._context_managers.append(cm)
12
+ def append(self, cm: AbstractAsyncContextManager) -> None:
13
+ suppressed_cm = _AExitSuppressingAsyncContextManager(cm)
14
+ self._context_managers.append(suppressed_cm)
15
+
16
+ def list(self) -> list[AbstractAsyncContextManager]:
17
+ return self._context_managers[:]
18
+
19
+
20
+ class _AExitSuppressingAsyncContextManager(AbstractAsyncContextManager):
21
+ def __init__(self, cm: AbstractAsyncContextManager) -> None:
22
+ self._cm = cm
14
23
 
15
- async def startup(self) -> None:
16
- self._stack = AsyncExitStack()
17
- for cm in self._context_managers:
18
- await self._stack.enter_async_context(cm)
24
+ async def __aenter__(self) -> AbstractAsyncContextManager:
25
+ await self._cm.__aenter__()
26
+ return self._cm
19
27
 
20
- async def shutdown(self) -> None:
21
- await self._stack.aclose()
28
+ async def __aexit__(
29
+ self,
30
+ exc_type: type[BaseException] | None,
31
+ exc_value: BaseException | None,
32
+ traceback: TracebackType | None,
33
+ /,
34
+ ) -> None:
35
+ try:
36
+ await self._cm.__aexit__(exc_type, exc_value, traceback)
37
+ except Exception as err:
38
+ LOG.error("error in lifecycle hook stop, ignoring...", exc_info=err)
engin/ext/asgi.py CHANGED
@@ -1,16 +1,16 @@
1
1
  import traceback
2
- import typing
3
- from typing import ClassVar, Protocol, TypeAlias
2
+ from collections.abc import Awaitable, Callable, MutableMapping
3
+ from typing import Any, ClassVar, Protocol, TypeAlias
4
4
 
5
5
  from engin import Engin, Option
6
6
 
7
7
  __all__ = ["ASGIEngin", "ASGIType"]
8
8
 
9
9
 
10
- _Scope: TypeAlias = typing.MutableMapping[str, typing.Any]
11
- _Message: TypeAlias = typing.MutableMapping[str, typing.Any]
12
- _Receive: TypeAlias = typing.Callable[[], typing.Awaitable[_Message]]
13
- _Send: TypeAlias = typing.Callable[[_Message], typing.Awaitable[None]]
10
+ _Scope: TypeAlias = MutableMapping[str, Any]
11
+ _Message: TypeAlias = MutableMapping[str, Any]
12
+ _Receive: TypeAlias = Callable[[], Awaitable[_Message]]
13
+ _Send: TypeAlias = Callable[[_Message], Awaitable[None]]
14
14
 
15
15
 
16
16
  class ASGIType(Protocol):
@@ -54,5 +54,5 @@ class _Rereceive:
54
54
  def __init__(self, message: _Message) -> None:
55
55
  self._message = message
56
56
 
57
- async def __call__(self, *args, **kwargs) -> _Message:
57
+ async def __call__(self, *args: Any, **kwargs: Any) -> _Message:
58
58
  return self._message
engin/ext/fastapi.py CHANGED
@@ -1,4 +1,3 @@
1
- import typing
2
1
  from typing import ClassVar, TypeVar
3
2
 
4
3
  from engin import Engin, Invoke, Option
@@ -8,13 +7,10 @@ try:
8
7
  from fastapi import FastAPI
9
8
  from fastapi.params import Depends
10
9
  from starlette.requests import HTTPConnection
11
- except ImportError:
12
- raise ImportError("fastapi must be installed to use the corresponding extension")
13
-
14
-
15
- if typing.TYPE_CHECKING:
16
- from fastapi import FastAPI
17
- from fastapi.params import Depends
10
+ except ImportError as err:
11
+ raise ImportError(
12
+ "fastapi package must be installed to use the fastapi extension"
13
+ ) from err
18
14
 
19
15
  __all__ = ["FastAPIEngin", "Inject"]
20
16
 
@@ -27,7 +23,7 @@ def _attach_engin(
27
23
 
28
24
 
29
25
  class FastAPIEngin(ASGIEngin):
30
- _LIB_OPTIONS: ClassVar[list[Option]] = [*ASGIEngin._LIB_OPTIONS, Invoke(_attach_engin)] # type: ignore[arg-type]
26
+ _LIB_OPTIONS: ClassVar[list[Option]] = [*ASGIEngin._LIB_OPTIONS, Invoke(_attach_engin)]
31
27
  _asgi_type = FastAPI
32
28
 
33
29
 
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.4
2
+ Name: engin
3
+ Version: 0.0.4
4
+ Summary: An async-first modular application framework
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+
9
+ # Engin 🏎️
10
+
11
+ Engin is a zero-dependency application framework for modern Python.
12
+
13
+ **Documentation**: https://engin.readthedocs.io/
14
+
15
+ ## Features ✨
16
+
17
+ - **Dependency Injection** - Engin includes a fully-featured Dependency Injection system,
18
+ powered by type hints.
19
+ - **Lifecycle Management** - Engin provides a simple & portable approach for attaching
20
+ startup and shutdown tasks to the application's lifecycle.
21
+ - **Code Reuse** - Engin's modular components, called Blocks, work great as distributed
22
+ packages allowing zero boiler-plate code reuse across multiple applications. Perfect for
23
+ maintaining many services across your organisation.
24
+ - **Ecosystem Compatability** - Engin ships with integrations for popular frameworks that
25
+ provide their own Dependency Injection, for example FastAPI, allowing you to integrate
26
+ Engin into existing code bases incrementally.
27
+ - **Async Native**: Engin is an async framework, meaning first class support for async
28
+ dependencies. However Engin will happily run synchronous code as well.
29
+
30
+ ## Installation
31
+
32
+ Engin is available on PyPI, install using your favourite dependency manager:
33
+
34
+ - **pip**:`pip install engin`
35
+ - **poetry**: `poetry add engin`
36
+ - **uv**: `uv add engin`
37
+
38
+ ## Getting Started
39
+
40
+ A minimal example:
41
+
42
+ ```python
43
+ import asyncio
44
+
45
+ from httpx import AsyncClient
46
+
47
+ from engin import Engin, Invoke, Provide
48
+
49
+
50
+ def httpx_client() -> AsyncClient:
51
+ return AsyncClient()
52
+
53
+
54
+ async def main(http_client: AsyncClient) -> None:
55
+ print(await http_client.get("https://httpbin.org/get"))
56
+
57
+ engin = Engin(Provide(httpx_client), Invoke(main))
58
+
59
+ asyncio.run(engin.run())
60
+ ```
61
+
@@ -0,0 +1,16 @@
1
+ engin/__init__.py,sha256=AIE9wnwvfXwS1mwp6Sa9xtatBYyYzhWa36GKfAkEx_M,508
2
+ engin/_assembler.py,sha256=UY97tXdHgvRi_iph_kolXZ8YTrxRjHKoIc4tAhqYOcw,5258
3
+ engin/_block.py,sha256=Sdl9o3ZmmplZgB6pWFCbKjMemWzJXUso63p_bUg6BtM,1152
4
+ engin/_dependency.py,sha256=G0ooXhYVauWiZ2keisot8iQXjGwdAwyvpwMtE1rjQzw,4754
5
+ engin/_engin.py,sha256=tkW-QP-CNwKH9tr3xIZh-uvBKgyn9MrSvXjn3xLp7VU,6833
6
+ engin/_exceptions.py,sha256=nkzTqxrW5nkcNgFDGoZ2TBtnHtO2RLk0qghM5LNAEmU,542
7
+ engin/_lifecycle.py,sha256=LrDTrxEW6Mo0U4Nol9Rq-49Bi0UCa-7pbSCYjuSmG50,1211
8
+ engin/_type_utils.py,sha256=naEk-lknC3Fdsd4jiP4YZAxjX3KXZN0MhFde9EV-Fmo,1835
9
+ engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ engin/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ engin/ext/asgi.py,sha256=6vuC4zIhsvAdmwRn2I6uuUWPYfqobox1dv7skg2OWwE,1940
12
+ engin/ext/fastapi.py,sha256=CH2Zi7Oh_Va0TJGx05e7_LqAiCsoI1qcu0Z59_rgfRk,899
13
+ engin-0.0.4.dist-info/METADATA,sha256=CMTjhIBzTSn-PYAAnV6wAVpr-32GqV_BVUg-uQgsKVY,1806
14
+ engin-0.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ engin-0.0.4.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
16
+ engin-0.0.4.dist-info/RECORD,,
@@ -1,56 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: engin
3
- Version: 0.0.2
4
- Summary: An async-first modular application framework
5
- License-File: LICENSE
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
-
9
- # Engin 🏎️
10
-
11
- Engin is a zero-dependency application framework for modern Python.
12
-
13
- ## Features ✨
14
-
15
- - **Lightweight**: Engin has no dependencies.
16
- - **Async First**: Engin provides first-class support for applications.
17
- - **Dependency Injection**: Engin promotes a modular decoupled architecture in your application.
18
- - **Lifecycle Management**: Engin provides an simple, portable approach for implememting
19
- startup and shutdown tasks.
20
- - **Ecosystem Compatability**: seamlessly integrate with frameworks such as FastAPI without
21
- having to migrate your dependencies.
22
- - **Code Reuse**: Engin's modular components work great as packages and distributions. Allowing
23
- low boiler-plate code reuse within your Organisation.
24
-
25
- ## Installation
26
-
27
- Engin is available on PyPI, install using your favourite dependency manager:
28
-
29
- - **pip**:`pip install engin`
30
- - **poetry**: `poetry add engin`
31
- - **uv**: `uv add engin`
32
-
33
- ## Getting Started
34
-
35
- A minimal example:
36
-
37
- ```python
38
- import asyncio
39
-
40
- from httpx import AsyncClient
41
-
42
- from engin import Engin, Invoke, Provide
43
-
44
-
45
- def httpx_client() -> AsyncClient:
46
- return AsyncClient()
47
-
48
-
49
- async def main(http_client: AsyncClient) -> None:
50
- print(await http_client.get("https://httpbin.org/get"))
51
-
52
- engin = Engin(Provide(httpx_client), Invoke(main))
53
-
54
- asyncio.run(engin.run())
55
- ```
56
-
@@ -1,16 +0,0 @@
1
- engin/__init__.py,sha256=AIE9wnwvfXwS1mwp6Sa9xtatBYyYzhWa36GKfAkEx_M,508
2
- engin/_assembler.py,sha256=Py159y4HvI54QuDcmbjL2kfXtbucYDE2KFBOgoC4qCM,5243
3
- engin/_block.py,sha256=XrGMFEeps_rvOGsYTlsefUkJGAY6jqdp71G8jE9FbwU,1109
4
- engin/_dependency.py,sha256=cL1qQFKDt7Rgg7mx8IXVkCZlUigg5cvhPFF9g2LNrgg,4749
5
- engin/_engin.py,sha256=KHjtncm0bG_N4sAFt7haxUBMeuLp0zP8Bxpftp7Zgy8,3109
6
- engin/_exceptions.py,sha256=nkzTqxrW5nkcNgFDGoZ2TBtnHtO2RLk0qghM5LNAEmU,542
7
- engin/_lifecycle.py,sha256=0hk24fiwaBos5kaZrnG_Qm0VmUhWKnGtwYCjc007XDk,729
8
- engin/_type_utils.py,sha256=naEk-lknC3Fdsd4jiP4YZAxjX3KXZN0MhFde9EV-Fmo,1835
9
- engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- engin/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- engin/ext/asgi.py,sha256=7viV6YgEwa0KatypDUrEYZT3YIQ3URQbzfVTGG7OuZE,1931
12
- engin/ext/fastapi.py,sha256=vf7eB5no6eVuZyYdJZdvDYZmjJAmoKbrKhskgiSWg5g,1005
13
- engin-0.0.2.dist-info/METADATA,sha256=CxEA114Eb2DF_voKUqPITeZiU1KWfSZaqvzIVNXaUAw,1491
14
- engin-0.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- engin-0.0.2.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
16
- engin-0.0.2.dist-info/RECORD,,
File without changes