fastapi-voyager 0.9.4__tar.gz → 0.10.1__tar.gz
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-0.9.4 → fastapi_voyager-0.10.1}/PKG-INFO +19 -18
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/README.md +18 -17
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/render.py +5 -5
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/server.py +2 -2
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/version.py +1 -1
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/voyager.py +83 -58
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/index.html +67 -37
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/vue-main.js +57 -3
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/demo.py +5 -1
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/programatic.py +1 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/.gitignore +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/.python-version +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/LICENSE +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/pyproject.toml +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/__init__.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/cli.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/filter.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/module.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/type.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/type_helper.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/component/render-graph.js +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/component/route-code-display.js +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/component/schema-code-display.js +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/component/schema-field-filter.js +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/graph-ui.js +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/quasar.min.css +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/quasar.min.js +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/__init__.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/demo_anno.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/service/__init__.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/service/schema.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/test_analysis.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/test_filter.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/test_generic.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/test_import.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/test_module.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/tests/test_type_helper.py +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/uv.lock +0 -0
- {fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/voyager.jpg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-voyager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
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
|
|
@@ -163,18 +163,19 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
163
163
|
|
|
164
164
|
### backlog
|
|
165
165
|
- [ ] user can generate nodes/edges manually and connect to generated ones
|
|
166
|
-
- [ ] add owner
|
|
166
|
+
- [ ] eg: add owner
|
|
167
167
|
- [ ] add extra info for schema
|
|
168
168
|
- [ ] display standard ER diagram `hard`
|
|
169
169
|
- [ ] display potential invalid links
|
|
170
|
-
- [ ]
|
|
170
|
+
- [ ] optimize static resource (allow manually config url)
|
|
171
|
+
- [ ] improve search dialog
|
|
172
|
+
- [ ] add route/tag list
|
|
171
173
|
|
|
172
174
|
### in analysis
|
|
173
175
|
- [ ] click field to highlight links
|
|
174
176
|
- [ ] animation effect for edges
|
|
175
177
|
- [ ] customrized right click panel
|
|
176
178
|
- [ ] show own dependencies
|
|
177
|
-
- [ ] clean up fe code
|
|
178
179
|
|
|
179
180
|
### plan:
|
|
180
181
|
#### <0.9:
|
|
@@ -221,24 +222,24 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
221
222
|
- [x] schema detail panel show fields by default
|
|
222
223
|
- [x] adjust schema panel's height
|
|
223
224
|
- [x] show from base information in subset case
|
|
225
|
+
- 0.9.5
|
|
226
|
+
- [x] route list should have a max height
|
|
224
227
|
|
|
225
228
|
#### 0.10
|
|
229
|
+
- [x] refactor voyager.py tag -> route structure
|
|
230
|
+
- [x] fix missing route (tag has only one route which return primitive value)
|
|
231
|
+
- [x] make right panel resizable by dragging
|
|
232
|
+
- [x] allow closing tag expansion item
|
|
233
|
+
- [x] hide brief mode if not configured
|
|
234
|
+
- [x] add focus button to only show related nodes under current route/tag graph in dialog
|
|
235
|
+
|
|
236
|
+
#### 0.11
|
|
237
|
+
- [ ] enable/disable module cluster (to save space)
|
|
238
|
+
- [ ] fix layout issue when rendering huge graph
|
|
239
|
+
- [ ] logging information
|
|
226
240
|
- [ ] support opening route in swagger
|
|
227
241
|
- config docs path
|
|
228
|
-
- [ ] add http method for route
|
|
229
|
-
- [ ] enable/disable module cluster (may save space)
|
|
230
|
-
- [ ] logging information
|
|
231
242
|
- [ ] add tests
|
|
232
|
-
- [ ] hide brief mode if not configured
|
|
233
|
-
- [ ] optimize static resource
|
|
234
|
-
- [ ] show route count in tag expansion item
|
|
235
|
-
- [ ] route list show have a max height to trigger scrollable
|
|
236
|
-
- [ ] fix layout issue when rendering huge graph
|
|
237
|
-
|
|
238
|
-
#### 0.11
|
|
239
|
-
- [ ] improve user experience
|
|
240
|
-
- double click to show detail
|
|
241
|
-
- improve search dialog
|
|
242
243
|
|
|
243
244
|
#### 0.12
|
|
244
245
|
- [ ] integration with pydantic-resolve
|
|
@@ -247,7 +248,7 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
247
248
|
|
|
248
249
|
#### 0.13
|
|
249
250
|
- [ ] config release pipeline
|
|
250
|
-
|
|
251
|
+
|
|
251
252
|
|
|
252
253
|
## Using with pydantic-resolve
|
|
253
254
|
|
|
@@ -136,18 +136,19 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
136
136
|
|
|
137
137
|
### backlog
|
|
138
138
|
- [ ] user can generate nodes/edges manually and connect to generated ones
|
|
139
|
-
- [ ] add owner
|
|
139
|
+
- [ ] eg: add owner
|
|
140
140
|
- [ ] add extra info for schema
|
|
141
141
|
- [ ] display standard ER diagram `hard`
|
|
142
142
|
- [ ] display potential invalid links
|
|
143
|
-
- [ ]
|
|
143
|
+
- [ ] optimize static resource (allow manually config url)
|
|
144
|
+
- [ ] improve search dialog
|
|
145
|
+
- [ ] add route/tag list
|
|
144
146
|
|
|
145
147
|
### in analysis
|
|
146
148
|
- [ ] click field to highlight links
|
|
147
149
|
- [ ] animation effect for edges
|
|
148
150
|
- [ ] customrized right click panel
|
|
149
151
|
- [ ] show own dependencies
|
|
150
|
-
- [ ] clean up fe code
|
|
151
152
|
|
|
152
153
|
### plan:
|
|
153
154
|
#### <0.9:
|
|
@@ -194,24 +195,24 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
194
195
|
- [x] schema detail panel show fields by default
|
|
195
196
|
- [x] adjust schema panel's height
|
|
196
197
|
- [x] show from base information in subset case
|
|
198
|
+
- 0.9.5
|
|
199
|
+
- [x] route list should have a max height
|
|
197
200
|
|
|
198
201
|
#### 0.10
|
|
202
|
+
- [x] refactor voyager.py tag -> route structure
|
|
203
|
+
- [x] fix missing route (tag has only one route which return primitive value)
|
|
204
|
+
- [x] make right panel resizable by dragging
|
|
205
|
+
- [x] allow closing tag expansion item
|
|
206
|
+
- [x] hide brief mode if not configured
|
|
207
|
+
- [x] add focus button to only show related nodes under current route/tag graph in dialog
|
|
208
|
+
|
|
209
|
+
#### 0.11
|
|
210
|
+
- [ ] enable/disable module cluster (to save space)
|
|
211
|
+
- [ ] fix layout issue when rendering huge graph
|
|
212
|
+
- [ ] logging information
|
|
199
213
|
- [ ] support opening route in swagger
|
|
200
214
|
- config docs path
|
|
201
|
-
- [ ] add http method for route
|
|
202
|
-
- [ ] enable/disable module cluster (may save space)
|
|
203
|
-
- [ ] logging information
|
|
204
215
|
- [ ] add tests
|
|
205
|
-
- [ ] hide brief mode if not configured
|
|
206
|
-
- [ ] optimize static resource
|
|
207
|
-
- [ ] show route count in tag expansion item
|
|
208
|
-
- [ ] route list show have a max height to trigger scrollable
|
|
209
|
-
- [ ] fix layout issue when rendering huge graph
|
|
210
|
-
|
|
211
|
-
#### 0.11
|
|
212
|
-
- [ ] improve user experience
|
|
213
|
-
- double click to show detail
|
|
214
|
-
- improve search dialog
|
|
215
216
|
|
|
216
217
|
#### 0.12
|
|
217
218
|
- [ ] integration with pydantic-resolve
|
|
@@ -220,7 +221,7 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
220
221
|
|
|
221
222
|
#### 0.13
|
|
222
223
|
- [ ] config release pipeline
|
|
223
|
-
|
|
224
|
+
|
|
224
225
|
|
|
225
226
|
## Using with pydantic-resolve
|
|
226
227
|
|
|
@@ -62,9 +62,8 @@ class Renderer:
|
|
|
62
62
|
else:
|
|
63
63
|
raise ValueError(f'Unknown link type: {link.type}')
|
|
64
64
|
|
|
65
|
-
def
|
|
65
|
+
def render_module_schema_content(self, mods: list[ModuleNode]) -> str:
|
|
66
66
|
module_color_flag = set(self.module_color.keys())
|
|
67
|
-
print(module_color_flag)
|
|
68
67
|
|
|
69
68
|
def render_module_schema(mod: ModuleNode) -> str:
|
|
70
69
|
color: Optional[str] = None
|
|
@@ -104,7 +103,7 @@ class Renderer:
|
|
|
104
103
|
inner_nodes = [
|
|
105
104
|
f'''
|
|
106
105
|
"{r.id}" [
|
|
107
|
-
label = " {r.name}
|
|
106
|
+
label = " {r.name} | {r.response_schema} "
|
|
108
107
|
margin="0.5,0.1"
|
|
109
108
|
shape = "record"
|
|
110
109
|
];''' for r in mod.routes
|
|
@@ -122,7 +121,7 @@ class Renderer:
|
|
|
122
121
|
{child_str}
|
|
123
122
|
}}'''
|
|
124
123
|
|
|
125
|
-
def render_dot(self, tags: list[Tag], routes: list[Route], nodes: list[SchemaNode], links: list[Link]) -> str:
|
|
124
|
+
def render_dot(self, tags: list[Tag], routes: list[Route], nodes: list[SchemaNode], links: list[Link], spline_line=False) -> str:
|
|
126
125
|
module_schemas = build_module_schema_tree(nodes)
|
|
127
126
|
module_routes = build_module_route_tree(routes)
|
|
128
127
|
|
|
@@ -136,7 +135,7 @@ class Renderer:
|
|
|
136
135
|
])
|
|
137
136
|
|
|
138
137
|
|
|
139
|
-
module_schemas_str = self.
|
|
138
|
+
module_schemas_str = self.render_module_schema_content(module_schemas)
|
|
140
139
|
module_routes_str = '\n'.join(self.render_module_route(m) for m in module_routes)
|
|
141
140
|
link_str = '\n'.join(self.render_link(link) for link in links)
|
|
142
141
|
|
|
@@ -144,6 +143,7 @@ class Renderer:
|
|
|
144
143
|
digraph world {{
|
|
145
144
|
pad="0.5"
|
|
146
145
|
nodesep=0.8
|
|
146
|
+
{'splines=line' if spline_line else ''}
|
|
147
147
|
fontname="Helvetica,Arial,sans-serif"
|
|
148
148
|
node [fontname="Helvetica,Arial,sans-serif"]
|
|
149
149
|
edge [
|
|
@@ -19,6 +19,7 @@ class OptionParam(BaseModel):
|
|
|
19
19
|
tags: list[Tag]
|
|
20
20
|
schemas: list[SchemaNode]
|
|
21
21
|
dot: str
|
|
22
|
+
enable_brief_mode: bool
|
|
22
23
|
|
|
23
24
|
class Payload(BaseModel):
|
|
24
25
|
tags: Optional[list[str]] = None
|
|
@@ -53,11 +54,10 @@ def create_route(
|
|
|
53
54
|
schemas = voyager.nodes[:]
|
|
54
55
|
schemas.sort(key=lambda s: s.name)
|
|
55
56
|
|
|
56
|
-
return OptionParam(tags=tags, schemas=schemas, dot=dot)
|
|
57
|
+
return OptionParam(tags=tags, schemas=schemas, dot=dot, enable_brief_mode=bool(module_prefix))
|
|
57
58
|
|
|
58
59
|
@router.post("/dot", response_class=PlainTextResponse)
|
|
59
60
|
def get_filtered_dot(payload: Payload) -> str:
|
|
60
|
-
print(payload)
|
|
61
61
|
voyager = Voyager(
|
|
62
62
|
include_tags=payload.tags,
|
|
63
63
|
schema=payload.schema_name,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.10.1"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
from pydantic import BaseModel
|
|
2
2
|
from fastapi import FastAPI, routing
|
|
3
3
|
from fastapi_voyager.type_helper import (
|
|
4
4
|
get_core_types,
|
|
@@ -54,9 +54,14 @@ class Voyager:
|
|
|
54
54
|
|
|
55
55
|
def _get_available_route(self, app: FastAPI):
|
|
56
56
|
for route in app.routes:
|
|
57
|
-
if isinstance(route, routing.APIRoute)
|
|
57
|
+
if isinstance(route, routing.APIRoute):
|
|
58
58
|
yield route
|
|
59
59
|
|
|
60
|
+
def analysis_route(self, route: routing.APIRoute):
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
def analysis_tags(self, tag: str):
|
|
64
|
+
...
|
|
60
65
|
|
|
61
66
|
def analysis(self, app: FastAPI):
|
|
62
67
|
"""
|
|
@@ -68,67 +73,76 @@ class Voyager:
|
|
|
68
73
|
"""
|
|
69
74
|
schemas: list[type[BaseModel]] = []
|
|
70
75
|
|
|
76
|
+
# First, group all routes by tag
|
|
77
|
+
routes_by_tag: dict[str, list] = {}
|
|
71
78
|
for route in self._get_available_route(app):
|
|
72
|
-
# check tags
|
|
73
79
|
tags = getattr(route, 'tags', None)
|
|
74
|
-
route_tag = tags[0] if tags else '__default__'
|
|
75
|
-
if self.include_tags and route_tag not in self.include_tags:
|
|
76
|
-
continue
|
|
77
80
|
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
tag_obj = Tag(id=tag_id, name=route_tag, routes=[])
|
|
82
|
-
self.tag_set[tag_id] = tag_obj
|
|
83
|
-
self.tags.append(tag_obj)
|
|
84
|
-
|
|
85
|
-
# add route and create links
|
|
86
|
-
route_id = full_class_name(route.endpoint)
|
|
87
|
-
route_name = route.endpoint.__name__
|
|
88
|
-
route_module = route.endpoint.__module__
|
|
89
|
-
|
|
90
|
-
# filter by route_name (route.id) if provided
|
|
91
|
-
if self.route_name is not None and route_id != self.route_name:
|
|
92
|
-
continue
|
|
81
|
+
# using multiple tags is harmful, it's not recommended and will not be supported
|
|
82
|
+
route_tag = tags[0] if tags else '__default__'
|
|
83
|
+
routes_by_tag.setdefault(route_tag, []).append(route)
|
|
93
84
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
# Then filter by include_tags if provided
|
|
86
|
+
if self.include_tags:
|
|
87
|
+
filtered_routes_by_tag = {tag: routes for tag, routes in routes_by_tag.items()
|
|
88
|
+
if tag in self.include_tags}
|
|
89
|
+
else:
|
|
90
|
+
filtered_routes_by_tag = routes_by_tag
|
|
98
91
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
source_origin=tag_id,
|
|
102
|
-
target=route_id,
|
|
103
|
-
target_origin=route_id,
|
|
104
|
-
type='tag_route'
|
|
105
|
-
))
|
|
92
|
+
# Process filtered routes
|
|
93
|
+
for route_tag, routes in filtered_routes_by_tag.items():
|
|
106
94
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
95
|
+
tag_id = f'tag__{route_tag}'
|
|
96
|
+
tag_obj = Tag(id=tag_id, name=route_tag, routes=[])
|
|
97
|
+
self.tags.append(tag_obj)
|
|
98
|
+
|
|
99
|
+
for route in routes:
|
|
100
|
+
# add route and create links
|
|
101
|
+
route_id = full_class_name(route.endpoint)
|
|
102
|
+
route_name = route.endpoint.__name__
|
|
103
|
+
route_module = route.endpoint.__module__
|
|
104
|
+
|
|
105
|
+
# filter by route_name (route.id) if provided
|
|
106
|
+
if self.route_name is not None and route_id != self.route_name:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
is_primitive_response = is_non_pydantic_type(route.response_model)
|
|
110
|
+
# filter primitive route if needed
|
|
111
|
+
if self.hide_primitive_route and is_primitive_response:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
self.links.append(Link(
|
|
115
|
+
source=tag_id,
|
|
116
|
+
source_origin=tag_id,
|
|
117
|
+
target=route_id,
|
|
118
|
+
target_origin=route_id,
|
|
119
|
+
type='tag_route'
|
|
120
|
+
))
|
|
121
|
+
|
|
122
|
+
route_obj = Route(
|
|
123
|
+
id=route_id,
|
|
124
|
+
name=route_name,
|
|
125
|
+
module=route_module,
|
|
126
|
+
response_schema=get_type_name(route.response_model),
|
|
127
|
+
is_primitive=is_primitive_response
|
|
128
|
+
)
|
|
129
|
+
self.routes.append(route_obj)
|
|
130
|
+
tag_obj.routes.append(route_obj)
|
|
131
|
+
|
|
132
|
+
# add response_models and create links from route -> response_model
|
|
133
|
+
for schema in get_core_types(route.response_model):
|
|
134
|
+
if schema and issubclass(schema, BaseModel):
|
|
135
|
+
is_primitive_response = False
|
|
136
|
+
target_name = full_class_name(schema)
|
|
137
|
+
self.links.append(Link(
|
|
138
|
+
source=route_id,
|
|
139
|
+
source_origin=route_id,
|
|
140
|
+
target=self.generate_node_head(target_name),
|
|
141
|
+
target_origin=target_name,
|
|
142
|
+
type='route_to_schema'
|
|
143
|
+
))
|
|
144
|
+
|
|
145
|
+
schemas.append(schema)
|
|
132
146
|
|
|
133
147
|
for s in schemas:
|
|
134
148
|
self.analysis_schemas(s)
|
|
@@ -271,6 +285,12 @@ class Voyager:
|
|
|
271
285
|
schema=self.schema
|
|
272
286
|
)
|
|
273
287
|
|
|
288
|
+
def handle_hide(self, tags, routes, links):
|
|
289
|
+
if self.include_tags:
|
|
290
|
+
return [], routes, [lk for lk in links if lk.type != 'tag_route']
|
|
291
|
+
else:
|
|
292
|
+
return tags, routes, links
|
|
293
|
+
|
|
274
294
|
def render_dot(self):
|
|
275
295
|
_tags, _routes, _nodes, _links = filter_graph(
|
|
276
296
|
schema=self.schema,
|
|
@@ -281,7 +301,10 @@ class Voyager:
|
|
|
281
301
|
links=self.links,
|
|
282
302
|
node_set=self.node_set,
|
|
283
303
|
)
|
|
304
|
+
|
|
284
305
|
renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema)
|
|
306
|
+
|
|
307
|
+
_tags, _routes, _links = self.handle_hide(_tags, _routes, _links)
|
|
285
308
|
return renderer.render_dot(_tags, _routes, _nodes, _links)
|
|
286
309
|
|
|
287
310
|
def render_brief_dot(self, module_prefix: str | None = None):
|
|
@@ -293,4 +316,6 @@ class Voyager:
|
|
|
293
316
|
links=self.links,
|
|
294
317
|
)
|
|
295
318
|
renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=None)
|
|
296
|
-
|
|
319
|
+
|
|
320
|
+
_tags, _routes, _links = self.handle_hide(_tags, _routes, _links)
|
|
321
|
+
return renderer.render_dot(_tags, _routes, _nodes, _links, True)
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
</div>
|
|
87
87
|
<div class="col-auto q-ml-auto">
|
|
88
88
|
<q-toggle
|
|
89
|
+
v-if="state.enableBriefMode"
|
|
89
90
|
class="q-mr-md"
|
|
90
91
|
v-model="state.brief"
|
|
91
92
|
label="Brief Mode"
|
|
@@ -163,12 +164,29 @@
|
|
|
163
164
|
|
|
164
165
|
<q-drawer
|
|
165
166
|
v-model="state.detailDrawer"
|
|
166
|
-
width="
|
|
167
|
+
:width="state.drawerWidth"
|
|
167
168
|
side="right"
|
|
169
|
+
style="border-left: 1px solid #888;"
|
|
168
170
|
overlay
|
|
169
171
|
bordered
|
|
170
172
|
>
|
|
171
|
-
|
|
173
|
+
<!-- 可拖拽的调整栏 -->
|
|
174
|
+
<div
|
|
175
|
+
@mousedown="startDragDrawer"
|
|
176
|
+
style="
|
|
177
|
+
position: absolute;
|
|
178
|
+
left: -3px;
|
|
179
|
+
top: 0;
|
|
180
|
+
width: 6px;
|
|
181
|
+
height: 100%;
|
|
182
|
+
cursor: col-resize;
|
|
183
|
+
background: transparent;
|
|
184
|
+
z-index: 10;
|
|
185
|
+
"
|
|
186
|
+
title="drag to resize"
|
|
187
|
+
></div>
|
|
188
|
+
|
|
189
|
+
<div style="z-index: 11; position: absolute; left: -17px; top: 9px">
|
|
172
190
|
<q-btn
|
|
173
191
|
@click="state.detailDrawer = !state.detailDrawer"
|
|
174
192
|
round
|
|
@@ -208,60 +226,69 @@
|
|
|
208
226
|
v-for="tag in state.rawTags"
|
|
209
227
|
:key="tag.name"
|
|
210
228
|
expand-separator
|
|
211
|
-
:model-value="state.
|
|
229
|
+
:model-value="state._tag === tag.name"
|
|
212
230
|
@update:model-value="(val) => toggleTag(tag.name, val)"
|
|
213
231
|
:header-class="state.tag === tag.name ? 'text-primary text-bold' : ''"
|
|
214
232
|
content-class="q-pa-none"
|
|
215
233
|
>
|
|
216
234
|
<template #header>
|
|
217
235
|
<div class="row items-center" style="width: 100%">
|
|
218
|
-
<div class="row items-
|
|
236
|
+
<div class="row items-center">
|
|
219
237
|
<q-icon
|
|
220
238
|
class="q-mr-sm"
|
|
221
239
|
:name="state.tag == tag.name ? 'folder' : 'folder_open'"
|
|
222
240
|
></q-icon>
|
|
223
|
-
<span>{{ tag.name }}</span>
|
|
241
|
+
<span>{{ tag.name }} <q-chip class="q-ml-sm" dense>{{ tag.routes.length }}</q-chip></span>
|
|
224
242
|
</div>
|
|
225
243
|
</div>
|
|
226
244
|
</template>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
@click="selectRoute(route.id)"
|
|
237
|
-
>
|
|
238
|
-
<q-item-section>
|
|
239
|
-
<span class="q-ml-lg" style="white-space: nowrap">
|
|
240
|
-
<q-icon
|
|
241
|
-
class="q-mr-sm"
|
|
242
|
-
name="data_object"
|
|
243
|
-
></q-icon>
|
|
244
|
-
{{ route.name }}
|
|
245
|
-
</span>
|
|
246
|
-
</q-item-section>
|
|
247
|
-
</q-item>
|
|
248
|
-
<q-item
|
|
249
|
-
v-if="!tag.routes || tag.routes.length === 0"
|
|
250
|
-
dense
|
|
251
|
-
>
|
|
252
|
-
<q-item-section class="text-grey-6"
|
|
253
|
-
>No routes</q-item-section
|
|
245
|
+
<q-list separator style="overflow: auto; max-height: 60vh;">
|
|
246
|
+
<q-item
|
|
247
|
+
v-for="route in (state.hidePrimitiveRoute ? tag.routes.filter(r => !r.is_primitive) :tag.routes || [])"
|
|
248
|
+
:key="route.id"
|
|
249
|
+
clickable
|
|
250
|
+
v-ripple
|
|
251
|
+
:active="state.routeId === route.id"
|
|
252
|
+
active-class=""
|
|
253
|
+
@click="selectRoute(route.id)"
|
|
254
254
|
>
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
<q-item-section>
|
|
256
|
+
<span class="q-ml-lg" style="white-space: nowrap">
|
|
257
|
+
<q-icon
|
|
258
|
+
class="q-mr-sm"
|
|
259
|
+
name="data_object"
|
|
260
|
+
></q-icon>
|
|
261
|
+
{{ route.name }}
|
|
262
|
+
</span>
|
|
263
|
+
</q-item-section>
|
|
264
|
+
</q-item>
|
|
265
|
+
<q-item
|
|
266
|
+
v-if="!tag.routes || tag.routes.length === 0"
|
|
267
|
+
dense
|
|
268
|
+
>
|
|
269
|
+
<q-item-section class="text-grey-6"
|
|
270
|
+
>No routes</q-item-section
|
|
271
|
+
>
|
|
272
|
+
</q-item>
|
|
273
|
+
</q-list>
|
|
274
|
+
</q-scroll-area>
|
|
257
275
|
</q-expansion-item>
|
|
258
276
|
</q-list>
|
|
259
|
-
</q-scroll-area>
|
|
260
277
|
</div>
|
|
261
278
|
</template>
|
|
262
279
|
|
|
263
280
|
<template #after>
|
|
264
|
-
<div
|
|
281
|
+
<div style="position: relative; width: 100%; height: 100%;">
|
|
282
|
+
<div id="graph" class="fit"></div>
|
|
283
|
+
<q-toggle
|
|
284
|
+
v-model="state.focus"
|
|
285
|
+
v-show="schemaCodeName"
|
|
286
|
+
@update:model-value="val => onFocusChange(val)"
|
|
287
|
+
label="Focus"
|
|
288
|
+
style="position: absolute; left: 8px; top: 8px; z-index: 10; background: rgba(255,255,255,0.85); border-radius: 4px; padding: 2px 8px;"
|
|
289
|
+
size="sm"
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
265
292
|
</template>
|
|
266
293
|
</q-splitter>
|
|
267
294
|
</q-page-container>
|
|
@@ -291,7 +318,10 @@
|
|
|
291
318
|
|
|
292
319
|
<q-dialog v-model="showRouteDetail" seamless position="bottom">
|
|
293
320
|
<q-card style="width: 1100px; max-width: 1100px; max-height: 40vh">
|
|
294
|
-
<route-code-display
|
|
321
|
+
<route-code-display
|
|
322
|
+
:route-id="routeCodeId"
|
|
323
|
+
@close="showRouteDetail=false"
|
|
324
|
+
/>
|
|
295
325
|
</q-card>
|
|
296
326
|
</q-dialog>
|
|
297
327
|
|
|
@@ -10,6 +10,7 @@ const app = createApp({
|
|
|
10
10
|
const state = reactive({
|
|
11
11
|
// options and selections
|
|
12
12
|
tag: null, // picked tag
|
|
13
|
+
_tag: null, // display tag
|
|
13
14
|
routeId: null, // picked route
|
|
14
15
|
schemaId: null, // picked schema
|
|
15
16
|
showFields: "object",
|
|
@@ -18,7 +19,9 @@ const app = createApp({
|
|
|
18
19
|
{ label: "Object fields", value: "object" },
|
|
19
20
|
{ label: "All fields", value: "all" },
|
|
20
21
|
],
|
|
22
|
+
enableBriefMode: false,
|
|
21
23
|
brief: false,
|
|
24
|
+
focus: false,
|
|
22
25
|
hidePrimitiveRoute: false,
|
|
23
26
|
generating: false,
|
|
24
27
|
rawTags: [], // [{ name, routes: [{ id, name }] }]
|
|
@@ -28,6 +31,7 @@ const app = createApp({
|
|
|
28
31
|
// Splitter size (left panel width in px)
|
|
29
32
|
splitter: 300,
|
|
30
33
|
detailDrawer: false,
|
|
34
|
+
drawerWidth: 500, // drawer 宽度
|
|
31
35
|
});
|
|
32
36
|
|
|
33
37
|
const showDetail = ref(false);
|
|
@@ -70,6 +74,7 @@ const app = createApp({
|
|
|
70
74
|
acc[r.id] = r;
|
|
71
75
|
return acc;
|
|
72
76
|
}, {});
|
|
77
|
+
state.enableBriefMode = data.enable_brief_mode || false;
|
|
73
78
|
|
|
74
79
|
// default route options placeholder
|
|
75
80
|
} catch (e) {
|
|
@@ -79,18 +84,30 @@ const app = createApp({
|
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
async function
|
|
87
|
+
async function onFocusChange(val) {
|
|
88
|
+
if (val) {
|
|
89
|
+
await onGenerate(false, schemaCodeName.value)
|
|
90
|
+
} else {
|
|
91
|
+
await onGenerate(false, null)
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
const ele = $(`[data-name='${schemaCodeName.value}'] polygon`)
|
|
94
|
+
debugger
|
|
95
|
+
ele.click()
|
|
96
|
+
}, 1)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function onGenerate(resetZoom = true, schema_name = null) {
|
|
83
101
|
state.generating = true;
|
|
84
102
|
try {
|
|
85
103
|
const payload = {
|
|
86
104
|
tags: state.tag ? [state.tag] : null,
|
|
87
|
-
schema_name:
|
|
105
|
+
schema_name: schema_name || null,
|
|
88
106
|
route_name: state.routeId || null,
|
|
89
107
|
show_fields: state.showFields,
|
|
90
108
|
brief: state.brief,
|
|
91
109
|
hide_primitive_route: state.hidePrimitiveRoute,
|
|
92
110
|
};
|
|
93
|
-
|
|
94
111
|
const res = await fetch("dot", {
|
|
95
112
|
method: "POST",
|
|
96
113
|
headers: { "Content-Type": "application/json" },
|
|
@@ -119,6 +136,7 @@ const app = createApp({
|
|
|
119
136
|
resetCb: () => {
|
|
120
137
|
state.detailDrawer = false;
|
|
121
138
|
showRouteDetail.value = false;
|
|
139
|
+
schemaCodeName.value = ''
|
|
122
140
|
}
|
|
123
141
|
});
|
|
124
142
|
|
|
@@ -198,15 +216,23 @@ const app = createApp({
|
|
|
198
216
|
state.schemaId = null;
|
|
199
217
|
// state.showFields = "object";
|
|
200
218
|
state.brief = false;
|
|
219
|
+
state.focus = false
|
|
220
|
+
schemaCodeName.value = ''
|
|
201
221
|
onGenerate();
|
|
202
222
|
}
|
|
203
223
|
|
|
204
224
|
function toggleTag(tagName, expanded = null) {
|
|
205
225
|
if (expanded === true) {
|
|
226
|
+
state._tag = tagName;
|
|
206
227
|
state.tag = tagName;
|
|
207
228
|
state.routeId = "";
|
|
229
|
+
state.focus = false
|
|
230
|
+
schemaCodeName.value = ''
|
|
208
231
|
onGenerate();
|
|
232
|
+
} else {
|
|
233
|
+
state._tag = null
|
|
209
234
|
}
|
|
235
|
+
|
|
210
236
|
state.detailDrawer = false;
|
|
211
237
|
showRouteDetail.value = false;
|
|
212
238
|
}
|
|
@@ -219,6 +245,8 @@ const app = createApp({
|
|
|
219
245
|
}
|
|
220
246
|
state.detailDrawer = false;
|
|
221
247
|
showRouteDetail.value = false;
|
|
248
|
+
state.focus = false
|
|
249
|
+
schemaCodeName.value = ''
|
|
222
250
|
onGenerate();
|
|
223
251
|
}
|
|
224
252
|
|
|
@@ -237,6 +265,30 @@ const app = createApp({
|
|
|
237
265
|
onGenerate(false);
|
|
238
266
|
}
|
|
239
267
|
|
|
268
|
+
function startDragDrawer(e) {
|
|
269
|
+
const startX = e.clientX;
|
|
270
|
+
const startWidth = state.drawerWidth;
|
|
271
|
+
|
|
272
|
+
function onMouseMove(moveEvent) {
|
|
273
|
+
const deltaX = startX - moveEvent.clientX;
|
|
274
|
+
const newWidth = Math.max(300, Math.min(800, startWidth + deltaX));
|
|
275
|
+
state.drawerWidth = newWidth;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function onMouseUp() {
|
|
279
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
280
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
281
|
+
document.body.style.cursor = '';
|
|
282
|
+
document.body.style.userSelect = '';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
286
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
287
|
+
document.body.style.cursor = 'col-resize';
|
|
288
|
+
document.body.style.userSelect = 'none';
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
}
|
|
291
|
+
|
|
240
292
|
onMounted(async () => {
|
|
241
293
|
await loadInitial();
|
|
242
294
|
});
|
|
@@ -272,6 +324,8 @@ const app = createApp({
|
|
|
272
324
|
showRenderGraph,
|
|
273
325
|
renderCoreData,
|
|
274
326
|
toggleShowField,
|
|
327
|
+
startDragDrawer,
|
|
328
|
+
onFocusChange
|
|
275
329
|
};
|
|
276
330
|
},
|
|
277
331
|
});
|
|
@@ -7,7 +7,7 @@ import tests.service.schema as serv
|
|
|
7
7
|
|
|
8
8
|
app = FastAPI(title="Demo API", description="A demo FastAPI application for router visualization")
|
|
9
9
|
|
|
10
|
-
@app.get("/sprints", tags=['for-restapi'], response_model=list[serv.Sprint])
|
|
10
|
+
@app.get("/sprints", tags=['for-restapi', 'group_a'], response_model=list[serv.Sprint])
|
|
11
11
|
def get_sprint():
|
|
12
12
|
return []
|
|
13
13
|
|
|
@@ -96,4 +96,8 @@ def get_page_test_2():
|
|
|
96
96
|
|
|
97
97
|
@app.get("/page_test_3/", tags=['for-page'], response_model=bool)
|
|
98
98
|
def get_page_test_3_long_long_long_name():
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
@app.get("/page_test_4/", tags=['for-page', 'group_b'])
|
|
102
|
+
def get_page_test_3_no_response_model():
|
|
99
103
|
return True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/component/render-graph.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/apple-touch-icon.png
RENAMED
|
File without changes
|
{fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/favicon-16x16.png
RENAMED
|
File without changes
|
{fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/favicon-32x32.png
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_voyager-0.9.4 → fastapi_voyager-0.10.1}/src/fastapi_voyager/web/icon/site.webmanifest
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|