fastapi-voyager 0.11.1__py3-none-any.whl → 0.11.2__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/render.py CHANGED
@@ -10,10 +10,12 @@ class Renderer:
10
10
  show_fields: FieldType = 'single',
11
11
  module_color: dict[str, str] | None = None,
12
12
  schema: str | None = None,
13
+ show_module: bool = True
13
14
  ) -> None:
14
15
  self.show_fields = show_fields if show_fields in ('single', 'object', 'all') else 'single'
15
16
  self.module_color = module_color or {}
16
17
  self.schema = schema
18
+ self.show_module = show_module
17
19
 
18
20
  def render_schema_label(self, node: SchemaNode) -> str:
19
21
  has_base_fields = any(f.from_base for f in node.fields)
@@ -60,13 +62,18 @@ class Renderer:
60
62
  elif link.type == 'subset':
61
63
  return f"""{h(link.source)}:e -> {h(link.target)}:w [style = "solid, dashed", dir="back", minlen=3, taillabel = "< subset >", color = "orange", tailport="n"];"""
62
64
  elif link.type == 'tag_to_schema':
63
- return f"""{h(link.source)}:e -> {h(link.target)}:w [style = "solid", dir="back", arrowtail="odot", minlen=3];"""
65
+ return f"""{h(link.source)}:e -> {h(link.target)}:w [style = "solid", minlen=3];"""
64
66
  else:
65
67
  raise ValueError(f'Unknown link type: {link.type}')
66
68
 
67
- def render_module_schema_content(self, mods: list[ModuleNode]) -> str:
68
- module_color_flag = set(self.module_color.keys())
69
-
69
+ def render_module_schema_content(self, nodes: list[SchemaNode]) -> str:
70
+ def render_node(node: SchemaNode) -> str:
71
+ return f'''
72
+ "{node.id}" [
73
+ label = {self.render_schema_label(node)}
74
+ shape = "plain"
75
+ margin="0.5,0.1"
76
+ ];'''
70
77
  def render_module_schema(mod: ModuleNode) -> str:
71
78
  color: Optional[str] = None
72
79
 
@@ -76,14 +83,7 @@ class Renderer:
76
83
  color = self.module_color[k]
77
84
  break
78
85
 
79
- inner_nodes = [
80
- f'''
81
- "{node.id}" [
82
- label = {self.render_schema_label(node)}
83
- shape = "plain"
84
- margin="0.5,0.1"
85
- ];''' for node in mod.schema_nodes
86
- ]
86
+ inner_nodes = [ render_node(node) for node in mod.schema_nodes ]
87
87
  inner_nodes_str = '\n'.join(inner_nodes)
88
88
  child_str = '\n'.join(render_module_schema(m) for m in mod.modules)
89
89
  return f'''
@@ -98,34 +98,49 @@ class Renderer:
98
98
  {inner_nodes_str}
99
99
  {child_str}
100
100
  }}'''
101
- return '\n'.join(render_module_schema(m) for m in mods)
101
+ if self.show_module:
102
+ module_schemas = build_module_schema_tree(nodes)
103
+ module_color_flag = set(self.module_color.keys())
104
+ return '\n'.join(render_module_schema(m) for m in module_schemas)
105
+ else:
106
+ return '\n'.join(render_node(n) for n in nodes)
102
107
 
103
- def render_module_route(self, mod: ModuleRoute) -> str:
104
- # Inner route nodes, same style as flat route_str
105
- inner_nodes = [
106
- f'''
107
- "{r.id}" [
108
- label = " {r.name} | {r.response_schema} "
109
- margin="0.5,0.1"
110
- shape = "record"
111
- ];''' for r in mod.routes
112
- ]
113
- inner_nodes_str = '\n'.join(inner_nodes)
114
- child_str = '\n'.join(self.render_module_route(m) for m in mod.modules)
115
- return f'''
116
- subgraph cluster_route_module_{mod.fullname.replace('.', '_')} {{
117
- tooltip="{mod.fullname}"
118
- color = "#666"
119
- style="rounded"
120
- label = " {mod.name}"
121
- labeljust = "l"
122
- {inner_nodes_str}
123
- {child_str}
124
- }}'''
108
+ def render_module_route_content(self, routes: list[Route]) -> str:
109
+ def render_route(route: Route) -> str:
110
+ response_schema = route.response_schema[:25] + '..' if len(route.response_schema) > 25 else route.response_schema
111
+ return f'''
112
+ "{route.id}" [
113
+ label = " {route.name} | {response_schema} "
114
+ margin="0.5,0.1"
115
+ shape = "record"
116
+ ];'''
117
+
118
+ def render_module_route(mod: ModuleRoute) -> str:
119
+ # Inner route nodes, same style as flat route_str
120
+ inner_nodes = [
121
+ render_route(r) for r in mod.routes
122
+ ]
123
+ inner_nodes_str = '\n'.join(inner_nodes)
124
+ child_str = '\n'.join(render_module_route(m) for m in mod.modules)
125
+ return f'''
126
+ subgraph cluster_route_module_{mod.fullname.replace('.', '_')} {{
127
+ tooltip="{mod.fullname}"
128
+ color = "#666"
129
+ style="rounded"
130
+ label = " {mod.name}"
131
+ labeljust = "l"
132
+ {inner_nodes_str}
133
+ {child_str}
134
+ }}'''
135
+ if self.show_module:
136
+ module_routes = build_module_route_tree(routes)
137
+ module_routes_str = '\n'.join(render_module_route(m) for m in module_routes)
138
+ return module_routes_str
139
+ else:
140
+ return '\n'.join(render_route(r) for r in routes)
141
+
125
142
 
126
143
  def render_dot(self, tags: list[Tag], routes: list[Route], nodes: list[SchemaNode], links: list[Link], spline_line=False) -> str:
127
- module_schemas = build_module_schema_tree(nodes)
128
- module_routes = build_module_route_tree(routes)
129
144
 
130
145
  tag_str = '\n'.join([
131
146
  f'''
@@ -136,9 +151,8 @@ class Renderer:
136
151
  ];''' for t in tags
137
152
  ])
138
153
 
139
-
140
- module_schemas_str = self.render_module_schema_content(module_schemas)
141
- module_routes_str = '\n'.join(self.render_module_route(m) for m in module_routes)
154
+ module_routes_str = self.render_module_route_content(routes)
155
+ module_schemas_str = self.render_module_schema_content(nodes)
142
156
  link_str = '\n'.join(self.render_link(link) for link in links)
143
157
 
144
158
  dot_str = f'''
fastapi_voyager/server.py CHANGED
@@ -33,6 +33,7 @@ class Payload(BaseModel):
33
33
  show_meta: bool = False
34
34
  brief: bool = False
35
35
  hide_primitive_route: bool = False
36
+ show_module: bool = True
36
37
 
37
38
 
38
39
  def create_route(
@@ -77,6 +78,7 @@ def create_route(
77
78
  module_color=module_color,
78
79
  route_name=payload.route_name,
79
80
  hide_primitive_route=payload.hide_primitive_route,
81
+ show_module=payload.show_module,
80
82
  )
81
83
  voyager.analysis(target_app)
82
84
  if payload.brief:
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.11.1"
2
+ __version__ = "0.11.2"
@@ -27,6 +27,7 @@ class Voyager:
27
27
  module_color: dict[str, str] | None = None,
28
28
  route_name: str | None = None,
29
29
  hide_primitive_route: bool = False,
30
+ show_module: bool = True
30
31
  ):
31
32
 
32
33
  self.routes: list[Route] = []
@@ -48,6 +49,7 @@ class Voyager:
48
49
  self.module_color = module_color or {}
49
50
  self.route_name = route_name
50
51
  self.hide_primitive_route = hide_primitive_route
52
+ self.show_module = show_module
51
53
 
52
54
 
53
55
  def _get_available_route(self, app: FastAPI):
@@ -301,7 +303,7 @@ class Voyager:
301
303
  node_set=self.node_set,
302
304
  )
303
305
 
304
- renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema)
306
+ renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema, show_module=self.show_module)
305
307
 
306
308
  _tags, _routes, _links = self.handle_hide(_tags, _routes, _links)
307
309
  return renderer.render_dot(_tags, _routes, _nodes, _links)
@@ -326,7 +328,7 @@ class Voyager:
326
328
  links=_links,
327
329
  )
328
330
 
329
- renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema)
331
+ renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema, show_module=self.show_module)
330
332
 
331
333
  _tags, _routes, _links = self.handle_hide(_tags, _routes, _links)
332
334
  return renderer.render_dot(_tags, _routes, _nodes, _links, True)
@@ -350,7 +352,7 @@ class Voyager:
350
352
  links=_links,
351
353
  )
352
354
 
353
- renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema)
355
+ renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema, show_module=self.show_module)
354
356
 
355
357
  _tags, _routes, _links = self.handle_hide(_tags, _routes, _links)
356
358
  return renderer.render_dot(_tags, _routes, _nodes, _links, True)
@@ -148,6 +148,7 @@
148
148
  side="right"
149
149
  style="border-left: 1px solid #888;"
150
150
  bordered
151
+ overlay
151
152
  >
152
153
  <!-- 可拖拽的调整栏 -->
153
154
  <div
@@ -265,32 +266,44 @@
265
266
  <div style="position: relative; width: 100%; height: 100%;">
266
267
  <div id="graph" class="adjust-fit"></div>
267
268
  <div style="position: absolute; left: 8px; bottom: 8px; z-index: 10; background: rgba(255,255,255,0.85); border-radius: 4px; padding: 2px 8px;">
268
- <div>
269
+ <div class="q-mt-sm">
269
270
  <q-toggle
270
271
  v-model="state.focus"
271
272
  v-show="schemaCodeName"
272
273
  @update:model-value="val => onFocusChange(val)"
273
274
  label="Focus"
275
+ dense
274
276
  title="pick a schema and toggle focus on to display related nodes only"
275
277
  />
276
278
  </div>
277
- <div>
279
+ <div class="q-mt-sm">
278
280
  <q-toggle
279
281
  v-if="state.enableBriefMode"
282
+ dense
280
283
  v-model="state.brief"
281
284
  label="Brief Mode"
282
285
  @update:model-value="(val) => toggleBrief(val)"
283
286
  title="skip middle classes, config module_prefix to enable it"
284
287
  />
285
288
  </div>
286
- <div>
289
+ <div class="q-mt-sm">
287
290
  <q-toggle
288
291
  v-model="state.hidePrimitiveRoute"
289
292
  @update:model-value="(val) => toggleHidePrimitiveRoute(val)"
290
293
  label="Hide Primitive"
294
+ dense
291
295
  title="hide routes return primitive"
292
296
  />
293
297
  </div>
298
+ <div class="q-mt-sm">
299
+ <q-toggle
300
+ v-model="state.showModule"
301
+ @update:model-value="(val) => toggleShowModule(val)"
302
+ label="Show Module Cluster"
303
+ dense
304
+ title="show module cluster"
305
+ />
306
+ </div>
294
307
  </div>
295
308
  </div>
296
309
  </template>
@@ -34,6 +34,7 @@ const app = createApp({
34
34
  detailDrawer: false,
35
35
  drawerWidth: 300, // drawer 宽度
36
36
  version: "", // version from backend
37
+ showModule: true
37
38
  });
38
39
 
39
40
  const showDetail = ref(false);
@@ -112,6 +113,7 @@ const app = createApp({
112
113
  show_fields: state.showFields,
113
114
  brief: state.brief,
114
115
  hide_primitive_route: state.hidePrimitiveRoute,
116
+ show_module: state.showModule,
115
117
  };
116
118
  const res = await fetch("dot", {
117
119
  method: "POST",
@@ -261,6 +263,11 @@ const app = createApp({
261
263
  onGenerate();
262
264
  }
263
265
 
266
+ function toggleShowModule(val) {
267
+ state.showModule = val;
268
+ onGenerate()
269
+ }
270
+
264
271
  function toggleShowField(field) {
265
272
  state.showFields = field;
266
273
  onGenerate(false);
@@ -337,6 +344,7 @@ const app = createApp({
337
344
  toggleShowField,
338
345
  startDragDrawer,
339
346
  onFocusChange,
347
+ toggleShowModule
340
348
  };
341
349
  },
342
350
  });
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-voyager
3
- Version: 0.11.1
3
+ Version: 0.11.2
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
@@ -16,6 +16,8 @@ Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
19
21
  Requires-Python: >=3.10
20
22
  Requires-Dist: fastapi>=0.110
21
23
  Requires-Dist: pydantic-resolve>=1.13.2
@@ -32,7 +34,14 @@ Description-Content-Type: text/markdown
32
34
 
33
35
  > This repo is still in early stage, it supports pydantic v2 only
34
36
 
35
- Inspect your API interactively!
37
+ FastAPI can help you:
38
+
39
+ - design your API
40
+ - inspect your API
41
+ - refactor your API
42
+
43
+ interactively !!
44
+
36
45
 
37
46
  [visit online demo](https://www.newsyeah.fun/voyager/) of project: [composition oriented development pattern](https://github.com/allmonday/composition-oriented-development-pattern)
38
47
 
@@ -51,6 +60,22 @@ uv add fastapi-voyager
51
60
  voyager -m path.to.your.app.module --server
52
61
  ```
53
62
 
63
+ ## Mount into project
64
+
65
+ ```python
66
+ from fastapi import FastAPI
67
+ from fastapi_voyager import create_voyager
68
+ from tests.demo import app
69
+
70
+ app.mount('/voyager', create_voyager(
71
+ app,
72
+ module_color={"tests.service": "red"},
73
+ module_prefix="tests.service"),
74
+ swagger_url="/docs")
75
+ ```
76
+
77
+ more about [sub application](https://fastapi.tiangolo.com/advanced/sub-applications/?h=sub)
78
+
54
79
 
55
80
  ## Feature
56
81
 
@@ -99,20 +124,6 @@ click a node to highlight it's upperstream and downstream nodes. figure out the
99
124
  <img width="882" height="445" alt="image" src="https://github.com/user-attachments/assets/158560ef-63ca-4991-9b7d-587be4fa04e4" />
100
125
 
101
126
 
102
- ## Mount to target project
103
-
104
- ```python
105
- from fastapi import FastAPI
106
- from fastapi_voyager import create_voyager
107
- from tests.demo import app
108
-
109
- app.mount('/voyager', create_voyager(
110
- app,
111
- module_color={"tests.service": "red"},
112
- module_prefix="tests.service"))
113
- ```
114
-
115
- more about [sub application](https://fastapi.tiangolo.com/advanced/sub-applications/?h=sub)
116
127
 
117
128
 
118
129
  ## Command Line Usage
@@ -257,12 +268,12 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
257
268
  - [x] config docs path
258
269
  - [x] provide option to hide routes in brief mode (auto hide in full graph mode)
259
270
  - 0.11.2
260
- - [ ] enable/disable module cluster (to save space)
271
+ - [x] enable/disable module cluster (to save space)
272
+ - 0.11.3
273
+ - [ ] add loading for field detail panel
261
274
  - [ ] logging information
262
275
  - [ ] sort field name
263
276
  - [ ] set max limit for fields
264
- - [ ] add info icon alone with schema node
265
- - [ ] add loading for field detail panel
266
277
 
267
278
  #### 0.12
268
279
  - [ ] add tests
@@ -2,19 +2,19 @@ fastapi_voyager/__init__.py,sha256=tZy0Nkj8kTaMgbvHy-mGxVcFGVX0Km-36dnzsAIG2uk,2
2
2
  fastapi_voyager/cli.py,sha256=kQb4g6JEGZR99e5r8LyFFEeb_-uT-n_gp_sDoYG3R7k,11118
3
3
  fastapi_voyager/filter.py,sha256=GY2J9Vfsf_wbFwC-0t74-Lf-OlO77PnhEXD_rmgkfSw,11574
4
4
  fastapi_voyager/module.py,sha256=Z2QHNmiLk6ZAJlm2nSmO875Q33TweSg8UxZSzIpU9zY,3499
5
- fastapi_voyager/render.py,sha256=EtSkQWb3k3g6T6pnDW2GF9BM-NkDkbDjhlsnF0u-IG0,7970
6
- fastapi_voyager/server.py,sha256=UkTeMFduq4Ebt6eNisfPVUiy04XzHmOaZkBuwEk5mus,6617
5
+ fastapi_voyager/render.py,sha256=vdwqIync2wsP8gMPY0v_XjRhdPBtbKyRT8yTBa_Ep3Y,8744
6
+ fastapi_voyager/server.py,sha256=TOzGEyzE_lviWMcKuSYHDaoArMApENIfvzHdAzOuKY4,6679
7
7
  fastapi_voyager/type.py,sha256=VmcTB1G-LOT70EWCzi4LU_FUkSGWUIBJX15T_J5HnOo,1764
8
8
  fastapi_voyager/type_helper.py,sha256=hjBC4E0tgBpQDlYxGg74uK07SXjsrAgictEETJfIpYM,9231
9
- fastapi_voyager/version.py,sha256=mqCYzIn6okf6Z1_ZJPQL0K0ySha6En_onGIReGtmFxM,49
10
- fastapi_voyager/voyager.py,sha256=etSIrqJJQXArxYLp_45wa0kKdqSebYy9TBYbuyK0iC4,13332
9
+ fastapi_voyager/version.py,sha256=0HoMD0zNdXsQi-ZwAZecr3qNO9tE7hFLleGl1vf67AA,49
10
+ fastapi_voyager/voyager.py,sha256=hNal25S5Hi_ZRe-gnmdUKt8tnRd-BRCrzaybAEJ_1HI,13498
11
11
  fastapi_voyager/web/graph-ui.js,sha256=DTedkpZNbtufexONVkJ8mOwF_-VnvxoReYHtox6IKR4,5842
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=1WkNQ_xuS-oy4WkRChk53b9mZjATC5XZbIhPZ_pVBJQ,17317
14
+ fastapi_voyager/web/index.html,sha256=9zp6id31DB_cWdAoCkO5NkeHCMTnNaz2VOdJO24__eM,17837
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=ZM1BmFcA9hz6FmP5W-J1xxNE8mE_Rsj-5-dlh43zfcQ,10483
17
+ fastapi_voyager/web/vue-main.js,sha256=sUjLmn06jYy1guwxAcouHwkcjxGD2UgZgkiiIEDbk04,10663
18
18
  fastapi_voyager/web/component/render-graph.js,sha256=e8Xgh2Kl-nYU0P1gstEmAepCgFnk2J6UdxW8TlMafGs,2322
19
19
  fastapi_voyager/web/component/route-code-display.js,sha256=8NJPPjNRUC21gjpY8XYEQs4RBbhX1pCiqEhJp39ku6k,3678
20
20
  fastapi_voyager/web/component/schema-code-display.js,sha256=UgFotzvqSuhnPXNOr6w_r1fV2_savRiCdokEvferutE,6244
@@ -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.11.1.dist-info/METADATA,sha256=K47oTptR1SQCs7hgvlhJSX9FeDO0knh-QFtgjjfe8mY,9767
30
- fastapi_voyager-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
- fastapi_voyager-0.11.1.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
32
- fastapi_voyager-0.11.1.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
33
- fastapi_voyager-0.11.1.dist-info/RECORD,,
29
+ fastapi_voyager-0.11.2.dist-info/METADATA,sha256=ImBnVBoy2rqVkdOrd037aIgj9V8pRc0cN7dl5aXv3dM,9917
30
+ fastapi_voyager-0.11.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
+ fastapi_voyager-0.11.2.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
32
+ fastapi_voyager-0.11.2.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
33
+ fastapi_voyager-0.11.2.dist-info/RECORD,,