engin 0.0.7__py3-none-any.whl → 0.0.8__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/_dependency.py +4 -1
- engin/_engin.py +5 -3
- engin/_graph.py +39 -0
- engin/scripts/__init__.py +0 -0
- engin/scripts/graph.py +122 -0
- {engin-0.0.7.dist-info → engin-0.0.8.dist-info}/METADATA +7 -1
- {engin-0.0.7.dist-info → engin-0.0.8.dist-info}/RECORD +10 -6
- engin-0.0.8.dist-info/entry_points.txt +2 -0
- {engin-0.0.7.dist-info → engin-0.0.8.dist-info}/WHEEL +0 -0
- {engin-0.0.7.dist-info → engin-0.0.8.dist-info}/licenses/LICENSE +0 -0
engin/_dependency.py
CHANGED
@@ -18,7 +18,6 @@ from engin._type_utils import TypeId, type_id_of
|
|
18
18
|
P = ParamSpec("P")
|
19
19
|
T = TypeVar("T")
|
20
20
|
Func: TypeAlias = Callable[P, T]
|
21
|
-
_SELF = object()
|
22
21
|
|
23
22
|
|
24
23
|
def _noop(*args: Any, **kwargs: Any) -> None: ...
|
@@ -31,6 +30,10 @@ class Dependency(ABC, Generic[P, T]):
|
|
31
30
|
self._signature = inspect.signature(self._func)
|
32
31
|
self._block_name = block_name
|
33
32
|
|
33
|
+
@property
|
34
|
+
def module(self) -> str:
|
35
|
+
return self._func.__module__
|
36
|
+
|
34
37
|
@property
|
35
38
|
def block_name(self) -> str | None:
|
36
39
|
return self._block_name
|
engin/_engin.py
CHANGED
@@ -13,6 +13,7 @@ from engin import Entrypoint
|
|
13
13
|
from engin._assembler import AssembledDependency, Assembler
|
14
14
|
from engin._block import Block
|
15
15
|
from engin._dependency import Dependency, Invoke, Provide, Supply
|
16
|
+
from engin._graph import DependencyGrapher, Node
|
16
17
|
from engin._lifecycle import Lifecycle
|
17
18
|
from engin._type_utils import TypeId
|
18
19
|
|
@@ -80,7 +81,6 @@ class Engin:
|
|
80
81
|
Args:
|
81
82
|
*options: an instance of Provide, Supply, Invoke, Entrypoint or a Block.
|
82
83
|
"""
|
83
|
-
|
84
84
|
self._stop_requested_event = Event()
|
85
85
|
self._stop_complete_event = Event()
|
86
86
|
self._exit_stack: AsyncExitStack = AsyncExitStack()
|
@@ -95,8 +95,6 @@ class Engin:
|
|
95
95
|
self._destruct_options(chain(self._LIB_OPTIONS, options))
|
96
96
|
multi_providers = [p for multi in self._multiproviders.values() for p in multi]
|
97
97
|
self._assembler = Assembler(chain(self._providers.values(), multi_providers))
|
98
|
-
self._providers.clear()
|
99
|
-
self._multiproviders.clear()
|
100
98
|
|
101
99
|
@property
|
102
100
|
def assembler(self) -> Assembler:
|
@@ -162,6 +160,10 @@ class Engin:
|
|
162
160
|
return
|
163
161
|
await self._stop_complete_event.wait()
|
164
162
|
|
163
|
+
def graph(self) -> list[Node]:
|
164
|
+
grapher = DependencyGrapher({**self._providers, **self._multiproviders})
|
165
|
+
return grapher.resolve(self._invocations)
|
166
|
+
|
165
167
|
async def _shutdown(self) -> None:
|
166
168
|
LOG.info("stopping engin")
|
167
169
|
await self._exit_stack.aclose()
|
engin/_graph.py
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
from collections.abc import Iterable
|
2
|
+
from typing import TypedDict
|
3
|
+
|
4
|
+
from engin._dependency import Dependency, Provide
|
5
|
+
from engin._type_utils import TypeId
|
6
|
+
|
7
|
+
|
8
|
+
class Node(TypedDict):
|
9
|
+
node: Dependency
|
10
|
+
parent: Dependency | None
|
11
|
+
|
12
|
+
|
13
|
+
class DependencyGrapher:
|
14
|
+
def __init__(self, providers: dict[TypeId, Provide | list[Provide]]) -> None:
|
15
|
+
self._providers: dict[TypeId, Provide | list[Provide]] = providers
|
16
|
+
|
17
|
+
def resolve(self, roots: Iterable[Dependency]) -> list[Node]:
|
18
|
+
seen: set[TypeId] = set()
|
19
|
+
nodes: list[Node] = []
|
20
|
+
|
21
|
+
for root in roots:
|
22
|
+
for parameter in root.parameter_types:
|
23
|
+
if parameter in seen:
|
24
|
+
continue
|
25
|
+
|
26
|
+
seen.add(parameter)
|
27
|
+
provider = self._providers[parameter]
|
28
|
+
|
29
|
+
# multiprovider
|
30
|
+
if isinstance(provider, list):
|
31
|
+
for p in provider:
|
32
|
+
nodes.append({"node": p, "parent": root})
|
33
|
+
nodes.extend(self.resolve([p]))
|
34
|
+
# single provider
|
35
|
+
else:
|
36
|
+
nodes.append({"node": provider, "parent": root})
|
37
|
+
nodes.extend(self.resolve([provider]))
|
38
|
+
|
39
|
+
return nodes
|
File without changes
|
engin/scripts/graph.py
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
import importlib
|
2
|
+
import logging
|
3
|
+
import socketserver
|
4
|
+
import sys
|
5
|
+
import threading
|
6
|
+
from argparse import ArgumentParser
|
7
|
+
from http.server import BaseHTTPRequestHandler
|
8
|
+
from time import sleep
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
from engin import Engin
|
12
|
+
from engin._dependency import Dependency, Provide
|
13
|
+
|
14
|
+
# mute logging from importing of files + engin's debug logging.
|
15
|
+
logging.disable()
|
16
|
+
|
17
|
+
args = ArgumentParser(
|
18
|
+
prog="engin-graph",
|
19
|
+
description="Creates a visualisation of your application's dependencies",
|
20
|
+
)
|
21
|
+
args.add_argument(
|
22
|
+
"-e", "--exclude", help="a list of packages or module to exclude", default=["engin"]
|
23
|
+
)
|
24
|
+
args.add_argument(
|
25
|
+
"app",
|
26
|
+
help=(
|
27
|
+
"the import path of your Engin instance, in the form "
|
28
|
+
"'package:application', e.g. 'app.main:engin'"
|
29
|
+
),
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
def serve_graph() -> None:
|
34
|
+
# add cwd to path to enable local package imports
|
35
|
+
sys.path.insert(0, "")
|
36
|
+
|
37
|
+
parsed = args.parse_args()
|
38
|
+
|
39
|
+
app = parsed.app
|
40
|
+
excluded_modules = parsed.exclude
|
41
|
+
|
42
|
+
try:
|
43
|
+
module_name, engin_name = app.split(":", maxsplit=1)
|
44
|
+
except ValueError:
|
45
|
+
raise ValueError(
|
46
|
+
"Expected an argument of the form 'module:attribute', e.g. 'myapp:engin'"
|
47
|
+
) from None
|
48
|
+
|
49
|
+
module = importlib.import_module(module_name)
|
50
|
+
|
51
|
+
try:
|
52
|
+
instance = getattr(module, engin_name)
|
53
|
+
except LookupError:
|
54
|
+
raise LookupError(f"Module '{module_name}' has no attribute '{engin_name}'") from None
|
55
|
+
|
56
|
+
if not isinstance(instance, Engin):
|
57
|
+
raise TypeError(f"'{app}' is not an Engin instance")
|
58
|
+
|
59
|
+
nodes = instance.graph()
|
60
|
+
|
61
|
+
# transform dependencies into mermaid syntax
|
62
|
+
dependencies = [
|
63
|
+
f"{_render_node(node['parent'])} --> {_render_node(node['node'])}"
|
64
|
+
for node in nodes
|
65
|
+
if node["parent"] is not None
|
66
|
+
and not _should_exclude(node["node"].module, excluded_modules)
|
67
|
+
]
|
68
|
+
|
69
|
+
html = _GRAPH_HTML.replace("%%DATA%%", "\n".join(dependencies)).encode("utf8")
|
70
|
+
|
71
|
+
class Handler(BaseHTTPRequestHandler):
|
72
|
+
def do_GET(self) -> None:
|
73
|
+
self.send_response(200, "OK")
|
74
|
+
self.send_header("Content-type", "html")
|
75
|
+
self.end_headers()
|
76
|
+
self.wfile.write(html)
|
77
|
+
|
78
|
+
def log_message(self, format: str, *args: Any) -> None:
|
79
|
+
return
|
80
|
+
|
81
|
+
def _start_server() -> None:
|
82
|
+
with socketserver.TCPServer(("localhost", 8123), Handler) as httpd:
|
83
|
+
print("Serving dependency graph on http://localhost:8123")
|
84
|
+
httpd.serve_forever()
|
85
|
+
|
86
|
+
server_thread = threading.Thread(target=_start_server)
|
87
|
+
server_thread.daemon = True # Daemonize the thread so it exits when the main script exits
|
88
|
+
server_thread.start()
|
89
|
+
|
90
|
+
try:
|
91
|
+
sleep(10000)
|
92
|
+
except KeyboardInterrupt:
|
93
|
+
print("Exiting the server...")
|
94
|
+
|
95
|
+
|
96
|
+
def _render_node(node: Dependency) -> str:
|
97
|
+
if isinstance(node, Provide):
|
98
|
+
return str(node.return_type_id)
|
99
|
+
else:
|
100
|
+
return node.name
|
101
|
+
|
102
|
+
|
103
|
+
def _should_exclude(module: str, excluded: list[str]) -> bool:
|
104
|
+
return any(module.startswith(e) for e in excluded)
|
105
|
+
|
106
|
+
|
107
|
+
_GRAPH_HTML = """
|
108
|
+
<!doctype html>
|
109
|
+
<html lang="en">
|
110
|
+
<body>
|
111
|
+
<pre class="mermaid">
|
112
|
+
graph TD
|
113
|
+
%%DATA%%
|
114
|
+
</pre>
|
115
|
+
<script type="module">
|
116
|
+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
117
|
+
let config = { flowchart: { useMaxWidth: false, htmlLabels: true } };
|
118
|
+
mermaid.initialize(config);
|
119
|
+
</script>
|
120
|
+
</body>
|
121
|
+
</html>
|
122
|
+
"""
|
@@ -1,8 +1,14 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: engin
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.8
|
4
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
|
5
10
|
License-File: LICENSE
|
11
|
+
Keywords: Application Framework,Dependency Injection
|
6
12
|
Requires-Python: >=3.10
|
7
13
|
Description-Content-Type: text/markdown
|
8
14
|
|
@@ -1,16 +1,20 @@
|
|
1
1
|
engin/__init__.py,sha256=yTc8k0HDGMIrxDdEEA90qGD_dExQjVIbXCyaOFRrnMg,508
|
2
2
|
engin/_assembler.py,sha256=VCZA_Gq4hnH5LueB_vEVqsKbGXx-nI6KQ65YhzXw-VY,7575
|
3
3
|
engin/_block.py,sha256=-5qTp1Hdm3H54nScDGitFpcXRHLIyVHlDYATg_3dnPw,2045
|
4
|
-
engin/_dependency.py,sha256=
|
5
|
-
engin/_engin.py,sha256=
|
4
|
+
engin/_dependency.py,sha256=WjJKJY2KhseYpqM1Gg-VhYls4dcLg1CgdlpniJEUHjI,5489
|
5
|
+
engin/_engin.py,sha256=i2IxMIz-3yKjedyg5L6gQEpdFDUZnCbXi9Pwhw8Hsxk,9133
|
6
6
|
engin/_exceptions.py,sha256=fsc4pTOIGHUh0x7oZhEXPJUTE268sIhswLoiqXaudiw,635
|
7
|
+
engin/_graph.py,sha256=piqcocrWt0mX_UAr0DVdkCtVYjkjVpkikLCdRVCoUnU,1230
|
7
8
|
engin/_lifecycle.py,sha256=_jQnGFj4RYXsxMpcXPJQagFOwnoTVh7oSN8oUYoYuW0,3246
|
8
9
|
engin/_type_utils.py,sha256=C71kX2Dr-gluGSL018K4uihX3zkTe7QNWaHhFU10ZmA,2127
|
9
10
|
engin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
11
|
engin/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
12
|
engin/ext/asgi.py,sha256=6vuC4zIhsvAdmwRn2I6uuUWPYfqobox1dv7skg2OWwE,1940
|
12
13
|
engin/ext/fastapi.py,sha256=CH2Zi7Oh_Va0TJGx05e7_LqAiCsoI1qcu0Z59_rgfRk,899
|
13
|
-
engin
|
14
|
-
engin
|
15
|
-
engin-0.0.
|
16
|
-
engin-0.0.
|
14
|
+
engin/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
engin/scripts/graph.py,sha256=f_Yk4TRrw9N-gltdriiOviNGcowQckeM2R-7CPVXkHY,3424
|
16
|
+
engin-0.0.8.dist-info/METADATA,sha256=xX0-36ECkPFBcDsYjiY04-dhn-066fQhKKzEceUPkhQ,2161
|
17
|
+
engin-0.0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
18
|
+
engin-0.0.8.dist-info/entry_points.txt,sha256=Dehk4j5nK6zyuQtgOSRAoLE609V6eLzEp32bjqhO62Q,64
|
19
|
+
engin-0.0.8.dist-info/licenses/LICENSE,sha256=XHh5LPUPKZWTBqBv2xxN2RU7D59nHoiJGb5RIt8f45w,1070
|
20
|
+
engin-0.0.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|