engin 0.0.17__py3-none-any.whl → 0.0.19__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 +0 -4
- engin/_assembler.py +30 -18
- engin/_block.py +16 -4
- engin/_cli/__init__.py +11 -0
- engin/_cli/_common.py +51 -0
- engin/_cli/_graph.py +6 -37
- engin/_cli/_inspect.py +94 -0
- engin/_dependency.py +24 -20
- engin/{_exceptions.py → exceptions.py} +27 -1
- engin/{ext → extensions}/fastapi.py +1 -1
- {engin-0.0.17.dist-info → engin-0.0.19.dist-info}/METADATA +1 -1
- engin-0.0.19.dist-info/RECORD +25 -0
- engin/_cli/_utils.py +0 -18
- engin-0.0.17.dist-info/RECORD +0 -24
- /engin/{ext → extensions}/__init__.py +0 -0
- /engin/{ext → extensions}/asgi.py +0 -0
- {engin-0.0.17.dist-info → engin-0.0.19.dist-info}/WHEEL +0 -0
- {engin-0.0.17.dist-info → engin-0.0.19.dist-info}/entry_points.txt +0 -0
- {engin-0.0.17.dist-info → engin-0.0.19.dist-info}/licenses/LICENSE +0 -0
engin/__init__.py
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
from engin import ext
|
2
1
|
from engin._assembler import Assembler
|
3
2
|
from engin._block import Block, invoke, provide
|
4
3
|
from engin._dependency import Entrypoint, Invoke, Provide, Supply
|
5
4
|
from engin._engin import Engin
|
6
|
-
from engin._exceptions import ProviderError
|
7
5
|
from engin._lifecycle import Lifecycle
|
8
6
|
from engin._option import Option
|
9
7
|
from engin._type_utils import TypeId
|
@@ -17,10 +15,8 @@ __all__ = [
|
|
17
15
|
"Lifecycle",
|
18
16
|
"Option",
|
19
17
|
"Provide",
|
20
|
-
"ProviderError",
|
21
18
|
"Supply",
|
22
19
|
"TypeId",
|
23
|
-
"ext",
|
24
20
|
"invoke",
|
25
21
|
"provide",
|
26
22
|
]
|
engin/_assembler.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
import logging
|
3
3
|
from collections import defaultdict
|
4
|
-
from collections.abc import Iterable
|
4
|
+
from collections.abc import Iterable, Sequence
|
5
5
|
from contextvars import ContextVar
|
6
6
|
from dataclasses import dataclass
|
7
7
|
from inspect import BoundArguments, Signature
|
@@ -9,8 +9,8 @@ from types import TracebackType
|
|
9
9
|
from typing import Any, Generic, TypeVar, cast
|
10
10
|
|
11
11
|
from engin._dependency import Dependency, Provide, Supply
|
12
|
-
from engin._exceptions import NotInScopeError, ProviderError
|
13
12
|
from engin._type_utils import TypeId
|
13
|
+
from engin.exceptions import NotInScopeError, ProviderError
|
14
14
|
|
15
15
|
LOG = logging.getLogger("engin")
|
16
16
|
|
@@ -65,6 +65,7 @@ class Assembler:
|
|
65
65
|
self._multiproviders: dict[TypeId, list[Provide[list[Any]]]] = defaultdict(list)
|
66
66
|
self._assembled_outputs: dict[TypeId, Any] = {}
|
67
67
|
self._lock = asyncio.Lock()
|
68
|
+
self._graph_cache: dict[TypeId, set[Provide]] = defaultdict(set)
|
68
69
|
|
69
70
|
for provider in providers:
|
70
71
|
type_id = provider.return_type_id
|
@@ -75,6 +76,11 @@ class Assembler:
|
|
75
76
|
else:
|
76
77
|
self._multiproviders[type_id].append(provider)
|
77
78
|
|
79
|
+
@property
|
80
|
+
def providers(self) -> Sequence[Provide[Any]]:
|
81
|
+
multi_providers = [p for multi in self._multiproviders.values() for p in multi]
|
82
|
+
return [*self._providers.values(), *multi_providers]
|
83
|
+
|
78
84
|
async def assemble(self, dependency: Dependency[Any, T]) -> AssembledDependency[T]:
|
79
85
|
"""
|
80
86
|
Assemble a dependency.
|
@@ -174,8 +180,8 @@ class Assembler:
|
|
174
180
|
"""
|
175
181
|
Add a provider to the Assembler post-initialisation.
|
176
182
|
|
177
|
-
If this replaces an existing provider, this will clear
|
178
|
-
output
|
183
|
+
If this replaces an existing provider, this will clear all previously assembled
|
184
|
+
output. Note: multiproviders cannot be replaced, they are always appended.
|
179
185
|
|
180
186
|
Args:
|
181
187
|
provider: the Provide instance to add.
|
@@ -185,14 +191,13 @@ class Assembler:
|
|
185
191
|
"""
|
186
192
|
type_id = provider.return_type_id
|
187
193
|
if provider.is_multiprovider:
|
188
|
-
if type_id in self._assembled_outputs:
|
189
|
-
del self._assembled_outputs[type_id]
|
190
194
|
self._multiproviders[type_id].append(provider)
|
191
195
|
else:
|
192
|
-
if type_id in self._assembled_outputs:
|
193
|
-
del self._assembled_outputs[type_id]
|
194
196
|
self._providers[type_id] = provider
|
195
197
|
|
198
|
+
self._assembled_outputs.clear()
|
199
|
+
self._graph_cache.clear()
|
200
|
+
|
196
201
|
def scope(self, scope: str) -> "_ScopeContextManager":
|
197
202
|
return _ScopeContextManager(scope=scope, assembler=self)
|
198
203
|
|
@@ -201,13 +206,14 @@ class Assembler:
|
|
201
206
|
if provider.scope == scope:
|
202
207
|
self._assembled_outputs.pop(type_id, None)
|
203
208
|
|
204
|
-
def _resolve_providers(self, type_id: TypeId) ->
|
209
|
+
def _resolve_providers(self, type_id: TypeId, resolved: set[TypeId]) -> set[Provide]:
|
205
210
|
"""
|
206
211
|
Resolves the chain of providers required to satisfy the provider of a given type.
|
207
212
|
Ordering of the return value is very important!
|
208
|
-
|
209
|
-
# TODO: performance optimisation, do not recurse for already satisfied providers?
|
210
213
|
"""
|
214
|
+
if type_id in self._graph_cache:
|
215
|
+
return self._graph_cache[type_id]
|
216
|
+
|
211
217
|
if type_id.multi:
|
212
218
|
root_providers = self._multiproviders.get(type_id)
|
213
219
|
else:
|
@@ -225,22 +231,28 @@ class Assembler:
|
|
225
231
|
raise LookupError(msg)
|
226
232
|
|
227
233
|
# providers that must be satisfied to satisfy the root level providers
|
228
|
-
|
234
|
+
resolved_providers = {
|
229
235
|
child_provider
|
230
236
|
for root_provider in root_providers
|
231
237
|
for root_provider_param in root_provider.parameter_type_ids
|
232
|
-
for child_provider in self._resolve_providers(root_provider_param)
|
233
|
-
|
234
|
-
|
238
|
+
for child_provider in self._resolve_providers(root_provider_param, resolved)
|
239
|
+
if root_provider_param not in resolved
|
240
|
+
} | set(root_providers)
|
241
|
+
|
242
|
+
resolved.add(type_id)
|
243
|
+
self._graph_cache[type_id] = resolved_providers
|
244
|
+
|
245
|
+
return resolved_providers
|
235
246
|
|
236
247
|
async def _satisfy(self, target: TypeId) -> None:
|
237
|
-
for provider in self._resolve_providers(target):
|
248
|
+
for provider in self._resolve_providers(target, set()):
|
238
249
|
if (
|
239
250
|
not provider.is_multiprovider
|
240
251
|
and provider.return_type_id in self._assembled_outputs
|
241
252
|
):
|
242
253
|
continue
|
243
254
|
type_id = provider.return_type_id
|
255
|
+
|
244
256
|
bound_args = await self._bind_arguments(provider.signature)
|
245
257
|
try:
|
246
258
|
value = await provider(*bound_args.args, **bound_args.kwargs)
|
@@ -248,6 +260,7 @@ class Assembler:
|
|
248
260
|
raise ProviderError(
|
249
261
|
provider=provider, error_type=type(err), error_message=str(err)
|
250
262
|
) from err
|
263
|
+
|
251
264
|
if provider.is_multiprovider:
|
252
265
|
if type_id in self._assembled_outputs:
|
253
266
|
self._assembled_outputs[type_id].extend(value)
|
@@ -264,8 +277,7 @@ class Assembler:
|
|
264
277
|
args.append(object())
|
265
278
|
continue
|
266
279
|
param_key = TypeId.from_type(param.annotation)
|
267
|
-
|
268
|
-
if not has_dependency:
|
280
|
+
if param_key not in self._assembled_outputs:
|
269
281
|
await self._satisfy(param_key)
|
270
282
|
val = self._assembled_outputs[param_key]
|
271
283
|
if param.kind == param.POSITIONAL_ONLY:
|
engin/_block.py
CHANGED
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, ClassVar
|
|
5
5
|
|
6
6
|
from engin._dependency import Dependency, Func, Invoke, Provide
|
7
7
|
from engin._option import Option
|
8
|
+
from engin.exceptions import InvalidBlockError
|
8
9
|
|
9
10
|
if TYPE_CHECKING:
|
10
11
|
from engin._engin import Engin
|
@@ -42,7 +43,7 @@ def invoke(func_: Func | None = None) -> Func | Callable[[Func], Func]:
|
|
42
43
|
return _inner(func_)
|
43
44
|
|
44
45
|
|
45
|
-
class Block
|
46
|
+
class Block:
|
46
47
|
"""
|
47
48
|
A Block is a collection of providers and invocations.
|
48
49
|
|
@@ -74,7 +75,7 @@ class Block(Option):
|
|
74
75
|
|
75
76
|
@classmethod
|
76
77
|
def apply(cls, engin: "Engin") -> None:
|
77
|
-
block_name = cls.name or
|
78
|
+
block_name = cls.name or cls.__name__
|
78
79
|
for option in chain(cls.options, cls._method_options()):
|
79
80
|
if isinstance(option, Dependency):
|
80
81
|
option._block_name = block_name
|
@@ -82,8 +83,19 @@ class Block(Option):
|
|
82
83
|
|
83
84
|
@classmethod
|
84
85
|
def _method_options(cls) -> Iterable[Provide | Invoke]:
|
85
|
-
for
|
86
|
+
for name, method in inspect.getmembers(cls, inspect.isfunction):
|
86
87
|
if option := getattr(method, "_opt", None):
|
87
88
|
if not isinstance(option, Provide | Invoke):
|
88
|
-
raise
|
89
|
+
raise InvalidBlockError(
|
90
|
+
block=cls,
|
91
|
+
reason="Block option is not an instance of Provide or Invoke",
|
92
|
+
)
|
89
93
|
yield option
|
94
|
+
else:
|
95
|
+
raise InvalidBlockError(
|
96
|
+
block=cls,
|
97
|
+
reason=(
|
98
|
+
f"Method '{name}' is not a Provider or Invocation, did you "
|
99
|
+
"forget to decorate it?"
|
100
|
+
),
|
101
|
+
)
|
engin/_cli/__init__.py
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
import logging
|
2
|
+
import sys
|
3
|
+
|
1
4
|
try:
|
2
5
|
import typer
|
3
6
|
except ImportError:
|
@@ -7,7 +10,15 @@ except ImportError:
|
|
7
10
|
) from None
|
8
11
|
|
9
12
|
from engin._cli._graph import cli as graph_cli
|
13
|
+
from engin._cli._inspect import cli as inspect_cli
|
14
|
+
|
15
|
+
# mute logging from importing of files + engin's debug logging.
|
16
|
+
logging.disable()
|
17
|
+
|
18
|
+
# add cwd to path to enable local package imports
|
19
|
+
sys.path.insert(0, "")
|
10
20
|
|
11
21
|
app = typer.Typer()
|
12
22
|
|
13
23
|
app.add_typer(graph_cli)
|
24
|
+
app.add_typer(inspect_cli)
|
engin/_cli/_common.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
import importlib
|
2
|
+
from typing import Never
|
3
|
+
|
4
|
+
import typer
|
5
|
+
from rich import print
|
6
|
+
from rich.panel import Panel
|
7
|
+
|
8
|
+
from engin import Engin
|
9
|
+
|
10
|
+
|
11
|
+
def print_error(msg: str) -> Never:
|
12
|
+
print(
|
13
|
+
Panel(
|
14
|
+
title="Error",
|
15
|
+
renderable=msg,
|
16
|
+
title_align="left",
|
17
|
+
border_style="red",
|
18
|
+
highlight=True,
|
19
|
+
)
|
20
|
+
)
|
21
|
+
raise typer.Exit(code=1)
|
22
|
+
|
23
|
+
|
24
|
+
COMMON_HELP = {
|
25
|
+
"app": (
|
26
|
+
"The import path of your Engin instance, in the form 'package:application'"
|
27
|
+
", e.g. 'app.main:engin'"
|
28
|
+
)
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
def get_engin_instance(app: str) -> tuple[str, str, Engin]:
|
33
|
+
try:
|
34
|
+
module_name, engin_name = app.split(":", maxsplit=1)
|
35
|
+
except ValueError:
|
36
|
+
print_error("Expected an argument of the form 'module:attribute', e.g. 'myapp:engin'")
|
37
|
+
|
38
|
+
try:
|
39
|
+
module = importlib.import_module(module_name)
|
40
|
+
except ModuleNotFoundError:
|
41
|
+
print_error(f"Unable to find module '{module_name}'")
|
42
|
+
|
43
|
+
try:
|
44
|
+
instance = getattr(module, engin_name)
|
45
|
+
except AttributeError:
|
46
|
+
print_error(f"Module '{module_name}' has no attribute '{engin_name}'")
|
47
|
+
|
48
|
+
if not isinstance(instance, Engin):
|
49
|
+
print_error(f"'{app}' is not an Engin instance")
|
50
|
+
|
51
|
+
return module_name, engin_name, instance
|
engin/_cli/_graph.py
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
import contextlib
|
2
|
-
import importlib
|
3
|
-
import logging
|
4
2
|
import socketserver
|
5
|
-
import sys
|
6
3
|
import threading
|
7
4
|
from http.server import BaseHTTPRequestHandler
|
8
5
|
from pathlib import Path
|
@@ -12,65 +9,37 @@ from typing import Annotated, Any
|
|
12
9
|
import typer
|
13
10
|
from rich import print
|
14
11
|
|
15
|
-
from engin import
|
16
|
-
from engin._cli.
|
12
|
+
from engin import Entrypoint, Invoke, TypeId
|
13
|
+
from engin._cli._common import COMMON_HELP, get_engin_instance
|
17
14
|
from engin._dependency import Dependency, Provide, Supply
|
18
|
-
from engin.
|
15
|
+
from engin.extensions.asgi import ASGIEngin
|
19
16
|
|
20
17
|
try:
|
21
|
-
from engin.
|
18
|
+
from engin.extensions.fastapi import APIRouteDependency
|
22
19
|
except ImportError:
|
23
20
|
APIRouteDependency = None # type: ignore[assignment,misc]
|
24
21
|
|
25
22
|
cli = typer.Typer()
|
26
23
|
|
27
|
-
# mute logging from importing of files + engin's debug logging.
|
28
|
-
logging.disable()
|
29
24
|
|
30
25
|
_APP_ORIGIN = ""
|
31
26
|
|
32
|
-
_CLI_HELP = {
|
33
|
-
"app": (
|
34
|
-
"The import path of your Engin instance, in the form 'package:application'"
|
35
|
-
", e.g. 'app.main:engin'"
|
36
|
-
)
|
37
|
-
}
|
38
|
-
|
39
27
|
|
40
28
|
@cli.command(name="graph")
|
41
29
|
def serve_graph(
|
42
30
|
app: Annotated[
|
43
31
|
str,
|
44
|
-
typer.Argument(help=
|
32
|
+
typer.Argument(help=COMMON_HELP["app"]),
|
45
33
|
],
|
46
34
|
) -> None:
|
47
35
|
"""
|
48
36
|
Creates a visualisation of your application's dependencies.
|
49
37
|
"""
|
50
|
-
|
51
|
-
sys.path.insert(0, "")
|
52
|
-
|
53
|
-
try:
|
54
|
-
module_name, engin_name = app.split(":", maxsplit=1)
|
55
|
-
except ValueError:
|
56
|
-
print_error("Expected an argument of the form 'module:attribute', e.g. 'myapp:engin'")
|
38
|
+
module_name, _, instance = get_engin_instance(app)
|
57
39
|
|
58
40
|
global _APP_ORIGIN
|
59
41
|
_APP_ORIGIN = module_name.split(".", maxsplit=1)[0]
|
60
42
|
|
61
|
-
try:
|
62
|
-
module = importlib.import_module(module_name)
|
63
|
-
except ModuleNotFoundError:
|
64
|
-
print_error(f"unable to find module '{module_name}'")
|
65
|
-
|
66
|
-
try:
|
67
|
-
instance = getattr(module, engin_name)
|
68
|
-
except AttributeError:
|
69
|
-
print_error(f"module '{module_name}' has no attribute '{engin_name}'")
|
70
|
-
|
71
|
-
if not isinstance(instance, Engin):
|
72
|
-
print_error(f"'{app}' is not an Engin instance")
|
73
|
-
|
74
43
|
nodes = instance.graph()
|
75
44
|
|
76
45
|
# transform dependencies into mermaid syntax
|
engin/_cli/_inspect.py
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
from typing import Annotated
|
2
|
+
|
3
|
+
import typer
|
4
|
+
from rich import box
|
5
|
+
from rich.console import Console
|
6
|
+
from rich.table import Table
|
7
|
+
|
8
|
+
from engin import Supply
|
9
|
+
from engin._cli._common import COMMON_HELP, get_engin_instance, print_error
|
10
|
+
|
11
|
+
cli = typer.Typer()
|
12
|
+
_CLI_HELP = {
|
13
|
+
"type": "Filter providers by the provided type, e.g. `AsyncClient` or `float[]`",
|
14
|
+
"module": "Filter providers by the provided types' module, e.g. `engin` or `httpx`",
|
15
|
+
"verbose": "Enables verbose output",
|
16
|
+
}
|
17
|
+
|
18
|
+
|
19
|
+
@cli.command(name="inspect")
|
20
|
+
def serve_graph(
|
21
|
+
app: Annotated[
|
22
|
+
str,
|
23
|
+
typer.Argument(help=COMMON_HELP["app"]),
|
24
|
+
],
|
25
|
+
type_: Annotated[
|
26
|
+
str | None,
|
27
|
+
typer.Option("--type", help=_CLI_HELP["type"]),
|
28
|
+
] = None,
|
29
|
+
module: Annotated[
|
30
|
+
str | None,
|
31
|
+
typer.Option(help=_CLI_HELP["module"]),
|
32
|
+
] = None,
|
33
|
+
verbose: Annotated[
|
34
|
+
bool, typer.Option("--verbose", "-v", help=_CLI_HELP["verbose"])
|
35
|
+
] = False,
|
36
|
+
) -> None:
|
37
|
+
"""
|
38
|
+
Shows metadata for all matching providers.
|
39
|
+
|
40
|
+
Examples:
|
41
|
+
|
42
|
+
1. `engin inspect examples.simple.main:engin --module httpx`
|
43
|
+
|
44
|
+
2. `engin inspect examples.simple.main:engin --type AsyncClient`
|
45
|
+
"""
|
46
|
+
module_name, _, instance = get_engin_instance(app)
|
47
|
+
|
48
|
+
console = Console()
|
49
|
+
|
50
|
+
providers = []
|
51
|
+
for provider in instance.assembler.providers:
|
52
|
+
type_id = provider.return_type_id
|
53
|
+
if type_ is not None:
|
54
|
+
type_name = str(type_id).rsplit(".", maxsplit=1)[-1]
|
55
|
+
if type_ != type_name:
|
56
|
+
if verbose:
|
57
|
+
console.print(
|
58
|
+
f"Ignoring '{provider.return_type_id}' as `{type_} != {type_name}",
|
59
|
+
style="dim",
|
60
|
+
)
|
61
|
+
continue
|
62
|
+
if module is not None:
|
63
|
+
module_name = str(type_id).split(".", maxsplit=1)[0]
|
64
|
+
if module != module_name:
|
65
|
+
if verbose:
|
66
|
+
console.print(
|
67
|
+
f"Ignoring '{provider.return_type_id}' as `{module} != {module_name}",
|
68
|
+
style="dim",
|
69
|
+
)
|
70
|
+
continue
|
71
|
+
providers.append(provider)
|
72
|
+
|
73
|
+
matching_provider_count = len(providers)
|
74
|
+
if matching_provider_count == 0:
|
75
|
+
available = sorted(map(str, instance.assembler.providers))
|
76
|
+
print_error(f"No matching providers, available: {available}")
|
77
|
+
|
78
|
+
if matching_provider_count > 1:
|
79
|
+
console.print(f"Found {matching_provider_count} matching providers", style="dim")
|
80
|
+
|
81
|
+
table = Table(show_header=False, show_lines=False, box=box.ASCII)
|
82
|
+
|
83
|
+
for provider in sorted(providers, key=lambda p: p.source_module):
|
84
|
+
is_supply = isinstance(provider, Supply)
|
85
|
+
|
86
|
+
table.add_row("name", str(provider), style="bold", end_section=True)
|
87
|
+
table.add_row("scope", provider.scope or "N/A")
|
88
|
+
table.add_row("func", provider.func_name if not is_supply else "N/A")
|
89
|
+
table.add_row("block", provider.block_name or "N/A")
|
90
|
+
table.add_row("source module", provider.source_module or "N/A")
|
91
|
+
table.add_row("source package", provider.source_package or "N/A")
|
92
|
+
table.add_section()
|
93
|
+
|
94
|
+
console.print(table)
|
engin/_dependency.py
CHANGED
@@ -175,47 +175,37 @@ class Provide(Dependency[Any, T]):
|
|
175
175
|
self._scope = scope
|
176
176
|
self._override = override
|
177
177
|
self._explicit_type = as_type
|
178
|
+
self._return_type = self._resolve_return_type()
|
179
|
+
self._return_type_id = TypeId.from_type(self._return_type)
|
178
180
|
|
179
181
|
if self._explicit_type is not None:
|
180
182
|
self._signature = self._signature.replace(return_annotation=self._explicit_type)
|
181
183
|
|
182
|
-
self._is_multi = typing.get_origin(self.
|
184
|
+
self._is_multi = typing.get_origin(self._return_type) is list
|
183
185
|
|
184
186
|
# Validate that the provider does to depend on its own output value, as this will
|
185
187
|
# cause a recursion error and is undefined behaviour wise.
|
186
188
|
if any(
|
187
|
-
self.
|
189
|
+
self._return_type == param.annotation
|
188
190
|
for param in self._signature.parameters.values()
|
189
191
|
):
|
190
192
|
raise ValueError("A provider cannot depend on its own return type")
|
191
193
|
|
192
194
|
# Validate that multiproviders only return a list of one type.
|
193
195
|
if self._is_multi:
|
194
|
-
args = typing.get_args(self.
|
196
|
+
args = typing.get_args(self._return_type)
|
195
197
|
if len(args) != 1:
|
196
198
|
raise ValueError(
|
197
|
-
f"A multiprovider must be of the form list[X], not '{self.
|
199
|
+
f"A multiprovider must be of the form list[X], not '{self._return_type}'"
|
198
200
|
)
|
199
201
|
|
200
202
|
@property
|
201
203
|
def return_type(self) -> type[T]:
|
202
|
-
|
203
|
-
return self._explicit_type
|
204
|
-
if isclass(self._func):
|
205
|
-
return_type = self._func # __init__ returns self
|
206
|
-
else:
|
207
|
-
try:
|
208
|
-
return_type = get_type_hints(self._func, include_extras=True)["return"]
|
209
|
-
except KeyError as err:
|
210
|
-
raise RuntimeError(
|
211
|
-
f"Dependency '{self.name}' requires a return typehint"
|
212
|
-
) from err
|
213
|
-
|
214
|
-
return return_type
|
204
|
+
return self._return_type
|
215
205
|
|
216
206
|
@property
|
217
207
|
def return_type_id(self) -> TypeId:
|
218
|
-
return
|
208
|
+
return self._return_type_id
|
219
209
|
|
220
210
|
@property
|
221
211
|
def is_multiprovider(self) -> bool:
|
@@ -255,6 +245,21 @@ class Provide(Dependency[Any, T]):
|
|
255
245
|
def __str__(self) -> str:
|
256
246
|
return f"Provide({self.return_type_id})"
|
257
247
|
|
248
|
+
def _resolve_return_type(self) -> type[T]:
|
249
|
+
if self._explicit_type is not None:
|
250
|
+
return self._explicit_type
|
251
|
+
if isclass(self._func):
|
252
|
+
return_type = self._func # __init__ returns self
|
253
|
+
else:
|
254
|
+
try:
|
255
|
+
return_type = get_type_hints(self._func, include_extras=True)["return"]
|
256
|
+
except KeyError as err:
|
257
|
+
raise RuntimeError(
|
258
|
+
f"Dependency '{self.name}' requires a return typehint"
|
259
|
+
) from err
|
260
|
+
|
261
|
+
return return_type
|
262
|
+
|
258
263
|
|
259
264
|
class Supply(Provide, Generic[T]):
|
260
265
|
def __init__(
|
@@ -276,8 +281,7 @@ class Supply(Provide, Generic[T]):
|
|
276
281
|
self._value = value
|
277
282
|
super().__init__(builder=self._get_val, as_type=as_type, override=override)
|
278
283
|
|
279
|
-
|
280
|
-
def return_type(self) -> type[T]:
|
284
|
+
def _resolve_return_type(self) -> type[T]:
|
281
285
|
if self._explicit_type is not None:
|
282
286
|
return self._explicit_type
|
283
287
|
if isinstance(self._value, list):
|
@@ -1,7 +1,10 @@
|
|
1
|
-
from typing import Any
|
1
|
+
from typing import TYPE_CHECKING, Any
|
2
2
|
|
3
3
|
from engin._dependency import Provide
|
4
4
|
|
5
|
+
if TYPE_CHECKING:
|
6
|
+
from engin._block import Block
|
7
|
+
|
5
8
|
|
6
9
|
class EnginError(Exception):
|
7
10
|
"""
|
@@ -15,6 +18,20 @@ class AssemblerError(EnginError):
|
|
15
18
|
"""
|
16
19
|
|
17
20
|
|
21
|
+
class InvalidBlockError(EnginError):
|
22
|
+
"""
|
23
|
+
Raised when an invalid block is instantiated.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(self, block: "type[Block]", reason: str) -> None:
|
27
|
+
self.block = block
|
28
|
+
self.block_name = block.name or block.__name__
|
29
|
+
self.message = f"block '{self.block_name}' is invalid, reason: '{reason}'"
|
30
|
+
|
31
|
+
def __str__(self) -> str:
|
32
|
+
return self.message
|
33
|
+
|
34
|
+
|
18
35
|
class ProviderError(AssemblerError):
|
19
36
|
"""
|
20
37
|
Raised when a Provider errors during Assembly.
|
@@ -52,3 +69,12 @@ class NotInScopeError(AssemblerError):
|
|
52
69
|
|
53
70
|
def __str__(self) -> str:
|
54
71
|
return self.message
|
72
|
+
|
73
|
+
|
74
|
+
__all__ = [
|
75
|
+
"AssemblerError",
|
76
|
+
"EnginError",
|
77
|
+
"InvalidBlockError",
|
78
|
+
"NotInScopeError",
|
79
|
+
"ProviderError",
|
80
|
+
]
|
@@ -10,7 +10,7 @@ from engin import Assembler, Engin, Entrypoint, Invoke, Option
|
|
10
10
|
from engin._dependency import Dependency, Supply, _noop
|
11
11
|
from engin._graph import DependencyGrapher, Node
|
12
12
|
from engin._type_utils import TypeId
|
13
|
-
from engin.
|
13
|
+
from engin.extensions.asgi import ASGIEngin
|
14
14
|
|
15
15
|
try:
|
16
16
|
from fastapi import APIRouter, FastAPI
|
@@ -0,0 +1,25 @@
|
|
1
|
+
engin/__init__.py,sha256=A8TE_ci7idoR683535YoBrWZbYTgXXS-q7Y2y51nZ5M,486
|
2
|
+
engin/_assembler.py,sha256=-ENSrXPMWacionIYrTSQO7th9DDBOPyAT8ybPbBRtQw,11318
|
3
|
+
engin/_block.py,sha256=IacP4PoJKRhSQCbQSdoyCtmu362a4vj6qoUQKyaJwzI,3062
|
4
|
+
engin/_dependency.py,sha256=Nfq6L92LN4X53QpiMCIF3MjmWfuntYVOnZmmoPYYJEw,9165
|
5
|
+
engin/_engin.py,sha256=yIpZdeqvm8hv0RxOV0veFuvyu9xQ054JSaeuUWwHdOQ,7380
|
6
|
+
engin/_graph.py,sha256=y1g7Lm_Zy5GPEgRsggCKV5DDaDzcwUl8v3IZCK8jyGI,1631
|
7
|
+
engin/_introspect.py,sha256=VdREX6Lhhga5SnEP9G7mjHkgJR4mpqk_SMnmL2zTcqY,966
|
8
|
+
engin/_lifecycle.py,sha256=cSWe3euZkmpxmUPFvph2lsTtvuZbxttEfBL-RnOI7lo,5325
|
9
|
+
engin/_option.py,sha256=nZcdrehp1QwgxMUoIpsM0PJuu1q1pbXzhcVsetbsHpc,223
|
10
|
+
engin/_type_utils.py,sha256=Pmm4m1_WdevT5KTe8tzY_BseNxPyhu_nKsLGgyNcPpo,2247
|
11
|
+
engin/exceptions.py,sha256=-VPwPReZb9YEIkrWMR9TW2K5HEwmHHgEO7QWH6wfV8c,1946
|
12
|
+
engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
+
engin/_cli/__init__.py,sha256=koD5WTkZXb8QQIiVU5bJiSR1wwPGb5rv2iwd-v-BA7A,564
|
14
|
+
engin/_cli/_common.py,sha256=zMYb1Bs1yUuR3qf3r6WuVozYzDwHJvTVthVbTQfTF9w,1261
|
15
|
+
engin/_cli/_graph.html,sha256=rR5dnDKoz7KtSff0ERCi2UKuoH_Z03MRYiXI_W03G5k,2430
|
16
|
+
engin/_cli/_graph.py,sha256=HMC91nWvTOr6_czPBNx1RU55Ib3qesJRCmbnL2DsdDk,4659
|
17
|
+
engin/_cli/_inspect.py,sha256=0jm25d4wcbXVNJkyaeECSKY-irsxd-EIYBH1GDW_Yjc,3163
|
18
|
+
engin/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
+
engin/extensions/asgi.py,sha256=d5Z6gtMVWDZdAlvrTaMt987sKyiq__A0X4gJQ7IETmA,3247
|
20
|
+
engin/extensions/fastapi.py,sha256=e8F4L_nZ9dU9j8mb9lXKwJG6CTu5aIk4N5faRj4EyUA,6369
|
21
|
+
engin-0.0.19.dist-info/METADATA,sha256=Rb1VPxLjnzVxhnq8Llie5XRxpI0GANR-dK31BEcPAqg,2354
|
22
|
+
engin-0.0.19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
23
|
+
engin-0.0.19.dist-info/entry_points.txt,sha256=sW247zZUMxm0b5UKYvPuqQQljYDtU-j2zK3cu7gHwM0,41
|
24
|
+
engin-0.0.19.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
|
25
|
+
engin-0.0.19.dist-info/RECORD,,
|
engin/_cli/_utils.py
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
from typing import Never
|
2
|
-
|
3
|
-
import typer
|
4
|
-
from rich import print
|
5
|
-
from rich.panel import Panel
|
6
|
-
|
7
|
-
|
8
|
-
def print_error(msg: str) -> Never:
|
9
|
-
print(
|
10
|
-
Panel(
|
11
|
-
title="Error",
|
12
|
-
renderable=msg.capitalize(),
|
13
|
-
title_align="left",
|
14
|
-
border_style="red",
|
15
|
-
highlight=True,
|
16
|
-
)
|
17
|
-
)
|
18
|
-
raise typer.Exit(code=1)
|
engin-0.0.17.dist-info/RECORD
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
engin/__init__.py,sha256=rBTteMLAVKg4TJSaMElJUwz72BA_X7nBTREg-I-bWhA,584
|
2
|
-
engin/_assembler.py,sha256=r2Vr9UO7pHYvj71087PIJYT9lFBNPni3x34MtjMHN7A,10952
|
3
|
-
engin/_block.py,sha256=8ysWrmHkWpTm6bmSc6jZVoO0Ax5Svu1HwxpZwAtIF_o,2617
|
4
|
-
engin/_dependency.py,sha256=5x4_0QvHtqv6R_brKHRc-INKE4oMh1JU8-9RCmulp4Q,8976
|
5
|
-
engin/_engin.py,sha256=yIpZdeqvm8hv0RxOV0veFuvyu9xQ054JSaeuUWwHdOQ,7380
|
6
|
-
engin/_exceptions.py,sha256=UzMppJWDk_Hx3qWAypcPVLw9OYCibqiZjLYeTl22zaE,1355
|
7
|
-
engin/_graph.py,sha256=y1g7Lm_Zy5GPEgRsggCKV5DDaDzcwUl8v3IZCK8jyGI,1631
|
8
|
-
engin/_introspect.py,sha256=VdREX6Lhhga5SnEP9G7mjHkgJR4mpqk_SMnmL2zTcqY,966
|
9
|
-
engin/_lifecycle.py,sha256=cSWe3euZkmpxmUPFvph2lsTtvuZbxttEfBL-RnOI7lo,5325
|
10
|
-
engin/_option.py,sha256=nZcdrehp1QwgxMUoIpsM0PJuu1q1pbXzhcVsetbsHpc,223
|
11
|
-
engin/_type_utils.py,sha256=Pmm4m1_WdevT5KTe8tzY_BseNxPyhu_nKsLGgyNcPpo,2247
|
12
|
-
engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
engin/_cli/__init__.py,sha256=lp1KiBpcgk_dZU5V9DjgLPwmp0ja444fwLH2CYCscNc,302
|
14
|
-
engin/_cli/_graph.html,sha256=rR5dnDKoz7KtSff0ERCi2UKuoH_Z03MRYiXI_W03G5k,2430
|
15
|
-
engin/_cli/_graph.py,sha256=YZ0awBLtWD5W6xrHO9ueMnk_EIsYM4_j_bmquiLsb2Y,5542
|
16
|
-
engin/_cli/_utils.py,sha256=AQFtLO8qjYRCTQc9A8Z1HVf7eZr8iGWogxbYzsgIkS4,360
|
17
|
-
engin/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
-
engin/ext/asgi.py,sha256=d5Z6gtMVWDZdAlvrTaMt987sKyiq__A0X4gJQ7IETmA,3247
|
19
|
-
engin/ext/fastapi.py,sha256=TGNf0LFLaTLMLlAycH7GgP_GcBld262v9xboGOwhvgE,6362
|
20
|
-
engin-0.0.17.dist-info/METADATA,sha256=1SUQZwsFr3neO0WJ_gLLv90kgJCcBNUbwWWi2zZZ77Q,2354
|
21
|
-
engin-0.0.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
22
|
-
engin-0.0.17.dist-info/entry_points.txt,sha256=sW247zZUMxm0b5UKYvPuqQQljYDtU-j2zK3cu7gHwM0,41
|
23
|
-
engin-0.0.17.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
|
24
|
-
engin-0.0.17.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|