fastapi-voyager 0.6.2__py3-none-any.whl → 0.7.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/cli.py CHANGED
@@ -175,6 +175,12 @@ Examples:
175
175
  default="127.0.0.1",
176
176
  help="Host/IP for the preview server when --server is used (default: 127.0.0.1). Use 0.0.0.0 to listen on all interfaces."
177
177
  )
178
+ parser.add_argument(
179
+ "--module_prefix",
180
+ type=str,
181
+ default=None,
182
+ help="Prefix routes with module name when rendering brief view (only valid with --server)"
183
+ )
178
184
 
179
185
  parser.add_argument(
180
186
  "--version", "-v",
@@ -233,6 +239,9 @@ Examples:
233
239
  if not any(mc.startswith(key + ":") for mc in existing):
234
240
  args.module_color = (args.module_color or []) + [d]
235
241
 
242
+ if args.module_prefix and not args.server:
243
+ parser.error("--module_prefix can only be used together with --server")
244
+
236
245
  # Validate required target if not demo
237
246
  if not args.demo and not (args.module_name or args.module):
238
247
  parser.error("You must provide a module file, -m module name, or use --demo")
@@ -272,7 +281,11 @@ Examples:
272
281
  except ImportError:
273
282
  print("Error: uvicorn is required to run the server. Install via 'pip install uvicorn' or 'uv add uvicorn'.")
274
283
  sys.exit(1)
275
- app_server = viz_server.create_app_with_fastapi(app, module_color=module_color)
284
+ app_server = viz_server.create_app_with_fastapi(
285
+ app,
286
+ module_color=module_color,
287
+ module_prefix=args.module_prefix,
288
+ )
276
289
  print(f"Starting preview server at http://{args.host}:{args.port} ... (Ctrl+C to stop)")
277
290
  uvicorn.run(app_server, host=args.host, port=args.port)
278
291
  else:
fastapi_voyager/filter.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
+ from collections import deque
2
3
  from typing import Tuple
3
- from fastapi_voyager.type import Tag, Route, SchemaNode, Link
4
+ from fastapi_voyager.type import Tag, Route, SchemaNode, Link, PK
4
5
 
5
6
 
6
7
  def filter_graph(
@@ -103,3 +104,92 @@ def filter_graph(
103
104
  _routes = [r for r in routes if r.id in included_ids]
104
105
 
105
106
  return _tags, _routes, _nodes, _links
107
+
108
+
109
+ def filter_subgraph(
110
+ *,
111
+ tags: list[Tag],
112
+ routes: list[Route],
113
+ links: list[Link],
114
+ nodes: list[SchemaNode],
115
+ module_prefix: str
116
+ ) -> Tuple[list[Tag], list[Route], list[SchemaNode], list[Link]]:
117
+ """Collapse schema graph so routes link directly to nodes whose module matches ``module_prefix``.
118
+
119
+ The routine keeps tag→route links untouched, prunes schema nodes whose module does not start
120
+ with ``module_prefix``, and merges the remaining schema relationships so each route connects
121
+ directly to the surviving schema nodes. Traversal stops once a qualifying node is reached and
122
+ guards against cycles in the schema graph.
123
+ """
124
+
125
+ if not module_prefix:
126
+ # empty prefix keeps existing graph structure, so simply reuse incoming data
127
+ return tags, routes, nodes, [lk for lk in links if lk.type in ("tag_route", "route_to_schema")]
128
+
129
+ route_links = [lk for lk in links if lk.type == "route_to_schema"]
130
+ schema_links = [lk for lk in links if lk.type in {"schema", "parent", "subset"}]
131
+ tag_route_links = [lk for lk in links if lk.type == "tag_route"]
132
+
133
+ node_lookup: dict[str, SchemaNode] = {node.id: node for node in nodes}
134
+
135
+ filtered_nodes = [node for node in nodes if node_lookup[node.id].module.startswith(module_prefix)]
136
+ filtered_node_ids = {node.id for node in filtered_nodes}
137
+
138
+ adjacency: dict[str, list[str]] = {}
139
+ for link in schema_links:
140
+ if link.source_origin not in node_lookup or link.target_origin not in node_lookup:
141
+ continue
142
+ adjacency.setdefault(link.source_origin, [])
143
+ if link.target_origin not in adjacency[link.source_origin]:
144
+ adjacency[link.source_origin].append(link.target_origin)
145
+
146
+ merged_links: list[Link] = []
147
+ seen_pairs: set[tuple[str, str]] = set()
148
+
149
+ for link in route_links:
150
+ route_id = link.source_origin
151
+ start_node_id = link.target_origin
152
+ if route_id is None or start_node_id is None:
153
+ continue
154
+ if start_node_id not in node_lookup:
155
+ continue
156
+
157
+ visited: set[str] = set()
158
+ queue: deque[str] = deque([start_node_id])
159
+
160
+ while queue:
161
+ current = queue.popleft()
162
+ if current in visited:
163
+ continue
164
+ visited.add(current)
165
+
166
+ if current in filtered_node_ids:
167
+ key = (route_id, current)
168
+ if key not in seen_pairs:
169
+ seen_pairs.add(key)
170
+ merged_links.append(
171
+ Link(
172
+ source=link.source,
173
+ source_origin=route_id,
174
+ target=f"{current}::{PK}",
175
+ target_origin=current,
176
+ type="route_to_schema",
177
+ )
178
+ )
179
+ # stop traversing past a qualifying node
180
+ continue
181
+
182
+ for next_node in adjacency.get(current, () ):
183
+ if next_node not in visited:
184
+ queue.append(next_node)
185
+
186
+ module_prefix_links = [
187
+ lk
188
+ for lk in links
189
+ if (lk.source_origin or "").startswith(module_prefix)
190
+ and (lk.target_origin or "").startswith(module_prefix)
191
+ ]
192
+
193
+ filtered_links = tag_route_links + merged_links + module_prefix_links
194
+
195
+ return tags, routes, filtered_nodes, filtered_links
fastapi_voyager/server.py CHANGED
@@ -32,10 +32,12 @@ class Payload(BaseModel):
32
32
  route_name: Optional[str] = None
33
33
  show_fields: str = 'object'
34
34
  show_meta: bool = False
35
+ brief: bool = False
35
36
 
36
37
  def create_route(
37
38
  target_app: FastAPI,
38
39
  module_color: dict[str, str] | None = None,
40
+ module_prefix: Optional[str] = None,
39
41
  ):
40
42
  router = APIRouter(tags=['fastapi-voyager'])
41
43
 
@@ -73,7 +75,10 @@ def create_route(
73
75
  load_meta=False,
74
76
  )
75
77
  voyager.analysis(target_app)
76
- return voyager.render_dot()
78
+ if payload.brief:
79
+ return voyager.render_brief_dot(module_prefix=module_prefix)
80
+ else:
81
+ return voyager.render_dot()
77
82
 
78
83
  @router.post("/dot-core-data", response_model=CoreData)
79
84
  def get_filtered_dot_core_data(payload: Payload) -> str:
@@ -117,8 +122,9 @@ def create_app_with_fastapi(
117
122
  target_app: FastAPI,
118
123
  module_color: dict[str, str] | None = None,
119
124
  gzip_minimum_size: int | None = 500,
125
+ module_prefix: Optional[str] = None,
120
126
  ) -> FastAPI:
121
- router = create_route(target_app, module_color=module_color)
127
+ router = create_route(target_app, module_color=module_color, module_prefix=module_prefix)
122
128
 
123
129
  app = FastAPI(title="fastapi-voyager demo server")
124
130
  if gzip_minimum_size is not None and gzip_minimum_size >= 0:
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.6.2"
2
+ __version__ = "0.7.2"
@@ -12,7 +12,7 @@ from fastapi_voyager.type_helper import (
12
12
  )
13
13
  from pydantic import BaseModel
14
14
  from fastapi_voyager.type import Route, SchemaNode, Link, Tag, LinkType, FieldType, PK, CoreData
15
- from fastapi_voyager.filter import filter_graph
15
+ from fastapi_voyager.filter import filter_graph, filter_subgraph
16
16
  from fastapi_voyager.render import Renderer
17
17
  import pydantic_resolve.constant as const
18
18
 
@@ -271,4 +271,16 @@ class Voyager:
271
271
  node_set=self.node_set,
272
272
  )
273
273
  renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema)
274
+ return renderer.render_dot(_tags, _routes, _nodes, _links)
275
+
276
+ def render_brief_dot(self, module_prefix: str | None = None):
277
+ print(module_prefix)
278
+ _tags, _routes, _nodes, _links = filter_subgraph(
279
+ module_prefix=module_prefix,
280
+ tags=self.tags,
281
+ routes=self.routes,
282
+ nodes=self.nodes,
283
+ links=self.links,
284
+ )
285
+ renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=None)
274
286
  return renderer.render_dot(_tags, _routes, _nodes, _links)
@@ -109,6 +109,14 @@
109
109
  </div>
110
110
  </div>
111
111
 
112
+ <div class="col-auto">
113
+ <q-checkbox
114
+ v-model="state.brief"
115
+ label="Brief"
116
+ dense
117
+ />
118
+ </div>
119
+
112
120
  <div class="col-auto">
113
121
  <q-btn-dropdown
114
122
  class="q-ml-md"
@@ -22,6 +22,7 @@ const app = createApp({
22
22
  { label: "Object fields", value: "object" },
23
23
  { label: "All fields", value: "all" },
24
24
  ],
25
+ brief: false,
25
26
  generating: false,
26
27
  rawTags: [], // [{ name, routes: [{ id, name }] }]
27
28
  rawSchemas: [], // [{ name, fullname }]
@@ -132,6 +133,7 @@ const app = createApp({
132
133
  schema_name: state.schemaFullname || null,
133
134
  route_name: state.routeId || null,
134
135
  show_fields: state.showFields,
136
+ brief: state.brief,
135
137
  };
136
138
 
137
139
  const res = await fetch("dot", {
@@ -179,6 +181,7 @@ const app = createApp({
179
181
  schema_name: state.schemaFullname || null,
180
182
  route_name: state.routeId || null,
181
183
  show_fields: state.showFields,
184
+ brief: state.brief,
182
185
  };
183
186
  const res = await fetch("dot-core-data", {
184
187
  method: "POST",
@@ -238,6 +241,7 @@ const app = createApp({
238
241
  state.routeId = "";
239
242
  state.schemaFullname = null;
240
243
  state.showFields = "object";
244
+ state.brief = false;
241
245
  await loadInitial();
242
246
  }
243
247
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-voyager
3
- Version: 0.6.2
3
+ Version: 0.7.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
@@ -146,7 +146,7 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
146
146
 
147
147
  ## Plan before v1.0
148
148
 
149
- features
149
+ features:
150
150
  - [x] group schemas by module hierarchy
151
151
  - [x] module-based coloring via Analytics(module_color={...})
152
152
  - [x] view in web browser
@@ -162,31 +162,31 @@ features
162
162
  - [x] alt+click to show field details
163
163
  - [x] display source code of routes (including response_model)
164
164
  - [x] handle excluded field
165
- - [ ] add tooltips
166
- - [ ] route
167
- - [ ] group routes by module hierarchy
165
+ - [x] add tooltips
166
+ - [x] route
167
+ - [x] group routes by module hierarchy
168
168
  - [ ] add response_model in route
169
- - [ ] support dataclass
169
+ - [ ] support dataclass (pending)
170
170
  - [ ] click field to highlight links
171
171
  - [ ] user can generate nodes/edges manually and connect to generated ones
172
172
  - [ ] add owner
173
173
  - [ ] ui optimization
174
174
  - [ ] fixed left/right bar show field information
175
- - [ ] display standard ER diagram
175
+ - [ ] display standard ER diagram `difficult`
176
176
  - [ ] display potential invalid links
177
177
  - [ ] integration with pydantic-resolve
178
178
  - [ ] show difference between resolve, post fields
179
179
  - [x] strikethrough for excluded fields
180
180
  - [ ] display loader as edges
181
+ - [x] export voyager core data into json (for better debugging)
182
+ - [x] add api to rebuild core data from json, and render it
183
+ - [x] fix Generic case `test_generic.py`
184
+
185
+ bugs & non feature:
186
+ - [x] fix duplicated link from class and parent class, it also break clicking highlight
181
187
  - [ ] add tests
182
188
  - [ ] refactor
183
189
  - [ ] abstract render module
184
- - [ ] export voyager core data into json (for better debugging)
185
- - [ ] add api to rebuild core data from json, and render it
186
- - [x] fix Generic case `test_generic.py`
187
-
188
- bugs:
189
- - [ ] fix duplicated link from class and parent class, it also break clicking highlight
190
190
 
191
191
  ## Using with pydantic-resolve
192
192
 
@@ -203,5 +203,11 @@ TODO: ...
203
203
 
204
204
  ## Changelog
205
205
 
206
- - 0.6.2:
207
- - fix generic related issue
206
+ - 0.7:
207
+ - 0.7.2
208
+ - keep links inside filtered nodes.
209
+ - 0.7.1
210
+ - support brief mode, you can use `--module_prefix tests.service` to show links between routes and filtered schemas, to make the graph less complicated.
211
+ - 0.6:
212
+ - 0.6.2:
213
+ - fix generic related issue
@@ -1,20 +1,20 @@
1
1
  fastapi_voyager/__init__.py,sha256=E5WTV_sYs2LK8I6jzA7AuvFU5a8_vjnDseC3DMha0iQ,149
2
- fastapi_voyager/cli.py,sha256=wWI0YEB7DaCfK6PkkEMOh3VxXGPV92NN8x-bB354Njs,10705
3
- fastapi_voyager/filter.py,sha256=uZrVVMhHG5E7j1wdsiB02RAyoDdF1q8A4J04oCboYAU,4644
2
+ fastapi_voyager/cli.py,sha256=2eixX7mtPsZvukc4vrwQOt6XTPJgHUKIGLBy3IIC2jE,11127
3
+ fastapi_voyager/filter.py,sha256=2Yt37o8mhqSqleafO4YRrumh_ExYUqzXFOxQRPuTbAc,8078
4
4
  fastapi_voyager/module.py,sha256=Z2QHNmiLk6ZAJlm2nSmO875Q33TweSg8UxZSzIpU9zY,3499
5
5
  fastapi_voyager/render.py,sha256=ctwad-KNbFajhgnA8OI8412s6s67UbV-dvZFXBt_Ssg,7410
6
- fastapi_voyager/server.py,sha256=6pDFrVd4AZP3KaqukJS7xq_f7ar4gLkb6XhSO5zoW_A,3782
6
+ fastapi_voyager/server.py,sha256=UT-fHggdqicIo5m3uUX86-XFhAVDLXpXBsBQwd1HdIg,4001
7
7
  fastapi_voyager/type.py,sha256=nad4WNxTcZFi7Mskw6p2W7v2Gs4f0giVLNoFjZlKmbA,1778
8
8
  fastapi_voyager/type_helper.py,sha256=f2Gy5r3Zi6a2wTkbqU9W-AkvcetajEYCfroACzcIcVY,9064
9
- fastapi_voyager/version.py,sha256=esNfySzRU9jKScInt96qMV6-yrFqc5OFPWfMqNLhEWE,48
10
- fastapi_voyager/voyager.py,sha256=qMXZrQsmUiBljhOFOUtXV--a5i7THz9l00UNoxDu_0w,10193
9
+ fastapi_voyager/version.py,sha256=PvKHya-0S2FiTp3eTEPk4ccgA86pgtDfAJ-N601Tvss,48
10
+ fastapi_voyager/voyager.py,sha256=uOQEzrs3o6UUUswvHGRKELNWYUH1ix0Z7SbzMHwm320,10709
11
11
  fastapi_voyager/web/graph-ui.js,sha256=eEjDnJVMvk35LdRoxcqX_fZxLFS9_bUrGAZL6K2O5C0,4176
12
12
  fastapi_voyager/web/graphviz.svg.css,sha256=zDCjjpT0Idufu5YOiZI76PL70-avP3vTyzGPh9M85Do,1563
13
13
  fastapi_voyager/web/graphviz.svg.js,sha256=lvAdbjHc-lMSk4GQp-iqYA2PCFX4RKnW7dFaoe0LUHs,16005
14
- fastapi_voyager/web/index.html,sha256=hRbsdraGUZYB1dP_FIifyhp6ZqT0VyoNE-U3S4lCRaQ,10611
14
+ fastapi_voyager/web/index.html,sha256=B_MaewnyHmEqozJHmPHYBfF6n5WOvrhoU3tDryS6ngY,10795
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=twafDl_vDIRjlY0Oltslg2_UAO4L1fezKxuKszvnbjU,8799
17
+ fastapi_voyager/web/vue-main.js,sha256=ZIxPa0mWOKK7xntfVWbAKzKSx_Tak-TLU-3rkr13wjI,8906
18
18
  fastapi_voyager/web/component/render-graph.js,sha256=e8Xgh2Kl-nYU0P1gstEmAepCgFnk2J6UdxW8TlMafGs,2322
19
19
  fastapi_voyager/web/component/route-code-display.js,sha256=NECC1OGcPCdDfbghtRJEnmFM6HmH5J3win2ibapWPeA,2649
20
20
  fastapi_voyager/web/component/schema-code-display.js,sha256=oOusgTvCaWGnoKb-NBwu0SXqJJf2PTUtp3lUczokTBM,5515
@@ -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.6.2.dist-info/METADATA,sha256=LGxHZaMAoK5qMgAJiWZTM4ukoDrrndj4R3NUKsygzko,6866
30
- fastapi_voyager-0.6.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
- fastapi_voyager-0.6.2.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
32
- fastapi_voyager-0.6.2.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
33
- fastapi_voyager-0.6.2.dist-info/RECORD,,
29
+ fastapi_voyager-0.7.2.dist-info/METADATA,sha256=4BI77g1dUQy0LM7Vkl8JlRgPBjFL07de6QHPFV5Dego,7154
30
+ fastapi_voyager-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
+ fastapi_voyager-0.7.2.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
32
+ fastapi_voyager-0.7.2.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
33
+ fastapi_voyager-0.7.2.dist-info/RECORD,,