fastapi-voyager 0.5.1__py3-none-any.whl → 0.5.3__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.
- fastapi_voyager/module.py +73 -38
- fastapi_voyager/render.py +37 -17
- fastapi_voyager/type.py +8 -0
- fastapi_voyager/version.py +1 -1
- fastapi_voyager/voyager.py +13 -0
- fastapi_voyager/web/component/render-graph.js +47 -19
- fastapi_voyager/web/index.html +1 -1
- fastapi_voyager/web/vue-main.js +7 -16
- {fastapi_voyager-0.5.1.dist-info → fastapi_voyager-0.5.3.dist-info}/METADATA +8 -1
- {fastapi_voyager-0.5.1.dist-info → fastapi_voyager-0.5.3.dist-info}/RECORD +13 -13
- {fastapi_voyager-0.5.1.dist-info → fastapi_voyager-0.5.3.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.5.1.dist-info → fastapi_voyager-0.5.3.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.5.1.dist-info → fastapi_voyager-0.5.3.dist-info}/licenses/LICENSE +0 -0
fastapi_voyager/module.py
CHANGED
|
@@ -1,64 +1,99 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Callable, Type, TypeVar, Any
|
|
2
|
+
from fastapi_voyager.type import SchemaNode, ModuleNode, Route, ModuleRoute
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
N = TypeVar('N') # Node type: ModuleNode or ModuleRoute
|
|
6
|
+
I = TypeVar('I') # Item type: SchemaNode or Route
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _build_module_tree(
|
|
10
|
+
items: list[I],
|
|
11
|
+
*,
|
|
12
|
+
get_module_path: Callable[[I], str | None],
|
|
13
|
+
NodeClass: Type[N],
|
|
14
|
+
item_list_attr: str,
|
|
15
|
+
) -> list[N]:
|
|
4
16
|
"""
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
Generic builder that groups items by dotted module path into a tree of NodeClass.
|
|
18
|
+
|
|
19
|
+
NodeClass must accept kwargs: name, fullname, modules(list), and an item list via
|
|
20
|
+
item_list_attr (e.g., 'schema_nodes' or 'routes').
|
|
9
21
|
"""
|
|
10
|
-
# Map from top-level module name to
|
|
11
|
-
top_modules: dict[str,
|
|
12
|
-
#
|
|
13
|
-
|
|
22
|
+
# Map from top-level module name to node
|
|
23
|
+
top_modules: dict[str, N] = {}
|
|
24
|
+
# Items without module path
|
|
25
|
+
root_level_items: list[I] = []
|
|
14
26
|
|
|
15
|
-
def
|
|
16
|
-
|
|
27
|
+
def make_node(name: str, fullname: str) -> N:
|
|
28
|
+
kwargs: dict[str, Any] = {
|
|
29
|
+
'name': name,
|
|
30
|
+
'fullname': fullname,
|
|
31
|
+
'modules': [],
|
|
32
|
+
item_list_attr: [],
|
|
33
|
+
}
|
|
34
|
+
return NodeClass(**kwargs) # type: ignore[arg-type]
|
|
35
|
+
|
|
36
|
+
def get_or_create(child_name: str, parent: N) -> N:
|
|
37
|
+
for m in getattr(parent, 'modules'):
|
|
17
38
|
if m.name == child_name:
|
|
18
39
|
return m
|
|
19
|
-
|
|
20
|
-
parent_full = parent.fullname
|
|
40
|
+
parent_full = getattr(parent, 'fullname')
|
|
21
41
|
fullname = child_name if not parent_full or parent_full == "__root__" else f"{parent_full}.{child_name}"
|
|
22
|
-
new_node =
|
|
23
|
-
parent
|
|
42
|
+
new_node = make_node(child_name, fullname)
|
|
43
|
+
getattr(parent, 'modules').append(new_node)
|
|
24
44
|
return new_node
|
|
25
45
|
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
# Build the tree
|
|
47
|
+
for it in items:
|
|
48
|
+
module_path = get_module_path(it) or ""
|
|
28
49
|
if not module_path:
|
|
29
|
-
|
|
50
|
+
root_level_items.append(it)
|
|
30
51
|
continue
|
|
31
52
|
parts = module_path.split('.')
|
|
32
53
|
top_name = parts[0]
|
|
33
54
|
if top_name not in top_modules:
|
|
34
|
-
top_modules[top_name] =
|
|
55
|
+
top_modules[top_name] = make_node(top_name, top_name)
|
|
35
56
|
current = top_modules[top_name]
|
|
36
57
|
for part in parts[1:]:
|
|
37
58
|
current = get_or_create(part, current)
|
|
38
|
-
current.
|
|
59
|
+
getattr(current, item_list_attr).append(it)
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
result
|
|
61
|
+
result: list[N] = list(top_modules.values())
|
|
62
|
+
if root_level_items:
|
|
63
|
+
result.append(make_node("__root__", "__root__"))
|
|
64
|
+
setattr(result[-1], item_list_attr, root_level_items)
|
|
44
65
|
|
|
45
|
-
# Collapse
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
while len(node.modules) == 1 and len(node.schema_nodes) == 0:
|
|
50
|
-
child = node.modules[0]
|
|
51
|
-
# Merge child's identity into current node
|
|
66
|
+
# Collapse linear chains: no items on node and exactly one child module
|
|
67
|
+
def collapse(node: N) -> None:
|
|
68
|
+
while len(getattr(node, 'modules')) == 1 and len(getattr(node, item_list_attr)) == 0:
|
|
69
|
+
child = getattr(node, 'modules')[0]
|
|
52
70
|
node.name = f"{node.name}.{child.name}"
|
|
53
|
-
# Prefer child's fullname which already reflects full path
|
|
54
71
|
node.fullname = child.fullname
|
|
55
|
-
node
|
|
56
|
-
node
|
|
57
|
-
|
|
58
|
-
for m in node.modules:
|
|
72
|
+
setattr(node, item_list_attr, getattr(child, item_list_attr))
|
|
73
|
+
setattr(node, 'modules', getattr(child, 'modules'))
|
|
74
|
+
for m in getattr(node, 'modules'):
|
|
59
75
|
collapse(m)
|
|
60
76
|
|
|
61
77
|
for top in result:
|
|
62
78
|
collapse(top)
|
|
63
79
|
|
|
64
|
-
return result
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
def build_module_schema_tree(schema_nodes: list[SchemaNode]) -> list[ModuleNode]:
|
|
83
|
+
"""Build a module tree for schema nodes, grouped by their module path."""
|
|
84
|
+
return _build_module_tree(
|
|
85
|
+
schema_nodes,
|
|
86
|
+
get_module_path=lambda sn: sn.module,
|
|
87
|
+
NodeClass=ModuleNode,
|
|
88
|
+
item_list_attr='schema_nodes',
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build_module_route_tree(routes: list[Route]) -> list[ModuleRoute]:
|
|
93
|
+
"""Build a module tree for routes, grouped by their module path."""
|
|
94
|
+
return _build_module_tree(
|
|
95
|
+
routes,
|
|
96
|
+
get_module_path=lambda r: r.module,
|
|
97
|
+
NodeClass=ModuleRoute,
|
|
98
|
+
item_list_attr='routes',
|
|
99
|
+
)
|
fastapi_voyager/render.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from fastapi_voyager.type import SchemaNode, ModuleNode, Link, Tag, Route, FieldType, PK
|
|
2
|
-
from fastapi_voyager.module import
|
|
1
|
+
from fastapi_voyager.type import SchemaNode, ModuleNode, Link, Tag, Route, FieldType, PK, ModuleRoute
|
|
2
|
+
from fastapi_voyager.module import build_module_schema_tree, build_module_route_tree
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Renderer:
|
|
@@ -51,7 +51,7 @@ class Renderer:
|
|
|
51
51
|
if link.type == 'tag_route':
|
|
52
52
|
return f"""{h(link.source)}:e -> {h(link.target)}:w [style = "solid", minlen=3];"""
|
|
53
53
|
elif link.type == 'route_to_schema':
|
|
54
|
-
return f"""{h(link.source)}:e -> {h(link.target)}:
|
|
54
|
+
return f"""{h(link.source)}:e -> {h(link.target)}:w [style = "solid", dir="back", arrowtail="odot", minlen=3];"""
|
|
55
55
|
elif link.type == 'schema':
|
|
56
56
|
return f"""{h(link.source)}:e -> {h(link.target)}:w [style = "solid", label = "", dir="back", minlen=3, arrowtail="odot"];"""
|
|
57
57
|
elif link.type == 'parent':
|
|
@@ -61,7 +61,7 @@ class Renderer:
|
|
|
61
61
|
else:
|
|
62
62
|
raise ValueError(f'Unknown link type: {link.type}')
|
|
63
63
|
|
|
64
|
-
def
|
|
64
|
+
def render_module_schema(self, mod: ModuleNode) -> str:
|
|
65
65
|
color = self.module_color.get(mod.fullname)
|
|
66
66
|
inner_nodes = [
|
|
67
67
|
f'''
|
|
@@ -72,7 +72,7 @@ class Renderer:
|
|
|
72
72
|
];''' for node in mod.schema_nodes
|
|
73
73
|
]
|
|
74
74
|
inner_nodes_str = '\n'.join(inner_nodes)
|
|
75
|
-
child_str = '\n'.join(self.
|
|
75
|
+
child_str = '\n'.join(self.render_module_schema(m) for m in mod.modules)
|
|
76
76
|
return f'''
|
|
77
77
|
subgraph cluster_module_{mod.fullname.replace('.', '_')} {{
|
|
78
78
|
tooltip="{mod.fullname}"
|
|
@@ -85,9 +85,36 @@ class Renderer:
|
|
|
85
85
|
{inner_nodes_str}
|
|
86
86
|
{child_str}
|
|
87
87
|
}}'''
|
|
88
|
+
|
|
89
|
+
def render_module_route(self, mod: ModuleRoute) -> str:
|
|
90
|
+
color = self.module_color.get(mod.fullname)
|
|
91
|
+
# Inner route nodes, same style as flat route_str
|
|
92
|
+
inner_nodes = [
|
|
93
|
+
f'''
|
|
94
|
+
"{r.id}" [
|
|
95
|
+
label = " {r.name} "
|
|
96
|
+
margin="0.5,0.1"
|
|
97
|
+
shape = "record"
|
|
98
|
+
];''' for r in mod.routes
|
|
99
|
+
]
|
|
100
|
+
inner_nodes_str = '\n'.join(inner_nodes)
|
|
101
|
+
child_str = '\n'.join(self.render_module_route(m) for m in mod.modules)
|
|
102
|
+
return f'''
|
|
103
|
+
subgraph cluster_route_module_{mod.fullname.replace('.', '_')} {{
|
|
104
|
+
tooltip="{mod.fullname}"
|
|
105
|
+
color = "#666"
|
|
106
|
+
style="rounded"
|
|
107
|
+
label = " {mod.name}"
|
|
108
|
+
labeljust = "l"
|
|
109
|
+
{(f'pencolor = "{color}"' if color else 'pencolor="#ccc"')}
|
|
110
|
+
{(f'penwidth = 3' if color else 'penwidth=""')}
|
|
111
|
+
{inner_nodes_str}
|
|
112
|
+
{child_str}
|
|
113
|
+
}}'''
|
|
88
114
|
|
|
89
115
|
def render_dot(self, tags: list[Tag], routes: list[Route], nodes: list[SchemaNode], links: list[Link]) -> str:
|
|
90
|
-
|
|
116
|
+
module_schemas = build_module_schema_tree(nodes)
|
|
117
|
+
module_routes = build_module_route_tree(routes)
|
|
91
118
|
|
|
92
119
|
tag_str = '\n'.join([
|
|
93
120
|
f'''
|
|
@@ -98,16 +125,9 @@ class Renderer:
|
|
|
98
125
|
];''' for t in tags
|
|
99
126
|
])
|
|
100
127
|
|
|
101
|
-
route_str = '\n'.join([
|
|
102
|
-
f'''
|
|
103
|
-
"{r.id}" [
|
|
104
|
-
label = " {r.name} "
|
|
105
|
-
margin="0.5,0.1"
|
|
106
|
-
shape = "record"
|
|
107
|
-
];''' for r in routes
|
|
108
|
-
])
|
|
109
128
|
|
|
110
|
-
|
|
129
|
+
module_schemas_str = '\n'.join(self.render_module_schema(m) for m in module_schemas)
|
|
130
|
+
module_routes_str = '\n'.join(self.render_module_route(m) for m in module_routes)
|
|
111
131
|
link_str = '\n'.join(self.render_link(link) for link in links)
|
|
112
132
|
|
|
113
133
|
dot_str = f'''
|
|
@@ -144,7 +164,7 @@ class Renderer:
|
|
|
144
164
|
label = " Routes"
|
|
145
165
|
labeljust = "l"
|
|
146
166
|
fontsize = "20"
|
|
147
|
-
{
|
|
167
|
+
{module_routes_str}
|
|
148
168
|
}}
|
|
149
169
|
|
|
150
170
|
subgraph cluster_schema {{
|
|
@@ -154,7 +174,7 @@ class Renderer:
|
|
|
154
174
|
label=" Schema"
|
|
155
175
|
labeljust="l"
|
|
156
176
|
fontsize="20"
|
|
157
|
-
{
|
|
177
|
+
{module_schemas_str}
|
|
158
178
|
}}
|
|
159
179
|
|
|
160
180
|
{link_str}
|
fastapi_voyager/type.py
CHANGED
|
@@ -21,9 +21,17 @@ class Tag(NodeBase):
|
|
|
21
21
|
|
|
22
22
|
@dataclass
|
|
23
23
|
class Route(NodeBase):
|
|
24
|
+
module: str
|
|
24
25
|
source_code: str = ''
|
|
25
26
|
vscode_link: str = '' # optional vscode deep link
|
|
26
27
|
|
|
28
|
+
@dataclass
|
|
29
|
+
class ModuleRoute:
|
|
30
|
+
name: str
|
|
31
|
+
fullname: str
|
|
32
|
+
routes: list[Route]
|
|
33
|
+
modules: list['ModuleRoute']
|
|
34
|
+
|
|
27
35
|
@dataclass
|
|
28
36
|
class SchemaNode(NodeBase):
|
|
29
37
|
module: str
|
fastapi_voyager/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.5.
|
|
2
|
+
__version__ = "0.5.3"
|
fastapi_voyager/voyager.py
CHANGED
|
@@ -83,6 +83,7 @@ class Voyager:
|
|
|
83
83
|
# add route and create links
|
|
84
84
|
route_id = f'{route.endpoint.__name__}_{route.path.replace("/", "_")}'
|
|
85
85
|
route_name = route.endpoint.__name__
|
|
86
|
+
route_module = route.endpoint.__module__
|
|
86
87
|
|
|
87
88
|
# filter by route_name (route.id) if provided
|
|
88
89
|
if self.route_name is not None and route_id != self.route_name:
|
|
@@ -91,6 +92,7 @@ class Voyager:
|
|
|
91
92
|
route_obj = Route(
|
|
92
93
|
id=route_id,
|
|
93
94
|
name=route_name,
|
|
95
|
+
module=route_module,
|
|
94
96
|
vscode_link=get_vscode_link(route.endpoint) if self.load_meta else '',
|
|
95
97
|
source_code=inspect.getsource(route.endpoint) if self.load_meta else ''
|
|
96
98
|
)
|
|
@@ -182,6 +184,8 @@ class Voyager:
|
|
|
182
184
|
update_forward_refs(schema)
|
|
183
185
|
self.add_to_node_set(schema)
|
|
184
186
|
|
|
187
|
+
base_fields = set()
|
|
188
|
+
|
|
185
189
|
# handle schema inside ensure_subset(schema)
|
|
186
190
|
if subset_reference := getattr(schema, const.ENSURE_SUBSET_REFERENCE, None):
|
|
187
191
|
if is_inheritance_of_pydantic_base(subset_reference):
|
|
@@ -198,6 +202,12 @@ class Voyager:
|
|
|
198
202
|
# handle bases
|
|
199
203
|
for base_class in schema.__bases__:
|
|
200
204
|
if is_inheritance_of_pydantic_base(base_class):
|
|
205
|
+
# collect base class field names to avoid duplicating inherited fields
|
|
206
|
+
try:
|
|
207
|
+
base_fields.update(getattr(base_class, 'model_fields', {}).keys())
|
|
208
|
+
except Exception:
|
|
209
|
+
# be defensive in case of unconventional BaseModel subclasses
|
|
210
|
+
pass
|
|
201
211
|
self.add_to_node_set(base_class)
|
|
202
212
|
self.add_to_link_set(
|
|
203
213
|
source=self.generate_node_head(full_class_name(schema)),
|
|
@@ -209,6 +219,9 @@ class Voyager:
|
|
|
209
219
|
|
|
210
220
|
# handle fields
|
|
211
221
|
for k, v in schema.model_fields.items():
|
|
222
|
+
# skip fields inherited from base classes
|
|
223
|
+
if k in base_fields:
|
|
224
|
+
continue
|
|
212
225
|
annos = get_core_types(v.annotation)
|
|
213
226
|
for anno in annos:
|
|
214
227
|
if anno and is_inheritance_of_pydantic_base(anno):
|
|
@@ -1,48 +1,69 @@
|
|
|
1
1
|
import { GraphUI } from "../graph-ui.js";
|
|
2
|
-
const { defineComponent, ref, onMounted,
|
|
2
|
+
const { defineComponent, ref, onMounted, nextTick } = window.Vue;
|
|
3
3
|
|
|
4
|
-
// Simple dialog-embeddable component that renders a DOT graph.
|
|
5
|
-
// Props:
|
|
6
|
-
// - dot: String (required) the DOT source to render
|
|
7
|
-
// Emits:
|
|
8
|
-
// - close: when the close button is clicked
|
|
9
4
|
export default defineComponent({
|
|
10
5
|
name: "RenderGraph",
|
|
11
6
|
props: {
|
|
12
|
-
|
|
7
|
+
coreData: { type: [Object, Array], required: false, default: null },
|
|
13
8
|
},
|
|
14
9
|
emits: ["close"],
|
|
15
10
|
setup(props, { emit }) {
|
|
16
11
|
const containerId = `graph-render-${Math.random().toString(36).slice(2, 9)}`;
|
|
17
12
|
const hasRendered = ref(false);
|
|
13
|
+
const loading = ref(false);
|
|
18
14
|
let graphInstance = null;
|
|
19
15
|
|
|
20
|
-
async function
|
|
21
|
-
if (!props.dot) return;
|
|
16
|
+
async function ensureGraph() {
|
|
22
17
|
await nextTick();
|
|
23
18
|
if (!graphInstance) {
|
|
24
19
|
graphInstance = new GraphUI(`#${containerId}`);
|
|
25
20
|
}
|
|
26
|
-
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function renderFromDot(dotText) {
|
|
24
|
+
if (!dotText) return;
|
|
25
|
+
await ensureGraph();
|
|
26
|
+
await graphInstance.render(dotText);
|
|
27
27
|
hasRendered.value = true;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
async function renderFromCoreData() {
|
|
31
|
+
if (!props.coreData) return;
|
|
32
|
+
loading.value = true;
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch("/dot-render-core-data", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json" },
|
|
37
|
+
body: JSON.stringify(props.coreData),
|
|
38
|
+
});
|
|
39
|
+
const dotText = await res.text();
|
|
40
|
+
await renderFromDot(dotText);
|
|
41
|
+
if (window.Quasar?.Notify) {
|
|
42
|
+
window.Quasar.Notify.create({ type: "positive", message: "Rendered" });
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error("Render from core data failed", e);
|
|
46
|
+
if (window.Quasar?.Notify) {
|
|
47
|
+
window.Quasar.Notify.create({ type: "negative", message: "Render failed" });
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
loading.value = false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function reload() {
|
|
55
|
+
await renderFromCoreData();
|
|
56
|
+
}
|
|
57
|
+
|
|
30
58
|
onMounted(async () => {
|
|
31
|
-
await
|
|
59
|
+
await reload();
|
|
32
60
|
});
|
|
33
61
|
|
|
34
|
-
watch(
|
|
35
|
-
() => props.dot,
|
|
36
|
-
async () => {
|
|
37
|
-
await renderDot();
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
|
|
41
62
|
function close() {
|
|
42
63
|
emit("close");
|
|
43
64
|
}
|
|
44
65
|
|
|
45
|
-
return { containerId, close, hasRendered };
|
|
66
|
+
return { containerId, close, hasRendered, reload, loading };
|
|
46
67
|
},
|
|
47
68
|
template: `
|
|
48
69
|
<div style="height:100%; position:relative; background:#fff;">
|
|
@@ -52,6 +73,13 @@ export default defineComponent({
|
|
|
52
73
|
@click="close"
|
|
53
74
|
style="position:absolute; top:6px; right:6px; z-index:11; background:rgba(255,255,255,0.85);"
|
|
54
75
|
/>
|
|
76
|
+
<q-btn
|
|
77
|
+
flat dense round icon="refresh"
|
|
78
|
+
aria-label="Reload"
|
|
79
|
+
:loading="loading"
|
|
80
|
+
@click="reload"
|
|
81
|
+
style="position:absolute; top:6px; right:46px; z-index:11; background:rgba(255,255,255,0.85);"
|
|
82
|
+
/>
|
|
55
83
|
<div :id="containerId" style="width:100%; height:100%; overflow:auto; background:#fafafa"></div>
|
|
56
84
|
</div>
|
|
57
85
|
`,
|
fastapi_voyager/web/index.html
CHANGED
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
|
|
251
251
|
<!-- Render Graph Dialog (from imported core data) -->
|
|
252
252
|
<q-dialog v-model="showRenderGraph" :maximized="true" :persistent="false">
|
|
253
|
-
<render-graph :
|
|
253
|
+
<render-graph :core-data="renderCoreData" @close="showRenderGraph = false" />
|
|
254
254
|
</q-dialog>
|
|
255
255
|
|
|
256
256
|
<div id="graph" style="width: 100%; flex: 1 1 auto; overflow: auto"></div>
|
fastapi_voyager/web/vue-main.js
CHANGED
|
@@ -37,8 +37,8 @@ const app = createApp({
|
|
|
37
37
|
const dumpJson = ref("");
|
|
38
38
|
const showImportDialog = ref(false);
|
|
39
39
|
const importJsonText = ref("");
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const showRenderGraph = ref(false);
|
|
41
|
+
const renderCoreData = ref(null);
|
|
42
42
|
const schemaName = ref(""); // used by detail dialog
|
|
43
43
|
const schemaFieldFilterSchema = ref(null); // external schemaName for schema-field-filter
|
|
44
44
|
const schemaCodeName = ref("");
|
|
@@ -222,19 +222,10 @@ const app = createApp({
|
|
|
222
222
|
}
|
|
223
223
|
return;
|
|
224
224
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
body: JSON.stringify(payloadObj),
|
|
230
|
-
});
|
|
231
|
-
const dotText = await res.text();
|
|
232
|
-
renderDotString.value = dotText;
|
|
233
|
-
showRenderGraph.value = true;
|
|
234
|
-
showImportDialog.value = false;
|
|
235
|
-
} catch (e) {
|
|
236
|
-
console.error("Import render failed", e);
|
|
237
|
-
}
|
|
225
|
+
// Move the request into RenderGraph component: pass the parsed object and let the component call /dot-render-core-data
|
|
226
|
+
renderCoreData.value = payloadObj;
|
|
227
|
+
showRenderGraph.value = true;
|
|
228
|
+
showImportDialog.value = false;
|
|
238
229
|
}
|
|
239
230
|
|
|
240
231
|
function showDialog() {
|
|
@@ -290,7 +281,7 @@ const app = createApp({
|
|
|
290
281
|
onImportConfirm,
|
|
291
282
|
// render graph dialog
|
|
292
283
|
showRenderGraph,
|
|
293
|
-
|
|
284
|
+
renderCoreData,
|
|
294
285
|
};
|
|
295
286
|
},
|
|
296
287
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-voyager
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Visualize FastAPI application's routing tree and dependencies
|
|
5
5
|
Project-URL: Homepage, https://github.com/allmonday/fastapi-voyager
|
|
6
6
|
Project-URL: Source, https://github.com/allmonday/fastapi-voyager
|
|
@@ -183,6 +183,13 @@ features
|
|
|
183
183
|
- [ ] abstract render module
|
|
184
184
|
- [ ] export voyager core data into json (for better debugging)
|
|
185
185
|
- [ ] add api to rebuild core data from json, and render it
|
|
186
|
+
- [ ] fix Generic case
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
class Paged(BaseModel, Generic[T]):
|
|
190
|
+
total: int
|
|
191
|
+
items: List[T]
|
|
192
|
+
```
|
|
186
193
|
|
|
187
194
|
bugs:
|
|
188
195
|
- [ ] fix duplicated link from class and parent class, it also break clicking highlight
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
fastapi_voyager/__init__.py,sha256=E5WTV_sYs2LK8I6jzA7AuvFU5a8_vjnDseC3DMha0iQ,149
|
|
2
2
|
fastapi_voyager/cli.py,sha256=scBD2ojOq0_sJ8Usu79MoGUvkEc_zlnBO6xK0grz7dI,10478
|
|
3
3
|
fastapi_voyager/filter.py,sha256=uZrVVMhHG5E7j1wdsiB02RAyoDdF1q8A4J04oCboYAU,4644
|
|
4
|
-
fastapi_voyager/module.py,sha256=
|
|
5
|
-
fastapi_voyager/render.py,sha256=
|
|
4
|
+
fastapi_voyager/module.py,sha256=Z2QHNmiLk6ZAJlm2nSmO875Q33TweSg8UxZSzIpU9zY,3499
|
|
5
|
+
fastapi_voyager/render.py,sha256=ctwad-KNbFajhgnA8OI8412s6s67UbV-dvZFXBt_Ssg,7410
|
|
6
6
|
fastapi_voyager/server.py,sha256=Eer6GdD9-EG9QGHlYmClF3MeVxw68amjK4iW_YZ5ShI,3751
|
|
7
|
-
fastapi_voyager/type.py,sha256=
|
|
7
|
+
fastapi_voyager/type.py,sha256=nad4WNxTcZFi7Mskw6p2W7v2Gs4f0giVLNoFjZlKmbA,1778
|
|
8
8
|
fastapi_voyager/type_helper.py,sha256=j7AiFXsfl4kaxshYtofbsqo08dIXiHvJ190soIzUdLk,8380
|
|
9
|
-
fastapi_voyager/version.py,sha256=
|
|
10
|
-
fastapi_voyager/voyager.py,sha256=
|
|
9
|
+
fastapi_voyager/version.py,sha256=Hb2z9BH1asz5RSe-xgzQIT7VN25qyDUyXYTS-6rOm5g,48
|
|
10
|
+
fastapi_voyager/voyager.py,sha256=qMXZrQsmUiBljhOFOUtXV--a5i7THz9l00UNoxDu_0w,10193
|
|
11
11
|
fastapi_voyager/web/graph-ui.js,sha256=eEjDnJVMvk35LdRoxcqX_fZxLFS9_bUrGAZL6K2O5C0,4176
|
|
12
12
|
fastapi_voyager/web/graphviz.svg.css,sha256=zDCjjpT0Idufu5YOiZI76PL70-avP3vTyzGPh9M85Do,1563
|
|
13
13
|
fastapi_voyager/web/graphviz.svg.js,sha256=lvAdbjHc-lMSk4GQp-iqYA2PCFX4RKnW7dFaoe0LUHs,16005
|
|
14
|
-
fastapi_voyager/web/index.html,sha256=
|
|
14
|
+
fastapi_voyager/web/index.html,sha256=vndU6K9BN5f75OMPPPzREF7xACmf31wTVyGuOYksiys,10461
|
|
15
15
|
fastapi_voyager/web/quasar.min.css,sha256=F5jQe7X2XT54VlvAaa2V3GsBFdVD-vxDZeaPLf6U9CU,203145
|
|
16
16
|
fastapi_voyager/web/quasar.min.js,sha256=h0ftyPMW_CRiyzeVfQqiup0vrVt4_QWojpqmpnpn07E,502974
|
|
17
|
-
fastapi_voyager/web/vue-main.js,sha256=
|
|
18
|
-
fastapi_voyager/web/component/render-graph.js,sha256=
|
|
17
|
+
fastapi_voyager/web/vue-main.js,sha256=tW0jRSSABMmnAqLDaIXMeXQBm0WWOKycsYesgg8ArZw,8798
|
|
18
|
+
fastapi_voyager/web/component/render-graph.js,sha256=8jfN-ik7Ckn5Frx01umjlYqP6i0HELDCcWQMp5zbIhI,2323
|
|
19
19
|
fastapi_voyager/web/component/route-code-display.js,sha256=NECC1OGcPCdDfbghtRJEnmFM6HmH5J3win2ibapWPeA,2649
|
|
20
20
|
fastapi_voyager/web/component/schema-code-display.js,sha256=oOusgTvCaWGnoKb-NBwu0SXqJJf2PTUtp3lUczokTBM,5515
|
|
21
21
|
fastapi_voyager/web/component/schema-field-filter.js,sha256=9WBjO6JJl2yf6OiiXoddMgvL32qTDu0PM-RxkkJ7t5M,6267
|
|
@@ -26,8 +26,8 @@ fastapi_voyager/web/icon/favicon-16x16.png,sha256=JC07jEzfIYxBIoQn_FHXvyHuxESdhW
|
|
|
26
26
|
fastapi_voyager/web/icon/favicon-32x32.png,sha256=C7v1h58cfWOsiLp9yOIZtlx-dLasBcq3NqpHVGRmpt4,1859
|
|
27
27
|
fastapi_voyager/web/icon/favicon.ico,sha256=tZolYIXkkBcFiYl1A8ksaXN2VjGamzcSdes838dLvNc,15406
|
|
28
28
|
fastapi_voyager/web/icon/site.webmanifest,sha256=ep4Hzh9zhmiZF2At3Fp1dQrYQuYF_3ZPZxc1KcGBvwQ,263
|
|
29
|
-
fastapi_voyager-0.5.
|
|
30
|
-
fastapi_voyager-0.5.
|
|
31
|
-
fastapi_voyager-0.5.
|
|
32
|
-
fastapi_voyager-0.5.
|
|
33
|
-
fastapi_voyager-0.5.
|
|
29
|
+
fastapi_voyager-0.5.3.dist-info/METADATA,sha256=TFMnB94SxSoEblutrjAYkSqopvPgeY5zMsMJhH86_KI,6878
|
|
30
|
+
fastapi_voyager-0.5.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
31
|
+
fastapi_voyager-0.5.3.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
|
|
32
|
+
fastapi_voyager-0.5.3.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
|
|
33
|
+
fastapi_voyager-0.5.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|