engin 0.0.4__py3-none-any.whl → 0.0.5__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
@@ -3,12 +3,11 @@ 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
5
  from engin._engin import Engin, Option
6
- from engin._exceptions import AssemblyError
6
+ from engin._exceptions import ProviderError
7
7
  from engin._lifecycle import Lifecycle
8
8
 
9
9
  __all__ = [
10
10
  "Assembler",
11
- "AssemblyError",
12
11
  "Block",
13
12
  "Engin",
14
13
  "Entrypoint",
@@ -16,6 +15,7 @@ __all__ = [
16
15
  "Lifecycle",
17
16
  "Option",
18
17
  "Provide",
18
+ "ProviderError",
19
19
  "Supply",
20
20
  "ext",
21
21
  "invoke",
engin/_assembler.py CHANGED
@@ -7,7 +7,7 @@ from inspect import BoundArguments, Signature
7
7
  from typing import Any, Generic, TypeVar, cast
8
8
 
9
9
  from engin._dependency import Dependency, Provide, Supply
10
- from engin._exceptions import AssemblyError
10
+ from engin._exceptions import ProviderError
11
11
  from engin._type_utils import TypeId, type_id_of
12
12
 
13
13
  LOG = logging.getLogger("engin")
@@ -17,14 +17,40 @@ T = TypeVar("T")
17
17
 
18
18
  @dataclass(slots=True, kw_only=True, frozen=True)
19
19
  class AssembledDependency(Generic[T]):
20
+ """
21
+ An AssembledDependency can be called to construct the result.
22
+ """
23
+
20
24
  dependency: Dependency[Any, T]
21
25
  bound_args: BoundArguments
22
26
 
23
27
  async def __call__(self) -> T:
28
+ """
29
+ Construct the dependency.
30
+
31
+ Returns:
32
+ The constructed value.
33
+ """
24
34
  return await self.dependency(*self.bound_args.args, **self.bound_args.kwargs)
25
35
 
26
36
 
27
37
  class Assembler:
38
+ """
39
+ A container for Providers that is responsible for building provided types.
40
+
41
+ The Assembler acts as a cache for previously built types, meaning repeat calls
42
+ to `get` will produce the same value.
43
+
44
+ Examples:
45
+ ```python
46
+ def build_str() -> str:
47
+ return "foo"
48
+
49
+ a = Assembler([Provide(build_str)])
50
+ await a.get(str)
51
+ ```
52
+ """
53
+
28
54
  def __init__(self, providers: Iterable[Provide]) -> None:
29
55
  self._providers: dict[TypeId, Provide[Any]] = {}
30
56
  self._multiproviders: dict[TypeId, list[Provide[list[Any]]]] = defaultdict(list)
@@ -41,6 +67,82 @@ class Assembler:
41
67
  else:
42
68
  self._multiproviders[type_id].append(provider)
43
69
 
70
+ async def assemble(self, dependency: Dependency[Any, T]) -> AssembledDependency[T]:
71
+ """
72
+ Assemble a dependency.
73
+
74
+ Given a Dependency type, such as Invoke, the Assembler constructs the types
75
+ required by the Dependency's signature from its providers.
76
+
77
+ Args:
78
+ dependency: the Dependency to assemble.
79
+
80
+ Returns:
81
+ An AssembledDependency, which can be awaited to construct the final value.
82
+ """
83
+ async with self._lock:
84
+ return AssembledDependency(
85
+ dependency=dependency,
86
+ bound_args=await self._bind_arguments(dependency.signature),
87
+ )
88
+
89
+ async def get(self, type_: type[T]) -> T:
90
+ """
91
+ Return the constructed value for the given type.
92
+
93
+ This method assembles the required Providers and constructs their corresponding
94
+ values.
95
+
96
+ If the
97
+
98
+ Args:
99
+ type_: the type of the desired value.
100
+
101
+ Raises:
102
+ LookupError: When no provider is found for the given type.
103
+ ProviderError: When a provider errors when trying to construct the type or
104
+ any of its dependent types.
105
+
106
+ Returns:
107
+ The constructed value.
108
+ """
109
+ type_id = type_id_of(type_)
110
+ if type_id in self._dependencies:
111
+ return cast(T, self._dependencies[type_id])
112
+ if type_id.multi:
113
+ out = []
114
+ if type_id not in self._multiproviders:
115
+ raise LookupError(f"no provider found for target type id '{type_id}'")
116
+ for provider in self._multiproviders[type_id]:
117
+ assembled_dependency = await self.assemble(provider)
118
+ try:
119
+ out.extend(await assembled_dependency())
120
+ except Exception as err:
121
+ raise ProviderError(
122
+ provider=provider,
123
+ error_type=type(err),
124
+ error_message=str(err),
125
+ ) from err
126
+ self._dependencies[type_id] = out
127
+ return out # type: ignore[return-value]
128
+ else:
129
+ if type_id not in self._providers:
130
+ raise LookupError(f"no provider found for target type id '{type_id}'")
131
+ assembled_dependency = await self.assemble(self._providers[type_id])
132
+ try:
133
+ value = await assembled_dependency()
134
+ except Exception as err:
135
+ raise ProviderError(
136
+ provider=self._providers[type_id],
137
+ error_type=type(err),
138
+ error_message=str(err),
139
+ ) from err
140
+ self._dependencies[type_id] = value
141
+ return value # type: ignore[return-value]
142
+
143
+ def has(self, type_: type[T]) -> bool:
144
+ return type_id_of(type_) in self._providers
145
+
44
146
  def _resolve_providers(self, type_id: TypeId) -> Collection[Provide]:
45
147
  if type_id.multi:
46
148
  providers = self._multiproviders.get(type_id)
@@ -73,7 +175,7 @@ class Assembler:
73
175
  try:
74
176
  value = await provider(*bound_args.args, **bound_args.kwargs)
75
177
  except Exception as err:
76
- raise AssemblyError(
178
+ raise ProviderError(
77
179
  provider=provider, error_type=type(err), error_message=str(err)
78
180
  ) from err
79
181
  if provider.is_multiprovider:
@@ -102,30 +204,3 @@ class Assembler:
102
204
  kwargs[param.name] = val
103
205
 
104
206
  return signature.bind(*args, **kwargs)
105
-
106
- async def assemble(self, dependency: Dependency[Any, T]) -> AssembledDependency[T]:
107
- async with self._lock:
108
- return AssembledDependency(
109
- dependency=dependency,
110
- bound_args=await self._bind_arguments(dependency.signature),
111
- )
112
-
113
- async def get(self, type_: type[T]) -> T:
114
- type_id = type_id_of(type_)
115
- if type_id in self._dependencies:
116
- return cast(T, self._dependencies[type_id])
117
- if type_id.multi:
118
- out = []
119
- for provider in self._multiproviders[type_id]:
120
- assembled_dependency = await self.assemble(provider)
121
- out.extend(await assembled_dependency())
122
- self._dependencies[type_id] = out
123
- return out # type: ignore[return-value]
124
- else:
125
- assembled_dependency = await self.assemble(self._providers[type_id])
126
- value = await assembled_dependency()
127
- self._dependencies[type_id] = value
128
- return value # type: ignore[return-value]
129
-
130
- def has(self, type_: type[T]) -> bool:
131
- return type_id_of(type_) in self._providers
engin/_block.py CHANGED
@@ -6,16 +6,48 @@ from engin._dependency import Func, Invoke, Provide
6
6
 
7
7
 
8
8
  def provide(func: Func) -> Func:
9
+ """
10
+ A decorator for defining a Provider in a Block.
11
+ """
9
12
  func._opt = Provide(func) # type: ignore[attr-defined]
10
13
  return func
11
14
 
12
15
 
13
16
  def invoke(func: Func) -> Func:
17
+ """
18
+ A decorator for defining an Invocation in a Block.
19
+ """
14
20
  func._opt = Invoke(func) # type: ignore[attr-defined]
15
21
  return func
16
22
 
17
23
 
18
24
  class Block(Iterable[Provide | Invoke]):
25
+ """
26
+ A Block is a collection of providers and invocations.
27
+
28
+ Blocks are useful for grouping a collection of related providers and invocations, and
29
+ are themselves a valid Option type that can be passed to the Engin.
30
+
31
+ Providers are defined as methods decorated with the `provide` decorator, and similarly
32
+ for Invocations and the `invoke` decorator.
33
+
34
+ Examples:
35
+ Define a simple block.
36
+
37
+ ```python3
38
+ from engin import Block, provide, invoke
39
+
40
+ class MyBlock(Block):
41
+ @provide
42
+ def some_str(self) -> str:
43
+ return "foo"
44
+
45
+ @invoke
46
+ def print_str(self, string: str) -> None:
47
+ print(f"invoked on string '{string}')
48
+ ```
49
+ """
50
+
19
51
  options: ClassVar[list[Provide | Invoke]] = []
20
52
 
21
53
  def __init__(self, /, block_name: str | None = None) -> None:
engin/_dependency.py CHANGED
@@ -66,6 +66,24 @@ class Dependency(ABC, Generic[P, T]):
66
66
 
67
67
 
68
68
  class Invoke(Dependency):
69
+ """
70
+ Marks a function as an Invocation.
71
+
72
+ Invocations are functions that are called prior to lifecycle startup. Invocations
73
+ should not be long running as the application startup will be blocked until all
74
+ Invocation are completed.
75
+
76
+ Invocations can be provided as an Option to the Engin or a Block.
77
+
78
+ Examples:
79
+ ```python3
80
+ def print_string(a_string: str) -> None:
81
+ print(f"invoking with value: '{a_string}'")
82
+
83
+ invocation = Invoke(print_string)
84
+ ```
85
+ """
86
+
69
87
  def __init__(self, invocation: Func[P, T], block_name: str | None = None) -> None:
70
88
  super().__init__(func=invocation, block_name=block_name)
71
89
 
@@ -74,6 +92,12 @@ class Invoke(Dependency):
74
92
 
75
93
 
76
94
  class Entrypoint(Invoke):
95
+ """
96
+ Marks a type as an Entrypoint.
97
+
98
+ Entrypoints are a short hand for no-op Invocations that can be used to
99
+ """
100
+
77
101
  def __init__(self, type_: type[Any], *, block_name: str | None = None) -> None:
78
102
  self._type = type_
79
103
  super().__init__(invocation=_noop, block_name=block_name)
engin/_engin.py CHANGED
@@ -26,8 +26,26 @@ _OS_IS_WINDOWS = os.name == "nt"
26
26
 
27
27
  class Engin:
28
28
  """
29
- The Engin class runs your application. It assembles the required dependencies, invokes
30
- any invocations and manages your application's lifecycle.
29
+ The Engin is a modular application defined by a collection of options.
30
+
31
+ Users should instantiate the Engin with a number of options, where options can be an
32
+ instance of Provide, Invoke, or a collection of these combined in a Block.
33
+
34
+ To create a useful application, users should pass in one or more providers (Provide or
35
+ Supply) and at least one invocation (Invoke or Entrypoint).
36
+
37
+ When instantiated the Engin can be run. This is typically done via the `run` method,
38
+ but certain use cases, e.g. testing, it can be easier to use the `start` and `stop`
39
+ methods.
40
+
41
+ When ran the Engin takes care of the complete application lifecycle:
42
+ 1. The Engin assembles all Invocations. Only Providers that are required to satisfy
43
+ the Invoke options parameters are assembled.
44
+ 2. All Invocations are run sequentially in the order they were passed in to the Engin.
45
+ 3. Any Lifecycle Startup defined by a provider that was assembled in order to satisfy
46
+ the constructors is ran.
47
+ 4. The Engin waits for a stop signal, i.e. SIGINT or SIGTERM.
48
+ 5. Any Lifecyce Shutdown task is ran, in the reverse order to the Startup order.
31
49
 
32
50
  Examples:
33
51
  ```python
@@ -54,6 +72,15 @@ class Engin:
54
72
  _LIB_OPTIONS: ClassVar[list[Option]] = [Provide(Lifecycle)]
55
73
 
56
74
  def __init__(self, *options: Option) -> None:
75
+ """
76
+ Initialise the class with the provided options.
77
+
78
+ Examples:
79
+ >>> engin = Engin(Provide(construct_a), Invoke(do_b), Supply(C()), MyBlock())
80
+
81
+ Args:
82
+ *options: an instance of Provide, Supply, Invoke, Entrypoint or a Block.
83
+ """
57
84
  self._providers: dict[TypeId, Provide] = {TypeId.from_type(Engin): Provide(self._self)}
58
85
  self._invokables: list[Invoke] = []
59
86
 
@@ -72,17 +99,23 @@ class Engin:
72
99
 
73
100
  async def run(self) -> None:
74
101
  """
75
- Run the Engin and wait for it to be stopped via an external signal or by calling
76
- the `stop` method.
102
+ Run the engin.
103
+
104
+ The engin will run until it is stopped via an external signal (i.e. SIGTERM or
105
+ SIGINT) or the `stop` method is called on the engin.
77
106
  """
78
107
  await self.start()
79
- self._run_task = asyncio.create_task(_raise_on_stop(self._stop_requested_event))
108
+ self._run_task = asyncio.create_task(_wait_for_stop_signal(self._stop_requested_event))
80
109
  await self._stop_requested_event.wait()
81
110
  await self._shutdown()
82
111
 
83
112
  async def start(self) -> None:
84
113
  """
85
- Starts the engin, this method waits for the shutdown lifecycle to complete.
114
+ Start the engin.
115
+
116
+ This is an alternative to calling `run`. This method waits for the startup
117
+ lifecycle to complete and then returns. The caller is then responsible for
118
+ calling `stop`.
86
119
  """
87
120
  LOG.info("starting engin")
88
121
  assembled_invocations: list[AssembledDependency] = [
@@ -113,7 +146,11 @@ class Engin:
113
146
 
114
147
  async def stop(self) -> None:
115
148
  """
116
- Stops the engin, this method waits for the shutdown lifecycle to complete.
149
+ Stop the engin.
150
+
151
+ This method will wait for the shutdown lifecycle to complete before returning.
152
+ Note this method can be safely called at any point, even before the engin is
153
+ started.
117
154
  """
118
155
  self._stop_requested_event.set()
119
156
  await self._stop_complete_event.wait()
@@ -162,15 +199,7 @@ class Engin:
162
199
  return self
163
200
 
164
201
 
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
- """
202
+ async def _wait_for_stop_signal(stop_requested_event: Event) -> None:
174
203
  try:
175
204
  # try to gracefully handle sigint/sigterm
176
205
  if not _OS_IS_WINDOWS:
@@ -179,7 +208,6 @@ async def _raise_on_stop(stop_requested_event: Event) -> None:
179
208
  loop.add_signal_handler(signame, stop_requested_event.set)
180
209
 
181
210
  await stop_requested_event.wait()
182
- raise _StopRequested()
183
211
  else:
184
212
  should_stop = False
185
213
 
engin/_exceptions.py CHANGED
@@ -3,9 +3,16 @@ from typing import Any
3
3
  from engin._dependency import Provide
4
4
 
5
5
 
6
- class AssemblyError(Exception):
6
+ class ProviderError(Exception):
7
+ """
8
+ Raised when a Provider errors during Assembly.
9
+ """
10
+
7
11
  def __init__(
8
- self, provider: Provide[Any], error_type: type[Exception], error_message: str
12
+ self,
13
+ provider: Provide[Any],
14
+ error_type: type[Exception],
15
+ error_message: str,
9
16
  ) -> None:
10
17
  self.provider = provider
11
18
  self.error_type = error_type
engin/_lifecycle.py CHANGED
@@ -1,29 +1,83 @@
1
+ import asyncio
1
2
  import logging
2
- from contextlib import AbstractAsyncContextManager
3
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager
3
4
  from types import TracebackType
5
+ from typing import TypeAlias, TypeGuard, cast
4
6
 
5
7
  LOG = logging.getLogger("engin")
6
8
 
9
+ _AnyContextManager: TypeAlias = AbstractAsyncContextManager | AbstractContextManager
10
+
7
11
 
8
12
  class Lifecycle:
13
+ """
14
+ Allows dependencies to define startup and shutdown tasks for the application.
15
+
16
+ Lifecycle tasks are defined using Context Managers, these can be async or sync.
17
+
18
+ Lifecycle tasks should generally be defined in Providers as they are tied to the
19
+ construction of a given dependency, but can be used in Invocations. The Lifecycle
20
+ type is provided as a built-in Dependency by the Engin framework.
21
+
22
+ Examples:
23
+ Using a type that implements context management.
24
+
25
+ ```python
26
+ from httpx import AsyncClient
27
+
28
+ def my_provider(lifecycle: Lifecycle) -> AsyncClient:
29
+ client = AsyncClient()
30
+
31
+ # AsyncClient is a context manager
32
+ lifecycle.append(client)
33
+ ```
34
+
35
+ Defining a custom lifecycle.
36
+
37
+ ```python
38
+ def my_provider(lifecycle: Lifecycle) -> str:
39
+ @contextmanager
40
+ def task():
41
+ print("starting up!")
42
+ yield
43
+ print("shutting down!)
44
+
45
+ lifecycle.append(task)
46
+ ```
47
+ """
48
+
9
49
  def __init__(self) -> None:
10
50
  self._context_managers: list[AbstractAsyncContextManager] = []
11
51
 
12
- def append(self, cm: AbstractAsyncContextManager) -> None:
52
+ def append(self, cm: _AnyContextManager, /) -> None:
53
+ """
54
+ Append a Lifecycle task to the list.
55
+
56
+ Args:
57
+ cm: a task defined as a ContextManager or AsyncContextManager.
58
+ """
13
59
  suppressed_cm = _AExitSuppressingAsyncContextManager(cm)
14
60
  self._context_managers.append(suppressed_cm)
15
61
 
16
62
  def list(self) -> list[AbstractAsyncContextManager]:
63
+ """
64
+ List all the defined tasks.
65
+
66
+ Returns:
67
+ A copy of the list of Lifecycle tasks.
68
+ """
17
69
  return self._context_managers[:]
18
70
 
19
71
 
20
72
  class _AExitSuppressingAsyncContextManager(AbstractAsyncContextManager):
21
- def __init__(self, cm: AbstractAsyncContextManager) -> None:
73
+ def __init__(self, cm: _AnyContextManager) -> None:
22
74
  self._cm = cm
23
75
 
24
- async def __aenter__(self) -> AbstractAsyncContextManager:
25
- await self._cm.__aenter__()
26
- return self._cm
76
+ async def __aenter__(self) -> None:
77
+ if self._is_async_cm(self._cm):
78
+ await self._cm.__aenter__()
79
+ else:
80
+ await asyncio.to_thread(cast(AbstractContextManager, self._cm).__enter__)
27
81
 
28
82
  async def __aexit__(
29
83
  self,
@@ -33,6 +87,18 @@ class _AExitSuppressingAsyncContextManager(AbstractAsyncContextManager):
33
87
  /,
34
88
  ) -> None:
35
89
  try:
36
- await self._cm.__aexit__(exc_type, exc_value, traceback)
90
+ if self._is_async_cm(self._cm):
91
+ await self._cm.__aexit__(exc_type, exc_value, traceback)
92
+ else:
93
+ await asyncio.to_thread(
94
+ cast(AbstractContextManager, self._cm).__exit__,
95
+ exc_type,
96
+ exc_value,
97
+ traceback,
98
+ )
37
99
  except Exception as err:
38
100
  LOG.error("error in lifecycle hook stop, ignoring...", exc_info=err)
101
+
102
+ @staticmethod
103
+ def _is_async_cm(cm: _AnyContextManager) -> TypeGuard[AbstractAsyncContextManager]:
104
+ return hasattr(cm, "__aenter__")
engin/_type_utils.py CHANGED
@@ -5,13 +5,26 @@ from typing import Any
5
5
  _implict_modules = ["builtins", "typing", "collections.abc"]
6
6
 
7
7
 
8
- @dataclass(frozen=True, eq=True)
8
+ @dataclass(frozen=True, eq=True, slots=True)
9
9
  class TypeId:
10
+ """
11
+ Represents information about a Type in the Dependency Injection framework.
12
+ """
13
+
10
14
  type: type
11
15
  multi: bool
12
16
 
13
17
  @classmethod
14
18
  def from_type(cls, type_: Any) -> "TypeId":
19
+ """
20
+ Construct a TypeId from a given type.
21
+
22
+ Args:
23
+ type_: any type.
24
+
25
+ Returns:
26
+ The corresponding TypeId for that type.
27
+ """
15
28
  if is_multi_type(type_):
16
29
  inner_obj = typing.get_args(type_)[0]
17
30
  return TypeId(type=inner_obj, multi=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engin
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: An async-first modular application framework
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.10
@@ -0,0 +1,16 @@
1
+ engin/__init__.py,sha256=yTc8k0HDGMIrxDdEEA90qGD_dExQjVIbXCyaOFRrnMg,508
2
+ engin/_assembler.py,sha256=VCZA_Gq4hnH5LueB_vEVqsKbGXx-nI6KQ65YhzXw-VY,7575
3
+ engin/_block.py,sha256=-5qTp1Hdm3H54nScDGitFpcXRHLIyVHlDYATg_3dnPw,2045
4
+ engin/_dependency.py,sha256=oh1T7oR-c9MGcZ6ZFUgPnvHRf-n6AIvpbm59R97To80,5404
5
+ engin/_engin.py,sha256=kk3U_SZLlGYDbFbPYmErvlRqKE855yPvHBlX6XH2Row,8212
6
+ engin/_exceptions.py,sha256=fsc4pTOIGHUh0x7oZhEXPJUTE268sIhswLoiqXaudiw,635
7
+ engin/_lifecycle.py,sha256=_jQnGFj4RYXsxMpcXPJQagFOwnoTVh7oSN8oUYoYuW0,3246
8
+ engin/_type_utils.py,sha256=C71kX2Dr-gluGSL018K4uihX3zkTe7QNWaHhFU10ZmA,2127
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.5.dist-info/METADATA,sha256=7qu0kb9zWAZWkp0osgHkCqFKBwKykvYUrH4JcbmBY_M,1806
14
+ engin-0.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ engin-0.0.5.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
16
+ engin-0.0.5.dist-info/RECORD,,
@@ -1,16 +0,0 @@
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,,
File without changes