fastapi-voyager 0.10.4__tar.gz → 0.10.5__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.
Files changed (48) hide show
  1. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/PKG-INFO +7 -1
  2. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/README.md +6 -0
  3. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/render.py +1 -1
  4. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/version.py +1 -1
  5. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/component/schema-field-filter.js +5 -6
  6. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/graph-ui.js +56 -25
  7. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/index.html +2 -5
  8. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/vue-main.js +53 -51
  9. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/.gitignore +0 -0
  10. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/.python-version +0 -0
  11. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/LICENSE +0 -0
  12. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/pyproject.toml +0 -0
  13. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/__init__.py +0 -0
  14. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/cli.py +0 -0
  15. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/filter.py +0 -0
  16. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/module.py +0 -0
  17. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/server.py +0 -0
  18. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/type.py +0 -0
  19. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/type_helper.py +0 -0
  20. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/voyager.py +0 -0
  21. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/component/render-graph.js +0 -0
  22. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/component/route-code-display.js +0 -0
  23. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/component/schema-code-display.js +0 -0
  24. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
  25. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
  26. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
  27. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
  28. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
  29. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
  30. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
  31. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
  32. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
  33. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/quasar.min.css +0 -0
  34. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/quasar.min.js +0 -0
  35. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/__init__.py +0 -0
  36. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/demo.py +0 -0
  37. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/demo_anno.py +0 -0
  38. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/programatic.py +0 -0
  39. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/service/__init__.py +0 -0
  40. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/service/schema.py +0 -0
  41. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/test_analysis.py +0 -0
  42. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/test_filter.py +0 -0
  43. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/test_generic.py +0 -0
  44. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/test_import.py +0 -0
  45. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/test_module.py +0 -0
  46. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/tests/test_type_helper.py +0 -0
  47. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/uv.lock +0 -0
  48. {fastapi_voyager-0.10.4 → fastapi_voyager-0.10.5}/voyager.jpg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-voyager
3
- Version: 0.10.4
3
+ Version: 0.10.5
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
@@ -246,6 +246,8 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
246
246
  - [x] refactor naming
247
247
  - 0.10.4
248
248
  - [x] fix: when focus is on, should ensure changes from other params not broken.
249
+ - 0.10.5
250
+ - [x] double click to show details, and highlight as tomato
249
251
 
250
252
 
251
253
  #### 0.11
@@ -256,6 +258,10 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
256
258
  - config docs path
257
259
  - [ ] add tests
258
260
  - [ ] fix: focus should reset zoom
261
+ - [ ] sort field name
262
+ - [ ] set max limit for fields
263
+ - [ ] add info icon alone with schema node
264
+ - [ ] add loading for field detail panel
259
265
 
260
266
  #### 0.12
261
267
  - [ ] integration with pydantic-resolve
@@ -219,6 +219,8 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
219
219
  - [x] refactor naming
220
220
  - 0.10.4
221
221
  - [x] fix: when focus is on, should ensure changes from other params not broken.
222
+ - 0.10.5
223
+ - [x] double click to show details, and highlight as tomato
222
224
 
223
225
 
224
226
  #### 0.11
@@ -229,6 +231,10 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
229
231
  - config docs path
230
232
  - [ ] add tests
231
233
  - [ ] fix: focus should reset zoom
234
+ - [ ] sort field name
235
+ - [ ] set max limit for fields
236
+ - [ ] add info icon alone with schema node
237
+ - [ ] add loading for field detail panel
232
238
 
233
239
  #### 0.12
234
240
  - [ ] integration with pydantic-resolve
@@ -37,7 +37,7 @@ class Renderer:
37
37
  fields_parts.append(field_str)
38
38
 
39
39
  header_color = 'tomato' if node.id == self.schema else '#009485'
40
- header = f"""<tr><td cellpadding="1.5" bgcolor="{header_color}" align="center" colspan="1" port="{PK}"> <font color="white"> {node.name} </font> </td> </tr>"""
40
+ header = f"""<tr><td cellpadding="6" bgcolor="{header_color}" align="center" colspan="1" port="{PK}"> <font color="white"> {node.name} </font></td> </tr>"""
41
41
  field_content = ''.join(fields_parts) if fields_parts else ''
42
42
  return f"""<<table border="1" cellborder="0" cellpadding="0" bgcolor="white"> {header} {field_content} </table>>"""
43
43
 
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.10.4"
2
+ __version__ = "0.10.5"
@@ -42,11 +42,12 @@ export default defineComponent({
42
42
  async function loadSchemas() {
43
43
  // Use externally provided props.schemas dict directly; no network call.
44
44
  state.error = null;
45
- const dict = props.schemas && typeof props.schemas === "object" ? props.schemas : {};
45
+ const dict =
46
+ props.schemas && typeof props.schemas === "object" ? props.schemas : {};
46
47
  // Flatten to array for local operations
47
48
  state.schemas = Object.values(dict);
48
49
  state.schemaOptions = state.schemas.map((s) => ({
49
- label: `${s.name} (${s.id})`,
50
+ label: `${s.name} - ${s.id}`,
50
51
  value: s.id,
51
52
  }));
52
53
  // Maintain compatibility: loadingSchemas flag toggled quickly (no async work)
@@ -57,7 +58,7 @@ export default defineComponent({
57
58
  const needle = (val || "").toLowerCase();
58
59
  update(() => {
59
60
  let opts = state.schemas.map((s) => ({
60
- label: `${s.name} (${s.id})`,
61
+ label: `${s.name} - ${s.id}`,
61
62
  value: s.id,
62
63
  }));
63
64
  if (needle) {
@@ -127,7 +128,6 @@ export default defineComponent({
127
128
  }
128
129
  });
129
130
 
130
-
131
131
  function close() {
132
132
  emit("close");
133
133
  }
@@ -178,13 +178,12 @@ export default defineComponent({
178
178
  :disable="!state.schemaFullname"
179
179
  :loading="state.querying"
180
180
  @click="onQuery" />
181
- </div>
182
181
  <q-btn
183
182
  flat dense round icon="close"
184
183
  aria-label="Close"
185
184
  @click="close"
186
- style="position:absolute; top:6px; right:6px; z-index:11; background:rgba(255,255,255,0.85);"
187
185
  />
186
+ </div>
188
187
  <div v-if="state.error" style="position:absolute; top:52px; left:8px; z-index:10; color:#c10015; font-size:12px;">{{ state.error }}</div>
189
188
  <div id="graph-schema-field" style="width:100%; height:100%; overflow:auto; background:#fafafa"></div>
190
189
  </div>
@@ -57,6 +57,13 @@ export class GraphUI {
57
57
  return $result;
58
58
  }
59
59
 
60
+ highlightSchemaBanner(node) {
61
+ const ele = node.querySelector("polygon[fill='#009485']")
62
+ if (ele) {
63
+ ele.setAttribute('fill', 'tomato');
64
+ }
65
+ }
66
+
60
67
  _init() {
61
68
  const self = this;
62
69
  $(this.selector).graphviz({
@@ -65,6 +72,26 @@ export class GraphUI {
65
72
  ready: function () {
66
73
  self.gv = this;
67
74
 
75
+ self.gv.nodes().dblclick(function (event) {
76
+ event.stopPropagation();
77
+ try {
78
+ self.highlightSchemaBanner(this)
79
+ } catch(e) {
80
+ console.log(e)
81
+ }
82
+ const set = $();
83
+ set.push(this);
84
+ // const obj = { set, direction: "bidirectional" };
85
+ const schemaName = event.currentTarget.dataset.name;
86
+ // self.currentSelection = [obj];
87
+ if (schemaName) {
88
+ try {
89
+ self.options.onSchemaClick(schemaName);
90
+ } catch (e) {
91
+ console.warn("onSchemaClick callback failed", e);
92
+ }
93
+ }
94
+ });
68
95
  self.gv.nodes().click(function (event) {
69
96
  const set = $();
70
97
  set.push(this);
@@ -83,12 +110,10 @@ export class GraphUI {
83
110
  } else {
84
111
  self.currentSelection = [obj];
85
112
  self._highlight();
86
- if (schemaName) {
87
- try {
88
- self.options.onSchemaClick(schemaName);
89
- } catch (e) {
90
- console.warn("onSchemaClick callback failed", e);
91
- }
113
+ try {
114
+ self.options.resetCb();
115
+ } catch (e) {
116
+ console.warn("resetCb callback failed", e);
92
117
  }
93
118
  }
94
119
  });
@@ -107,28 +132,34 @@ export class GraphUI {
107
132
 
108
133
  // svg 背景点击高亮清空
109
134
 
110
- $(document).off('click.graphui').on('click.graphui', function (evt) {
111
- // 如果点击目标不在 graph 容器内,直接退出
112
- const graphContainer = $(self.selector)[0];
113
- if (!graphContainer || !evt.target || !graphContainer.contains(evt.target)) {
114
- return;
115
- }
135
+ $(document)
136
+ .off("click.graphui")
137
+ .on("click.graphui", function (evt) {
138
+ // 如果点击目标不在 graph 容器内,直接退出
139
+ const graphContainer = $(self.selector)[0];
140
+ if (
141
+ !graphContainer ||
142
+ !evt.target ||
143
+ !graphContainer.contains(evt.target)
144
+ ) {
145
+ return;
146
+ }
116
147
 
117
- let isNode = false;
118
- const $nodes = self.gv.nodes();
119
- const node = evt.target.parentNode;
120
- $nodes.each(function () {
121
- if (this === node) {
122
- isNode = true;
148
+ let isNode = false;
149
+ const $nodes = self.gv.nodes();
150
+ const node = evt.target.parentNode;
151
+ $nodes.each(function () {
152
+ if (this === node) {
153
+ isNode = true;
154
+ }
155
+ });
156
+ if (!isNode && self.gv) {
157
+ self.gv.highlight();
158
+ if (self.options.resetCb) {
159
+ self.options.resetCb();
160
+ }
123
161
  }
124
162
  });
125
- if (!isNode && self.gv) {
126
- self.gv.highlight();
127
- if (self.options.resetCb) {
128
- self.options.resetCb();
129
- }
130
- }
131
- });
132
163
 
133
164
  $(document).on("keydown.graphui", function (evt) {
134
165
  if (evt.keyCode === 27 && self.gv) {
@@ -127,12 +127,10 @@
127
127
  <ul>
128
128
  <li>scroll to zoom in/out</li>
129
129
  <li>
130
- click node to check highlight related nodes on the
131
- chart, esc to unselect
130
+ double click node to view details.
132
131
  </li>
133
132
  <li>
134
- shift + click to see schema's dependencies without
135
- unrelated nodes
133
+ shift + click to see schema's dependencies without unrelated nodes.
136
134
  </li>
137
135
  </ul>
138
136
  </div>
@@ -149,7 +147,6 @@
149
147
  :width="state.drawerWidth"
150
148
  side="right"
151
149
  style="border-left: 1px solid #888;"
152
- overlay
153
150
  bordered
154
151
  >
155
152
  <!-- 可拖拽的调整栏 -->
@@ -48,6 +48,7 @@ const app = createApp({
48
48
  const schemaCodeName = ref("");
49
49
  const routeCodeId = ref("");
50
50
  const showRouteDetail = ref(false);
51
+ let graphUI = null;
51
52
 
52
53
  function openDetail() {
53
54
  showDetail.value = true;
@@ -88,13 +89,13 @@ const app = createApp({
88
89
 
89
90
  async function onFocusChange(val) {
90
91
  if (val) {
91
- await onGenerate(false)
92
+ await onGenerate(true); // target could be out of view when switchingfrom big to small
92
93
  } else {
93
- await onGenerate(false)
94
+ await onGenerate(false);
94
95
  setTimeout(() => {
95
- const ele = $(`[data-name='${schemaCodeName.value}'] polygon`)
96
- ele.click()
97
- }, 1)
96
+ const ele = $(`[data-name='${schemaCodeName.value}'] polygon`);
97
+ ele.dblclick();
98
+ }, 1);
98
99
  }
99
100
  }
100
101
 
@@ -118,30 +119,31 @@ const app = createApp({
118
119
  const dotText = await res.text();
119
120
 
120
121
  // create graph instance once
121
- const graphUI = new GraphUI("#graph", {
122
- onSchemaShiftClick: (id) => {
123
- if (state.rawSchemas.has(id)) {
124
- resetDetailPanels()
125
- schemaFieldFilterSchema.value = id;
126
- showSchemaFieldFilter.value = true;
127
- }
128
- },
129
- onSchemaClick: (id) => {
130
- resetDetailPanels()
131
- if (state.rawSchemas.has(id)) {
132
- schemaCodeName.value = id;
133
- state.detailDrawer = true;
134
- }
135
- if (id in state.routeItems) {
136
- routeCodeId.value = id;
137
- showRouteDetail.value = true;
138
- }
139
- },
140
- resetCb: () => {
141
- resetDetailPanels()
142
- }
143
- });
144
-
122
+ if (!graphUI) {
123
+ graphUI = new GraphUI("#graph", {
124
+ onSchemaShiftClick: (id) => {
125
+ if (state.rawSchemas.has(id)) {
126
+ resetDetailPanels();
127
+ schemaFieldFilterSchema.value = id;
128
+ showSchemaFieldFilter.value = true;
129
+ }
130
+ },
131
+ onSchemaClick: (id) => {
132
+ resetDetailPanels();
133
+ if (state.rawSchemas.has(id)) {
134
+ schemaCodeName.value = id;
135
+ state.detailDrawer = true;
136
+ }
137
+ if (id in state.routeItems) {
138
+ routeCodeId.value = id;
139
+ showRouteDetail.value = true;
140
+ }
141
+ },
142
+ resetCb: () => {
143
+ resetDetailPanels();
144
+ },
145
+ });
146
+ }
145
147
  await graphUI.render(dotText, resetZoom);
146
148
  } catch (e) {
147
149
  console.error("Generate failed", e);
@@ -213,9 +215,9 @@ const app = createApp({
213
215
  }
214
216
 
215
217
  function resetDetailPanels() {
216
- state.detailDrawer = false
218
+ state.detailDrawer = false;
217
219
  showRouteDetail.value = false;
218
- schemaCodeName.value = ''
220
+ schemaCodeName.value = "";
219
221
  }
220
222
 
221
223
  async function onReset() {
@@ -224,8 +226,8 @@ const app = createApp({
224
226
  state.schemaId = null;
225
227
  // state.showFields = "object";
226
228
  state.brief = false;
227
- state.focus = false
228
- schemaCodeName.value = ''
229
+ state.focus = false;
230
+ schemaCodeName.value = "";
229
231
  onGenerate();
230
232
  }
231
233
 
@@ -234,11 +236,11 @@ const app = createApp({
234
236
  state._tag = tagName;
235
237
  state.tag = tagName;
236
238
  state.routeId = "";
237
- state.focus = false
238
- schemaCodeName.value = ''
239
+ state.focus = false;
240
+ schemaCodeName.value = "";
239
241
  onGenerate();
240
242
  } else {
241
- state._tag = null
243
+ state._tag = null;
242
244
  }
243
245
 
244
246
  state.detailDrawer = false;
@@ -253,8 +255,8 @@ const app = createApp({
253
255
  }
254
256
  state.detailDrawer = false;
255
257
  showRouteDetail.value = false;
256
- state.focus = false
257
- schemaCodeName.value = ''
258
+ state.focus = false;
259
+ schemaCodeName.value = "";
258
260
  onGenerate();
259
261
  }
260
262
 
@@ -276,24 +278,24 @@ const app = createApp({
276
278
  function startDragDrawer(e) {
277
279
  const startX = e.clientX;
278
280
  const startWidth = state.drawerWidth;
279
-
281
+
280
282
  function onMouseMove(moveEvent) {
281
- const deltaX = startX - moveEvent.clientX;
283
+ const deltaX = startX - moveEvent.clientX;
282
284
  const newWidth = Math.max(300, Math.min(800, startWidth + deltaX));
283
285
  state.drawerWidth = newWidth;
284
286
  }
285
-
287
+
286
288
  function onMouseUp() {
287
- document.removeEventListener('mousemove', onMouseMove);
288
- document.removeEventListener('mouseup', onMouseUp);
289
- document.body.style.cursor = '';
290
- document.body.style.userSelect = '';
289
+ document.removeEventListener("mousemove", onMouseMove);
290
+ document.removeEventListener("mouseup", onMouseUp);
291
+ document.body.style.cursor = "";
292
+ document.body.style.userSelect = "";
291
293
  }
292
-
293
- document.addEventListener('mousemove', onMouseMove);
294
- document.addEventListener('mouseup', onMouseUp);
295
- document.body.style.cursor = 'col-resize';
296
- document.body.style.userSelect = 'none';
294
+
295
+ document.addEventListener("mousemove", onMouseMove);
296
+ document.addEventListener("mouseup", onMouseUp);
297
+ document.body.style.cursor = "col-resize";
298
+ document.body.style.userSelect = "none";
297
299
  e.preventDefault();
298
300
  }
299
301
 
@@ -333,7 +335,7 @@ const app = createApp({
333
335
  renderCoreData,
334
336
  toggleShowField,
335
337
  startDragDrawer,
336
- onFocusChange
338
+ onFocusChange,
337
339
  };
338
340
  },
339
341
  });