fastapi-voyager 0.8.2__tar.gz → 0.9.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.8.2 → fastapi_voyager-0.9.1}/PKG-INFO +58 -24
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/README.md +57 -23
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/__init__.py +4 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/cli.py +1 -1
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/server.py +89 -21
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/type.py +0 -4
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/version.py +1 -1
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/voyager.py +1 -7
- fastapi_voyager-0.9.1/src/fastapi_voyager/web/component/route-code-display.js +135 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/component/schema-code-display.js +50 -18
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/component/schema-field-filter.js +7 -7
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/graph-ui.js +2 -2
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/index.html +102 -53
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/vue-main.js +51 -63
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/demo.py +1 -1
- fastapi_voyager-0.9.1/tests/programatic.py +4 -0
- fastapi_voyager-0.8.2/src/fastapi_voyager/web/component/route-code-display.js +0 -73
- fastapi_voyager-0.8.2/tests/programatic.py +0 -4
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/.gitignore +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/.python-version +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/LICENSE +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/pyproject.toml +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/filter.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/module.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/render.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/type_helper.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/component/render-graph.js +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/quasar.min.css +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/src/fastapi_voyager/web/quasar.min.js +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/__init__.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/demo_anno.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/service/__init__.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/service/schema.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/test_analysis.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/test_filter.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/test_generic.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/test_import.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/test_module.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/tests/test_type_helper.py +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.1}/uv.lock +0 -0
- {fastapi_voyager-0.8.2 → fastapi_voyager-0.9.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.9.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
|
|
@@ -30,6 +30,10 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
|
|
31
31
|
<p align="center"><img src="./voyager.jpg" alt="" /></p>
|
|
32
32
|
|
|
33
|
+
|
|
34
|
+
[](https://www.youtube.com/watch?v=PGlbQq1M-n8 "FastAPI Voyager")
|
|
35
|
+
|
|
36
|
+
|
|
33
37
|
> This repo is still in early stage, currently it supports pydantic v2 only, previous name: fastapi-router-viz
|
|
34
38
|
|
|
35
39
|
Inspect your API interactively
|
|
@@ -105,10 +109,10 @@ click a node to highlight it's upperstream and downstream nodes. figure out the
|
|
|
105
109
|
|
|
106
110
|
```python
|
|
107
111
|
from fastapi import FastAPI
|
|
108
|
-
from fastapi_voyager
|
|
112
|
+
from fastapi_voyager import create_voyager
|
|
109
113
|
from tests.demo import app
|
|
110
114
|
|
|
111
|
-
app.mount('/voyager',
|
|
115
|
+
app.mount('/voyager', create_voyager(
|
|
112
116
|
app,
|
|
113
117
|
module_color={"tests.service": "red"},
|
|
114
118
|
module_prefix="tests.service"))
|
|
@@ -164,7 +168,7 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
164
168
|
|
|
165
169
|
## Plan before v1.0
|
|
166
170
|
|
|
167
|
-
features:
|
|
171
|
+
### features:
|
|
168
172
|
- [x] group schemas by module hierarchy
|
|
169
173
|
- [x] module-based coloring via Analytics(module_color={...})
|
|
170
174
|
- [x] view in web browser
|
|
@@ -183,38 +187,62 @@ features:
|
|
|
183
187
|
- [x] add tooltips
|
|
184
188
|
- [x] route
|
|
185
189
|
- [x] group routes by module hierarchy
|
|
186
|
-
- [
|
|
187
|
-
- [
|
|
188
|
-
- [
|
|
190
|
+
- [x] add response_model in route
|
|
191
|
+
- [x] fixed left bar show tag/ route
|
|
192
|
+
- [x] export voyager core data into json (for better debugging)
|
|
193
|
+
- [x] add api to rebuild core data from json, and render it
|
|
194
|
+
- [x] fix Generic case `test_generic.py`
|
|
195
|
+
- [x] show tips for routes not return pydantic type.
|
|
196
|
+
- [x] fix duplicated link from class and parent class, it also break clicking highlight
|
|
197
|
+
- [x] refactor: abstract render module
|
|
198
|
+
|
|
199
|
+
### backlog
|
|
189
200
|
- [ ] user can generate nodes/edges manually and connect to generated ones
|
|
190
201
|
- [ ] add owner
|
|
191
202
|
- [ ] add extra info for schema
|
|
192
|
-
- [ ]
|
|
193
|
-
|
|
194
|
-
- [x] fixed left bar show tag/ route
|
|
195
|
-
- [ ] display standard ER diagram `difference`
|
|
203
|
+
- [ ] fixed left/right bar show field information
|
|
204
|
+
- [ ] display standard ER diagram `hard`
|
|
196
205
|
- [ ] display potential invalid links
|
|
197
|
-
- [ ] integration with pydantic-resolve
|
|
198
|
-
- [ ] show difference between resolve, post fields
|
|
199
|
-
- [x] strikethrough for excluded fields
|
|
200
|
-
- [ ] display loader as edges
|
|
201
|
-
- [x] export voyager core data into json (for better debugging)
|
|
202
|
-
- [x] add api to rebuild core data from json, and render it
|
|
203
|
-
- [x] fix Generic case `test_generic.py`
|
|
204
|
-
- [ ] show tips for routes not return pydantic type.
|
|
205
|
-
- [ ] add http method for route
|
|
206
206
|
|
|
207
207
|
bugs & non feature:
|
|
208
|
-
- [x] fix duplicated link from class and parent class, it also break clicking highlight
|
|
209
208
|
- [ ] add tests
|
|
210
|
-
- [
|
|
211
|
-
|
|
209
|
+
- [ ] support dataclass (pending)
|
|
210
|
+
|
|
211
|
+
### in analysis
|
|
212
|
+
- [ ] click field to highlight links
|
|
213
|
+
- [ ] animation effect for edges
|
|
214
|
+
- [ ] customrized right click panel
|
|
215
|
+
- [ ] show own dependencies
|
|
216
|
+
|
|
217
|
+
### plan:
|
|
218
|
+
|
|
219
|
+
#### 0.9
|
|
220
|
+
- [x] refactor: server.py
|
|
221
|
+
- [x] rename create_app_with_fastapi -> create_voyager
|
|
222
|
+
- [x] add doc for parameters
|
|
223
|
+
- [x] improve initialization time cost
|
|
224
|
+
- [x] query route / schema info through realtime api
|
|
225
|
+
- [x] adjust fe
|
|
226
|
+
|
|
227
|
+
#### 0.10
|
|
228
|
+
- [ ] logging information
|
|
229
|
+
- [ ] open route in swagger
|
|
230
|
+
- config docs path
|
|
231
|
+
- [ ] add http method for route
|
|
232
|
+
- [ ] enable/disable module cluster (may save space)
|
|
233
|
+
|
|
234
|
+
#### 0.11
|
|
235
|
+
- [ ] integration with pydantic-resolve
|
|
236
|
+
- [ ] show hint for resolve, post fields
|
|
237
|
+
- [ ] display loader as edges
|
|
238
|
+
|
|
212
239
|
|
|
213
240
|
## Using with pydantic-resolve
|
|
214
241
|
|
|
242
|
+
WIP: ...
|
|
243
|
+
|
|
215
244
|
pydantic-resolve's @ensure_subset decorator is helpful to pick fields from `source class` in safe.
|
|
216
245
|
|
|
217
|
-
TODO: ...
|
|
218
246
|
|
|
219
247
|
|
|
220
248
|
## Credits
|
|
@@ -225,7 +253,13 @@ TODO: ...
|
|
|
225
253
|
|
|
226
254
|
## Changelog
|
|
227
255
|
|
|
256
|
+
- 0.9:
|
|
257
|
+
- 0.9.1:
|
|
258
|
+
- api change: from `create_app_with_fastapi` to `create_voyager`, and expose as `from fastapi_voyager import create_voyager`
|
|
259
|
+
- optimization: lazy load vscode link and source code, speed up the initialization.
|
|
228
260
|
- 0.8:
|
|
261
|
+
- 0.8.3
|
|
262
|
+
- upgrade theme
|
|
229
263
|
- 0.8.2
|
|
230
264
|
- fix silly typo.
|
|
231
265
|
- 0.8.1
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
<p align="center"><img src="./voyager.jpg" alt="" /></p>
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
[](https://www.youtube.com/watch?v=PGlbQq1M-n8 "FastAPI Voyager")
|
|
8
|
+
|
|
9
|
+
|
|
6
10
|
> This repo is still in early stage, currently it supports pydantic v2 only, previous name: fastapi-router-viz
|
|
7
11
|
|
|
8
12
|
Inspect your API interactively
|
|
@@ -78,10 +82,10 @@ click a node to highlight it's upperstream and downstream nodes. figure out the
|
|
|
78
82
|
|
|
79
83
|
```python
|
|
80
84
|
from fastapi import FastAPI
|
|
81
|
-
from fastapi_voyager
|
|
85
|
+
from fastapi_voyager import create_voyager
|
|
82
86
|
from tests.demo import app
|
|
83
87
|
|
|
84
|
-
app.mount('/voyager',
|
|
88
|
+
app.mount('/voyager', create_voyager(
|
|
85
89
|
app,
|
|
86
90
|
module_color={"tests.service": "red"},
|
|
87
91
|
module_prefix="tests.service"))
|
|
@@ -137,7 +141,7 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
137
141
|
|
|
138
142
|
## Plan before v1.0
|
|
139
143
|
|
|
140
|
-
features:
|
|
144
|
+
### features:
|
|
141
145
|
- [x] group schemas by module hierarchy
|
|
142
146
|
- [x] module-based coloring via Analytics(module_color={...})
|
|
143
147
|
- [x] view in web browser
|
|
@@ -156,38 +160,62 @@ features:
|
|
|
156
160
|
- [x] add tooltips
|
|
157
161
|
- [x] route
|
|
158
162
|
- [x] group routes by module hierarchy
|
|
159
|
-
- [
|
|
160
|
-
- [
|
|
161
|
-
- [
|
|
163
|
+
- [x] add response_model in route
|
|
164
|
+
- [x] fixed left bar show tag/ route
|
|
165
|
+
- [x] export voyager core data into json (for better debugging)
|
|
166
|
+
- [x] add api to rebuild core data from json, and render it
|
|
167
|
+
- [x] fix Generic case `test_generic.py`
|
|
168
|
+
- [x] show tips for routes not return pydantic type.
|
|
169
|
+
- [x] fix duplicated link from class and parent class, it also break clicking highlight
|
|
170
|
+
- [x] refactor: abstract render module
|
|
171
|
+
|
|
172
|
+
### backlog
|
|
162
173
|
- [ ] user can generate nodes/edges manually and connect to generated ones
|
|
163
174
|
- [ ] add owner
|
|
164
175
|
- [ ] add extra info for schema
|
|
165
|
-
- [ ]
|
|
166
|
-
|
|
167
|
-
- [x] fixed left bar show tag/ route
|
|
168
|
-
- [ ] display standard ER diagram `difference`
|
|
176
|
+
- [ ] fixed left/right bar show field information
|
|
177
|
+
- [ ] display standard ER diagram `hard`
|
|
169
178
|
- [ ] display potential invalid links
|
|
170
|
-
- [ ] integration with pydantic-resolve
|
|
171
|
-
- [ ] show difference between resolve, post fields
|
|
172
|
-
- [x] strikethrough for excluded fields
|
|
173
|
-
- [ ] display loader as edges
|
|
174
|
-
- [x] export voyager core data into json (for better debugging)
|
|
175
|
-
- [x] add api to rebuild core data from json, and render it
|
|
176
|
-
- [x] fix Generic case `test_generic.py`
|
|
177
|
-
- [ ] show tips for routes not return pydantic type.
|
|
178
|
-
- [ ] add http method for route
|
|
179
179
|
|
|
180
180
|
bugs & non feature:
|
|
181
|
-
- [x] fix duplicated link from class and parent class, it also break clicking highlight
|
|
182
181
|
- [ ] add tests
|
|
183
|
-
- [
|
|
184
|
-
|
|
182
|
+
- [ ] support dataclass (pending)
|
|
183
|
+
|
|
184
|
+
### in analysis
|
|
185
|
+
- [ ] click field to highlight links
|
|
186
|
+
- [ ] animation effect for edges
|
|
187
|
+
- [ ] customrized right click panel
|
|
188
|
+
- [ ] show own dependencies
|
|
189
|
+
|
|
190
|
+
### plan:
|
|
191
|
+
|
|
192
|
+
#### 0.9
|
|
193
|
+
- [x] refactor: server.py
|
|
194
|
+
- [x] rename create_app_with_fastapi -> create_voyager
|
|
195
|
+
- [x] add doc for parameters
|
|
196
|
+
- [x] improve initialization time cost
|
|
197
|
+
- [x] query route / schema info through realtime api
|
|
198
|
+
- [x] adjust fe
|
|
199
|
+
|
|
200
|
+
#### 0.10
|
|
201
|
+
- [ ] logging information
|
|
202
|
+
- [ ] open route in swagger
|
|
203
|
+
- config docs path
|
|
204
|
+
- [ ] add http method for route
|
|
205
|
+
- [ ] enable/disable module cluster (may save space)
|
|
206
|
+
|
|
207
|
+
#### 0.11
|
|
208
|
+
- [ ] integration with pydantic-resolve
|
|
209
|
+
- [ ] show hint for resolve, post fields
|
|
210
|
+
- [ ] display loader as edges
|
|
211
|
+
|
|
185
212
|
|
|
186
213
|
## Using with pydantic-resolve
|
|
187
214
|
|
|
215
|
+
WIP: ...
|
|
216
|
+
|
|
188
217
|
pydantic-resolve's @ensure_subset decorator is helpful to pick fields from `source class` in safe.
|
|
189
218
|
|
|
190
|
-
TODO: ...
|
|
191
219
|
|
|
192
220
|
|
|
193
221
|
## Credits
|
|
@@ -198,7 +226,13 @@ TODO: ...
|
|
|
198
226
|
|
|
199
227
|
## Changelog
|
|
200
228
|
|
|
229
|
+
- 0.9:
|
|
230
|
+
- 0.9.1:
|
|
231
|
+
- api change: from `create_app_with_fastapi` to `create_voyager`, and expose as `from fastapi_voyager import create_voyager`
|
|
232
|
+
- optimization: lazy load vscode link and source code, speed up the initialization.
|
|
201
233
|
- 0.8:
|
|
234
|
+
- 0.8.3
|
|
235
|
+
- upgrade theme
|
|
202
236
|
- 0.8.2
|
|
203
237
|
- fix silly typo.
|
|
204
238
|
- 0.8.1
|
|
@@ -281,7 +281,7 @@ Examples:
|
|
|
281
281
|
except ImportError:
|
|
282
282
|
print("Error: uvicorn is required to run the server. Install via 'pip install uvicorn' or 'uv add uvicorn'.")
|
|
283
283
|
sys.exit(1)
|
|
284
|
-
app_server = viz_server.
|
|
284
|
+
app_server = viz_server.create_voyager(
|
|
285
285
|
app,
|
|
286
286
|
module_color=module_color,
|
|
287
287
|
module_prefix=args.module_prefix,
|
|
@@ -6,23 +6,18 @@ from pydantic import BaseModel
|
|
|
6
6
|
from fastapi.responses import HTMLResponse, PlainTextResponse, JSONResponse
|
|
7
7
|
from fastapi.staticfiles import StaticFiles
|
|
8
8
|
from fastapi_voyager.voyager import Voyager
|
|
9
|
-
from fastapi_voyager.type import Tag, FieldInfo, CoreData
|
|
9
|
+
from fastapi_voyager.type import Tag, FieldInfo, CoreData, SchemaNode
|
|
10
10
|
from fastapi_voyager.render import Renderer
|
|
11
|
+
from fastapi_voyager.type_helper import get_source, get_vscode_link
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
WEB_DIR = Path(__file__).parent / "web"
|
|
14
15
|
WEB_DIR.mkdir(exist_ok=True)
|
|
15
16
|
|
|
16
|
-
class SchemaType(BaseModel):
|
|
17
|
-
name: str
|
|
18
|
-
fullname: str
|
|
19
|
-
source_code: str
|
|
20
|
-
vscode_link: str
|
|
21
|
-
fields: list[FieldInfo]
|
|
22
17
|
|
|
23
18
|
class OptionParam(BaseModel):
|
|
24
19
|
tags: list[Tag]
|
|
25
|
-
schemas: list[
|
|
20
|
+
schemas: list[SchemaNode]
|
|
26
21
|
dot: str
|
|
27
22
|
|
|
28
23
|
class Payload(BaseModel):
|
|
@@ -40,26 +35,22 @@ def create_route(
|
|
|
40
35
|
module_color: dict[str, str] | None = None,
|
|
41
36
|
module_prefix: Optional[str] = None,
|
|
42
37
|
):
|
|
38
|
+
"""
|
|
39
|
+
module_color: dict mapping module name to color string, e.g. {'models': 'lightblue'}
|
|
40
|
+
module_prefix: prefix string to define schemas show in brief mode
|
|
41
|
+
"""
|
|
43
42
|
router = APIRouter(tags=['fastapi-voyager'])
|
|
44
43
|
|
|
45
44
|
@router.get("/dot", response_model=OptionParam)
|
|
46
45
|
def get_dot() -> str:
|
|
47
|
-
voyager = Voyager(module_color=module_color
|
|
46
|
+
voyager = Voyager(module_color=module_color)
|
|
48
47
|
voyager.analysis(target_app)
|
|
49
48
|
dot = voyager.render_dot()
|
|
50
49
|
|
|
51
50
|
# include tags and their routes
|
|
52
51
|
tags = voyager.tags
|
|
53
52
|
|
|
54
|
-
schemas = [
|
|
55
|
-
SchemaType(
|
|
56
|
-
name=s.name,
|
|
57
|
-
fullname=s.id,
|
|
58
|
-
fields=s.fields,
|
|
59
|
-
source_code=s.source_code,
|
|
60
|
-
vscode_link=s.vscode_link
|
|
61
|
-
) for s in voyager.nodes
|
|
62
|
-
]
|
|
53
|
+
schemas = voyager.nodes[:]
|
|
63
54
|
schemas.sort(key=lambda s: s.name)
|
|
64
55
|
|
|
65
56
|
return OptionParam(tags=tags, schemas=schemas, dot=dot)
|
|
@@ -74,7 +65,6 @@ def create_route(
|
|
|
74
65
|
show_fields=payload.show_fields,
|
|
75
66
|
module_color=module_color,
|
|
76
67
|
route_name=payload.route_name,
|
|
77
|
-
load_meta=False,
|
|
78
68
|
hide_primitive_route=payload.hide_primitive_route,
|
|
79
69
|
)
|
|
80
70
|
voyager.analysis(target_app)
|
|
@@ -92,7 +82,6 @@ def create_route(
|
|
|
92
82
|
show_fields=payload.show_fields,
|
|
93
83
|
module_color=module_color,
|
|
94
84
|
route_name=payload.route_name,
|
|
95
|
-
load_meta=False,
|
|
96
85
|
)
|
|
97
86
|
voyager.analysis(target_app)
|
|
98
87
|
return voyager.dump_core_data()
|
|
@@ -117,11 +106,90 @@ def create_route(
|
|
|
117
106
|
</body>
|
|
118
107
|
</html>
|
|
119
108
|
"""
|
|
109
|
+
|
|
110
|
+
class SourcePayload(BaseModel):
|
|
111
|
+
schema_name: str
|
|
120
112
|
|
|
113
|
+
@router.post("/source")
|
|
114
|
+
def get_object_by_module_name(payload: SourcePayload):
|
|
115
|
+
"""
|
|
116
|
+
input: __module__ + __name__, eg: tests.demo.PageStories
|
|
117
|
+
output: source code of the object
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
components = payload.schema_name.split('.')
|
|
121
|
+
if len(components) < 2:
|
|
122
|
+
return JSONResponse(
|
|
123
|
+
status_code=400,
|
|
124
|
+
content={"error": "Invalid schema name format. Expected format: module.ClassName"}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
module_name = '.'.join(components[:-1])
|
|
128
|
+
class_name = components[-1]
|
|
129
|
+
|
|
130
|
+
mod = __import__(module_name, fromlist=[class_name])
|
|
131
|
+
obj = getattr(mod, class_name)
|
|
132
|
+
source_code = get_source(obj)
|
|
133
|
+
|
|
134
|
+
return JSONResponse(content={"source_code": source_code})
|
|
135
|
+
except ImportError as e:
|
|
136
|
+
return JSONResponse(
|
|
137
|
+
status_code=404,
|
|
138
|
+
content={"error": f"Module not found: {e}"}
|
|
139
|
+
)
|
|
140
|
+
except AttributeError as e:
|
|
141
|
+
return JSONResponse(
|
|
142
|
+
status_code=404,
|
|
143
|
+
content={"error": f"Class not found: {e}"}
|
|
144
|
+
)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
return JSONResponse(
|
|
147
|
+
status_code=500,
|
|
148
|
+
content={"error": f"Internal error: {str(e)}"}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@router.post("/vscode-link")
|
|
152
|
+
def get_vscode_link_by_module_name(payload: SourcePayload):
|
|
153
|
+
"""
|
|
154
|
+
input: __module__ + __name__, eg: tests.demo.PageStories
|
|
155
|
+
output: source path of the object
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
components = payload.schema_name.split('.')
|
|
159
|
+
if len(components) < 2:
|
|
160
|
+
return JSONResponse(
|
|
161
|
+
status_code=400,
|
|
162
|
+
content={"error": "Invalid schema name format. Expected format: module.ClassName"}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
module_name = '.'.join(components[:-1])
|
|
166
|
+
class_name = components[-1]
|
|
167
|
+
|
|
168
|
+
mod = __import__(module_name, fromlist=[class_name])
|
|
169
|
+
obj = getattr(mod, class_name)
|
|
170
|
+
link = get_vscode_link(obj)
|
|
171
|
+
|
|
172
|
+
return JSONResponse(content={"link": link})
|
|
173
|
+
except ImportError as e:
|
|
174
|
+
return JSONResponse(
|
|
175
|
+
status_code=404,
|
|
176
|
+
content={"error": f"Module not found: {e}"}
|
|
177
|
+
)
|
|
178
|
+
except AttributeError as e:
|
|
179
|
+
return JSONResponse(
|
|
180
|
+
status_code=404,
|
|
181
|
+
content={"error": f"Class not found: {e}"}
|
|
182
|
+
)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
return JSONResponse(
|
|
185
|
+
status_code=500,
|
|
186
|
+
content={"error": f"Internal error: {str(e)}"}
|
|
187
|
+
)
|
|
188
|
+
|
|
121
189
|
return router
|
|
122
190
|
|
|
123
191
|
|
|
124
|
-
def
|
|
192
|
+
def create_voyager(
|
|
125
193
|
target_app: FastAPI,
|
|
126
194
|
module_color: dict[str, str] | None = None,
|
|
127
195
|
gzip_minimum_size: int | None = 500,
|
|
@@ -22,8 +22,6 @@ class Tag(NodeBase):
|
|
|
22
22
|
@dataclass
|
|
23
23
|
class Route(NodeBase):
|
|
24
24
|
module: str
|
|
25
|
-
source_code: str = ''
|
|
26
|
-
vscode_link: str = '' # optional vscode deep link
|
|
27
25
|
response_schema: str = ''
|
|
28
26
|
is_primitive: bool = True
|
|
29
27
|
|
|
@@ -37,8 +35,6 @@ class ModuleRoute:
|
|
|
37
35
|
@dataclass
|
|
38
36
|
class SchemaNode(NodeBase):
|
|
39
37
|
module: str
|
|
40
|
-
source_code: str = '' # optional for tests / backward compatibility
|
|
41
|
-
vscode_link: str = '' # optional vscode deep link
|
|
42
38
|
fields: list[FieldInfo] = field(default_factory=list)
|
|
43
39
|
|
|
44
40
|
@dataclass
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.9.1"
|
|
@@ -29,7 +29,6 @@ class Voyager:
|
|
|
29
29
|
module_color: dict[str, str] | None = None,
|
|
30
30
|
route_name: str | None = None,
|
|
31
31
|
hide_primitive_route: bool = False,
|
|
32
|
-
load_meta: bool = False
|
|
33
32
|
):
|
|
34
33
|
|
|
35
34
|
self.routes: list[Route] = []
|
|
@@ -50,7 +49,6 @@ class Voyager:
|
|
|
50
49
|
self.show_fields = show_fields if show_fields in ('single','object','all') else 'object'
|
|
51
50
|
self.module_color = module_color or {}
|
|
52
51
|
self.route_name = route_name
|
|
53
|
-
self.load_meta = load_meta
|
|
54
52
|
self.hide_primitive_route = hide_primitive_route
|
|
55
53
|
|
|
56
54
|
|
|
@@ -85,7 +83,7 @@ class Voyager:
|
|
|
85
83
|
self.tags.append(tag_obj)
|
|
86
84
|
|
|
87
85
|
# add route and create links
|
|
88
|
-
route_id =
|
|
86
|
+
route_id = full_class_name(route.endpoint)
|
|
89
87
|
route_name = route.endpoint.__name__
|
|
90
88
|
route_module = route.endpoint.__module__
|
|
91
89
|
|
|
@@ -110,8 +108,6 @@ class Voyager:
|
|
|
110
108
|
id=route_id,
|
|
111
109
|
name=route_name,
|
|
112
110
|
module=route_module,
|
|
113
|
-
vscode_link=get_vscode_link(route.endpoint) if self.load_meta else '',
|
|
114
|
-
source_code=inspect.getsource(route.endpoint) if self.load_meta else '',
|
|
115
111
|
response_schema=get_type_name(route.response_model),
|
|
116
112
|
is_primitive=is_primitive_response
|
|
117
113
|
)
|
|
@@ -154,8 +150,6 @@ class Voyager:
|
|
|
154
150
|
id=full_name,
|
|
155
151
|
module=schema.__module__,
|
|
156
152
|
name=schema.__name__,
|
|
157
|
-
source_code=get_source(schema) if self.load_meta else '',
|
|
158
|
-
vscode_link=get_vscode_link(schema) if self.load_meta else '',
|
|
159
153
|
fields=get_pydantic_fields(schema, bases_fields)
|
|
160
154
|
)
|
|
161
155
|
return full_name
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const { defineComponent, ref, watch, onMounted } = window.Vue;
|
|
2
|
+
|
|
3
|
+
// Component: RouteCodeDisplay
|
|
4
|
+
// Props:
|
|
5
|
+
// routeId: route id key in routeItems
|
|
6
|
+
// modelValue: dialog visibility
|
|
7
|
+
// routes: object map { id: { id, name, source_code } }
|
|
8
|
+
export default defineComponent({
|
|
9
|
+
name: "RouteCodeDisplay",
|
|
10
|
+
props: {
|
|
11
|
+
routeId: { type: String, required: true },
|
|
12
|
+
modelValue: { type: Boolean, default: false },
|
|
13
|
+
routes: { type: Object, default: () => ({}) },
|
|
14
|
+
},
|
|
15
|
+
emits: ["close"],
|
|
16
|
+
setup(props, { emit }) {
|
|
17
|
+
const loading = ref(false);
|
|
18
|
+
const code = ref("");
|
|
19
|
+
const error = ref("");
|
|
20
|
+
const link = ref("");
|
|
21
|
+
|
|
22
|
+
function close() {
|
|
23
|
+
emit("close");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function highlightLater() {
|
|
27
|
+
requestAnimationFrame(() => {
|
|
28
|
+
try {
|
|
29
|
+
if (window.hljs) {
|
|
30
|
+
const block = document.querySelector(
|
|
31
|
+
".frv-route-code-display pre code.language-python"
|
|
32
|
+
);
|
|
33
|
+
if (block) {
|
|
34
|
+
window.hljs.highlightElement(block);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.warn("highlight failed", e);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function load() {
|
|
44
|
+
if (!props.routeId) {
|
|
45
|
+
code.value = "";
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
loading.value = true;
|
|
50
|
+
error.value = null;
|
|
51
|
+
code.value = "";
|
|
52
|
+
link.value = "";
|
|
53
|
+
|
|
54
|
+
// try to fetch from server: POST /source with { schema_name: routeId }
|
|
55
|
+
const payload = { schema_name: props.routeId };
|
|
56
|
+
try {
|
|
57
|
+
const resp = await fetch(`source`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: {
|
|
60
|
+
Accept: "application/json",
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify(payload),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const data = await resp.json().catch(() => ({}));
|
|
67
|
+
if (resp.ok) {
|
|
68
|
+
code.value = data.source_code || "// no source code available";
|
|
69
|
+
} else {
|
|
70
|
+
error.value = (data && data.error) || "Failed to load source";
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
error.value = e && e.message ? e.message : "Failed to load source";
|
|
74
|
+
} finally {
|
|
75
|
+
loading.value = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const resp = await fetch(`vscode-link`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
Accept: "application/json",
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify(payload),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const data = await resp.json().catch(() => ({}));
|
|
89
|
+
if (resp.ok) {
|
|
90
|
+
link.value = data.link || "// no source code available";
|
|
91
|
+
} else {
|
|
92
|
+
error.value += (data && data.error) || "Failed to load vscode link";
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
} finally {
|
|
96
|
+
loading.value = false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!error.value) {
|
|
100
|
+
highlightLater();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
watch(
|
|
105
|
+
() => props.modelValue,
|
|
106
|
+
(v) => {
|
|
107
|
+
if (v) load();
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
watch(
|
|
111
|
+
() => props.routeId,
|
|
112
|
+
() => {
|
|
113
|
+
if (props.modelValue) load();
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
onMounted(() => {
|
|
118
|
+
if (props.modelValue) load();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return { loading, code, error, close, link };
|
|
122
|
+
},
|
|
123
|
+
template: `
|
|
124
|
+
<div class="frv-route-code-display" style="border:1px solid #ccc; position:relative; width:50vw; max-width:50vw; height:100%; background:#fff;">
|
|
125
|
+
<q-btn dense flat round icon="close" @click="close" aria-label="Close" style="position:absolute; top:6px; right:6px; z-index:10; background:rgba(255,255,255,0.85)" />
|
|
126
|
+
<div v-if="link" class="q-ml-md q-mt-md" style="padding-top:4px;">
|
|
127
|
+
<a :href="link" target="_blank" rel="noopener" style="font-size:12px; color:#3b82f6;">Open in VSCode</a>
|
|
128
|
+
</div>
|
|
129
|
+
<div style="padding:40px 16px 16px 16px; height:100%; box-sizing:border-box; overflow:auto;">
|
|
130
|
+
<div v-if="loading" style="font-family:Menlo, monospace; font-size:12px;">Loading source...</div>
|
|
131
|
+
<div v-else-if="error" style="color:#c10015; font-family:Menlo, monospace; font-size:12px;">{{ error }}</div>
|
|
132
|
+
<pre v-else style="margin:0;"><code class="language-python">{{ code }}</code></pre>
|
|
133
|
+
</div>
|
|
134
|
+
</div>`,
|
|
135
|
+
});
|