fastapi-voyager 0.11.10__py3-none-any.whl → 0.11.11__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 +35 -47
- fastapi_voyager/filter.py +0 -1
- fastapi_voyager/render.py +64 -41
- fastapi_voyager/type_helper.py +6 -4
- fastapi_voyager/version.py +1 -1
- {fastapi_voyager-0.11.10.dist-info → fastapi_voyager-0.11.11.dist-info}/METADATA +29 -15
- {fastapi_voyager-0.11.10.dist-info → fastapi_voyager-0.11.11.dist-info}/RECORD +10 -10
- {fastapi_voyager-0.11.10.dist-info → fastapi_voyager-0.11.11.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.11.10.dist-info → fastapi_voyager-0.11.11.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.11.10.dist-info → fastapi_voyager-0.11.11.dist-info}/licenses/LICENSE +0 -0
fastapi_voyager/cli.py
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
"""Command line interface for fastapi-voyager."""
|
|
2
2
|
import argparse
|
|
3
|
-
import sys
|
|
4
|
-
import importlib.util
|
|
5
3
|
import importlib
|
|
4
|
+
import importlib.util
|
|
5
|
+
import logging
|
|
6
6
|
import os
|
|
7
|
+
import sys
|
|
7
8
|
from typing import Optional
|
|
8
9
|
|
|
9
10
|
from fastapi import FastAPI
|
|
10
|
-
from fastapi_voyager.voyager import Voyager
|
|
11
|
-
from fastapi_voyager.version import __version__
|
|
12
11
|
from fastapi_voyager import server as viz_server
|
|
12
|
+
from fastapi_voyager.version import __version__
|
|
13
|
+
from fastapi_voyager.voyager import Voyager
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
def load_fastapi_app_from_file(module_path: str, app_name: str = "app") -> Optional[FastAPI]:
|
|
@@ -22,7 +25,7 @@ def load_fastapi_app_from_file(module_path: str, app_name: str = "app") -> Optio
|
|
|
22
25
|
# Load the module
|
|
23
26
|
spec = importlib.util.spec_from_file_location("app_module", module_path)
|
|
24
27
|
if spec is None or spec.loader is None:
|
|
25
|
-
|
|
28
|
+
logger.error(f"Could not load module from {module_path}")
|
|
26
29
|
return None
|
|
27
30
|
|
|
28
31
|
module = importlib.util.module_from_spec(spec)
|
|
@@ -34,15 +37,13 @@ def load_fastapi_app_from_file(module_path: str, app_name: str = "app") -> Optio
|
|
|
34
37
|
app = getattr(module, app_name)
|
|
35
38
|
if isinstance(app, FastAPI):
|
|
36
39
|
return app
|
|
37
|
-
|
|
38
|
-
print(f"Error: '{app_name}' is not a FastAPI instance")
|
|
39
|
-
return None
|
|
40
|
-
else:
|
|
41
|
-
print(f"Error: No attribute '{app_name}' found in the module")
|
|
40
|
+
logger.error(f"'{app_name}' is not a FastAPI instance")
|
|
42
41
|
return None
|
|
42
|
+
logger.error(f"No attribute '{app_name}' found in the module")
|
|
43
|
+
return None
|
|
43
44
|
|
|
44
45
|
except Exception as e:
|
|
45
|
-
|
|
46
|
+
logger.error(f"Error loading FastAPI app: {e}")
|
|
46
47
|
return None
|
|
47
48
|
|
|
48
49
|
|
|
@@ -66,22 +67,20 @@ def load_fastapi_app_from_module(module_name: str, app_name: str = "app") -> Opt
|
|
|
66
67
|
app = getattr(module, app_name)
|
|
67
68
|
if isinstance(app, FastAPI):
|
|
68
69
|
return app
|
|
69
|
-
|
|
70
|
-
print(f"Error: '{app_name}' is not a FastAPI instance")
|
|
71
|
-
return None
|
|
72
|
-
else:
|
|
73
|
-
print(f"Error: No attribute '{app_name}' found in module '{module_name}'")
|
|
70
|
+
logger.error(f"'{app_name}' is not a FastAPI instance")
|
|
74
71
|
return None
|
|
72
|
+
logger.error(f"No attribute '{app_name}' found in module '{module_name}'")
|
|
73
|
+
return None
|
|
75
74
|
finally:
|
|
76
75
|
# Cleanup: if we added the path, remove it
|
|
77
76
|
if path_added and current_dir in sys.path:
|
|
78
77
|
sys.path.remove(current_dir)
|
|
79
78
|
|
|
80
79
|
except ImportError as e:
|
|
81
|
-
|
|
80
|
+
logger.error(f"Could not import module '{module_name}': {e}")
|
|
82
81
|
return None
|
|
83
82
|
except Exception as e:
|
|
84
|
-
|
|
83
|
+
logger.error(f"Error loading FastAPI app from module '{module_name}': {e}")
|
|
85
84
|
return None
|
|
86
85
|
|
|
87
86
|
|
|
@@ -110,9 +109,9 @@ def generate_visualization(
|
|
|
110
109
|
# Optionally write to file
|
|
111
110
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
112
111
|
f.write(dot_content)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
logger.info(f"DOT file generated: {output_file}")
|
|
113
|
+
logger.info("To render the graph, use: dot -Tpng router_viz.dot -o router_viz.png")
|
|
114
|
+
logger.info("Or view online: https://dreampuf.github.io/GraphvizOnline/")
|
|
116
115
|
|
|
117
116
|
|
|
118
117
|
def main():
|
|
@@ -217,41 +216,30 @@ Examples:
|
|
|
217
216
|
help="Filter by route id (format: <endpoint>_<path with _>)"
|
|
218
217
|
)
|
|
219
218
|
parser.add_argument(
|
|
220
|
-
"--
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
"--log-level",
|
|
220
|
+
dest="log_level",
|
|
221
|
+
default="INFO",
|
|
222
|
+
help="Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)"
|
|
223
223
|
)
|
|
224
224
|
|
|
225
225
|
args = parser.parse_args()
|
|
226
226
|
|
|
227
|
-
# Handle demo mode: override module_name and defaults
|
|
228
|
-
if args.demo:
|
|
229
|
-
# Force module loading path
|
|
230
|
-
args.module_name = "tests.demo"
|
|
231
|
-
# Ensure server mode on
|
|
232
|
-
args.server = True
|
|
233
|
-
# Inject default module colors if absent / merge
|
|
234
|
-
demo_defaults = ["tests.service:blue", "tests.demo:tomato"]
|
|
235
|
-
existing = set(args.module_color or [])
|
|
236
|
-
for d in demo_defaults:
|
|
237
|
-
# only add if same key not already provided
|
|
238
|
-
key = d.split(":", 1)[0]
|
|
239
|
-
if not any(mc.startswith(key + ":") for mc in existing):
|
|
240
|
-
args.module_color = (args.module_color or []) + [d]
|
|
241
|
-
|
|
242
227
|
if args.module_prefix and not args.server:
|
|
243
228
|
parser.error("--module_prefix can only be used together with --server")
|
|
244
229
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
230
|
+
if not (args.module_name or args.module):
|
|
231
|
+
parser.error("You must provide a module file, -m module name")
|
|
232
|
+
|
|
233
|
+
# Configure logging based on --log-level
|
|
234
|
+
level_name = (args.log_level or "INFO").upper()
|
|
235
|
+
logging.basicConfig(level=level_name)
|
|
248
236
|
|
|
249
237
|
# Load FastAPI app based on the input method (module_name takes precedence)
|
|
250
238
|
if args.module_name:
|
|
251
239
|
app = load_fastapi_app_from_module(args.module_name, args.app)
|
|
252
240
|
else:
|
|
253
241
|
if not os.path.exists(args.module):
|
|
254
|
-
|
|
242
|
+
logger.error(f"File '{args.module}' not found")
|
|
255
243
|
sys.exit(1)
|
|
256
244
|
app = load_fastapi_app_from_file(args.module, args.app)
|
|
257
245
|
|
|
@@ -279,15 +267,15 @@ Examples:
|
|
|
279
267
|
try:
|
|
280
268
|
import uvicorn
|
|
281
269
|
except ImportError:
|
|
282
|
-
|
|
270
|
+
logger.info("uvicorn is required to run the server. Install via 'pip install uvicorn' or 'uv add uvicorn'.")
|
|
283
271
|
sys.exit(1)
|
|
284
272
|
app_server = viz_server.create_voyager(
|
|
285
273
|
app,
|
|
286
274
|
module_color=module_color,
|
|
287
275
|
module_prefix=args.module_prefix,
|
|
288
276
|
)
|
|
289
|
-
|
|
290
|
-
uvicorn.run(app_server, host=args.host, port=args.port)
|
|
277
|
+
logger.info(f"Starting preview server at http://{args.host}:{args.port} ... (Ctrl+C to stop)")
|
|
278
|
+
uvicorn.run(app_server, host=args.host, port=args.port, log_level=level_name.lower())
|
|
291
279
|
else:
|
|
292
280
|
# Generate and write dot file locally
|
|
293
281
|
generate_visualization(
|
|
@@ -300,7 +288,7 @@ Examples:
|
|
|
300
288
|
route_name=args.route_name,
|
|
301
289
|
)
|
|
302
290
|
except Exception as e:
|
|
303
|
-
|
|
291
|
+
logger.info(f"Error generating visualization: {e}")
|
|
304
292
|
sys.exit(1)
|
|
305
293
|
|
|
306
294
|
|
fastapi_voyager/filter.py
CHANGED
|
@@ -236,7 +236,6 @@ def filter_subgraph_from_tag_to_schema_by_module_prefix(
|
|
|
236
236
|
seen_pairs: set[tuple[str, str]] = set()
|
|
237
237
|
|
|
238
238
|
for link in tag_route_links:
|
|
239
|
-
# print(link)
|
|
240
239
|
tag_id = link.source_origin
|
|
241
240
|
start_node_id = link.target_origin
|
|
242
241
|
if tag_id is None or start_node_id is None:
|
fastapi_voyager/render.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
from fastapi_voyager.type import SchemaNode, ModuleNode, Link, Tag, Route, FieldType, PK, ModuleRoute
|
|
3
3
|
from fastapi_voyager.module import build_module_schema_tree, build_module_route_tree
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
|
|
6
|
+
logger = getLogger(__name__)
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
class Renderer:
|
|
@@ -17,7 +20,10 @@ class Renderer:
|
|
|
17
20
|
self.schema = schema
|
|
18
21
|
self.show_module = show_module
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
logger.info(f'show_module: {self.show_module}')
|
|
24
|
+
logger.info(f'module_color: {self.module_color}')
|
|
25
|
+
|
|
26
|
+
def render_schema_label(self, node: SchemaNode, color: Optional[str]=None) -> str:
|
|
21
27
|
has_base_fields = any(f.from_base for f in node.fields)
|
|
22
28
|
fields = [n for n in node.fields if n.from_base is False]
|
|
23
29
|
|
|
@@ -38,7 +44,8 @@ class Renderer:
|
|
|
38
44
|
field_str = f"""<tr><td align="left" port="f{field.name}" cellpadding="8"><font> {display_xml} </font></td></tr>"""
|
|
39
45
|
fields_parts.append(field_str)
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
default_color = '#009485' if color is None else color
|
|
48
|
+
header_color = 'tomato' if node.id == self.schema else default_color
|
|
42
49
|
header = f"""<tr><td cellpadding="6" bgcolor="{header_color}" align="center" colspan="1" port="{PK}"> <font color="white"> {node.name} </font></td> </tr>"""
|
|
43
50
|
field_content = ''.join(fields_parts) if fields_parts else ''
|
|
44
51
|
return f"""<<table border="1" cellborder="0" cellpadding="0" bgcolor="white"> {header} {field_content} </table>>"""
|
|
@@ -67,43 +74,56 @@ class Renderer:
|
|
|
67
74
|
raise ValueError(f'Unknown link type: {link.type}')
|
|
68
75
|
|
|
69
76
|
def render_module_schema_content(self, nodes: list[SchemaNode]) -> str:
|
|
70
|
-
def render_node(node: SchemaNode) -> str:
|
|
77
|
+
def render_node(node: SchemaNode, color: Optional[str]=None) -> str:
|
|
71
78
|
return f'''
|
|
72
79
|
"{node.id}" [
|
|
73
|
-
label = {self.render_schema_label(node)}
|
|
80
|
+
label = {self.render_schema_label(node, color)}
|
|
74
81
|
shape = "plain"
|
|
75
82
|
margin="0.5,0.1"
|
|
76
83
|
];'''
|
|
77
|
-
def render_module_schema(mod: ModuleNode) -> str:
|
|
78
|
-
color: Optional[str] = None
|
|
79
84
|
|
|
85
|
+
def render_module_schema(mod: ModuleNode, inherit_color: Optional[str]=None, show_cluster:bool=True) -> str:
|
|
86
|
+
color: Optional[str] = inherit_color
|
|
87
|
+
|
|
88
|
+
# recursively vist module from short to long: 'a', 'a.b', 'a.b.c'
|
|
89
|
+
# color_flag: {'a', 'a.b.c'}
|
|
90
|
+
# at first 'a', match 'a' -> color, remove 'a' from color_flag
|
|
91
|
+
# at 'a.b', no match
|
|
92
|
+
# at 'a.b.c', match 'a.b.c' -> color, remove 'a.b.c' from color_flag
|
|
80
93
|
for k in module_color_flag:
|
|
81
|
-
if mod.fullname.startswith(k):
|
|
94
|
+
if mod.fullname.startswith(k):
|
|
82
95
|
module_color_flag.remove(k)
|
|
83
96
|
color = self.module_color[k]
|
|
84
97
|
break
|
|
85
98
|
|
|
86
|
-
inner_nodes = [ render_node(node) for node in mod.schema_nodes ]
|
|
99
|
+
inner_nodes = [ render_node(node, color) for node in mod.schema_nodes ]
|
|
87
100
|
inner_nodes_str = '\n'.join(inner_nodes)
|
|
88
|
-
child_str = '\n'.join(render_module_schema(m) for m in mod.modules)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
child_str = '\n'.join(render_module_schema(mod=m, inherit_color=color, show_cluster=show_cluster) for m in mod.modules)
|
|
102
|
+
|
|
103
|
+
if show_cluster:
|
|
104
|
+
return f'''
|
|
105
|
+
subgraph cluster_module_{mod.fullname.replace('.', '_')} {{
|
|
106
|
+
tooltip="{mod.fullname}"
|
|
107
|
+
color = "#666"
|
|
108
|
+
style="rounded"
|
|
109
|
+
label = " {mod.name}"
|
|
110
|
+
labeljust = "l"
|
|
111
|
+
{(f'pencolor = "{color}"' if color else 'pencolor="#ccc"')}
|
|
112
|
+
{(f'penwidth = 3' if color else 'penwidth=""')}
|
|
113
|
+
{inner_nodes_str}
|
|
114
|
+
{child_str}
|
|
115
|
+
}}'''
|
|
116
|
+
else:
|
|
117
|
+
return f'''
|
|
98
118
|
{inner_nodes_str}
|
|
99
119
|
{child_str}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
'''
|
|
121
|
+
|
|
122
|
+
# if self.show_module:
|
|
123
|
+
module_schemas = build_module_schema_tree(nodes)
|
|
124
|
+
module_color_flag = set(self.module_color.keys())
|
|
125
|
+
return '\n'.join(render_module_schema(mod=m, show_cluster=self.show_module) for m in module_schemas)
|
|
126
|
+
|
|
107
127
|
|
|
108
128
|
def render_module_route_content(self, routes: list[Route]) -> str:
|
|
109
129
|
def render_route(route: Route) -> str:
|
|
@@ -115,29 +135,32 @@ class Renderer:
|
|
|
115
135
|
shape = "record"
|
|
116
136
|
];'''
|
|
117
137
|
|
|
118
|
-
def render_module_route(mod: ModuleRoute) -> str:
|
|
138
|
+
def render_module_route(mod: ModuleRoute, show_cluster: bool=True) -> str:
|
|
119
139
|
# Inner route nodes, same style as flat route_str
|
|
120
140
|
inner_nodes = [
|
|
121
141
|
render_route(r) for r in mod.routes
|
|
122
142
|
]
|
|
123
143
|
inner_nodes_str = '\n'.join(inner_nodes)
|
|
124
|
-
child_str = '\n'.join(render_module_route(m) for m in mod.modules)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
144
|
+
child_str = '\n'.join(render_module_route(m, show_cluster=show_cluster) for m in mod.modules)
|
|
145
|
+
if show_cluster:
|
|
146
|
+
return f'''
|
|
147
|
+
subgraph cluster_route_module_{mod.fullname.replace('.', '_')} {{
|
|
148
|
+
tooltip="{mod.fullname}"
|
|
149
|
+
color = "#666"
|
|
150
|
+
style="rounded"
|
|
151
|
+
label = " {mod.name}"
|
|
152
|
+
labeljust = "l"
|
|
153
|
+
{inner_nodes_str}
|
|
154
|
+
{child_str}
|
|
155
|
+
}}'''
|
|
156
|
+
else:
|
|
157
|
+
return f'''
|
|
132
158
|
{inner_nodes_str}
|
|
133
159
|
{child_str}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return module_routes_str
|
|
139
|
-
else:
|
|
140
|
-
return '\n'.join(render_route(r) for r in routes)
|
|
160
|
+
'''
|
|
161
|
+
module_routes = build_module_route_tree(routes)
|
|
162
|
+
module_routes_str = '\n'.join(render_module_route(m, show_cluster=self.show_module) for m in module_routes)
|
|
163
|
+
return module_routes_str
|
|
141
164
|
|
|
142
165
|
|
|
143
166
|
def render_dot(self, tags: list[Tag], routes: list[Route], nodes: list[SchemaNode], links: list[Link], spline_line=False) -> str:
|
fastapi_voyager/type_helper.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
import logging
|
|
2
3
|
import os
|
|
3
4
|
from pydantic import BaseModel
|
|
4
5
|
from typing import get_origin, get_args, Union, Annotated, Any, Type, Generic, Optional
|
|
@@ -6,6 +7,8 @@ from fastapi_voyager.type import FieldInfo
|
|
|
6
7
|
from types import UnionType
|
|
7
8
|
import pydantic_resolve.constant as const
|
|
8
9
|
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
9
12
|
# Python <3.12 compatibility: TypeAliasType exists only from 3.12 (PEP 695)
|
|
10
13
|
try: # pragma: no cover - import guard
|
|
11
14
|
from typing import TypeAliasType # type: ignore
|
|
@@ -228,12 +231,11 @@ def get_source(kls):
|
|
|
228
231
|
return "failed to get source"
|
|
229
232
|
|
|
230
233
|
|
|
231
|
-
def safe_issubclass(kls,
|
|
234
|
+
def safe_issubclass(kls, target_kls):
|
|
232
235
|
try:
|
|
233
|
-
return issubclass(kls,
|
|
236
|
+
return issubclass(kls, target_kls)
|
|
234
237
|
except TypeError:
|
|
235
|
-
|
|
236
|
-
print(str(kls), 'is not a target class')
|
|
238
|
+
logger.debug(f'{kls.__module__}:{kls.__qualname__} is not subclass of {target_kls.__module__}:{target_kls.__qualname__}')
|
|
237
239
|
return False
|
|
238
240
|
|
|
239
241
|
|
fastapi_voyager/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.11.
|
|
2
|
+
__version__ = "0.11.11"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-voyager
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.11
|
|
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
|
|
@@ -52,8 +52,11 @@ uv add fastapi-voyager
|
|
|
52
52
|
voyager -m path.to.your.app.module --server
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
>
|
|
55
|
+
> [Sub-Application mounts](https://fastapi.tiangolo.com/advanced/sub-applications/) are not supported yet, but you can specify the name of the FastAPI application used with `--app`. Only a single application (default: 'app') can be selected, but in a scenario where `api` is attached through `app.mount("/api", api)`, you can select `api` like this:
|
|
56
56
|
|
|
57
|
+
```shell
|
|
58
|
+
voyager -m path.to.your.app.module --server --app api
|
|
59
|
+
```
|
|
57
60
|
|
|
58
61
|
## Mount into project
|
|
59
62
|
|
|
@@ -141,6 +144,8 @@ voyager -m tests.demo --module_color=tests.demo:red --module_color=tests.service
|
|
|
141
144
|
voyager -m tests.demo -o my_visualization.dot
|
|
142
145
|
|
|
143
146
|
voyager --version
|
|
147
|
+
|
|
148
|
+
voyager --help
|
|
144
149
|
```
|
|
145
150
|
|
|
146
151
|
The tool will generate a DOT file that you can render using Graphviz:
|
|
@@ -166,8 +171,6 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
166
171
|
- [ ] user can generate nodes/edges manually and connect to generated ones
|
|
167
172
|
- [ ] eg: add owner
|
|
168
173
|
- [ ] add extra info for schema
|
|
169
|
-
- [ ] display standard ER diagram `hard`
|
|
170
|
-
- [ ] display potential invalid links
|
|
171
174
|
- [ ] optimize static resource (allow manually config url)
|
|
172
175
|
- [ ] improve search dialog
|
|
173
176
|
- [ ] add route/tag list
|
|
@@ -178,11 +181,9 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
178
181
|
### in analysis
|
|
179
182
|
- [ ] click field to highlight links
|
|
180
183
|
- [ ] animation effect for edges
|
|
181
|
-
- [ ]
|
|
182
|
-
- [ ]
|
|
183
|
-
- [ ]
|
|
184
|
-
- [ ] set max limit for fields
|
|
185
|
-
- [ ] logging information
|
|
184
|
+
- [ ] display standard ER diagram spec. `hard but important`
|
|
185
|
+
- [ ] display potential invalid links
|
|
186
|
+
- [ ] highlight relationship belongs to ER diagram
|
|
186
187
|
|
|
187
188
|
### plan:
|
|
188
189
|
#### <0.9:
|
|
@@ -279,15 +280,28 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
279
280
|
- [x] replace issubclass with safe_issubclass to prevent exception.
|
|
280
281
|
- 0.11.10
|
|
281
282
|
- [x] fix bug during updating forward refs
|
|
283
|
+
- 0.11.11
|
|
284
|
+
- [x] replace print with logging and add `--log-level` in cli, by default info
|
|
285
|
+
- [x] fill node title color with module color
|
|
286
|
+
- [x] optimize cluster render logic
|
|
287
|
+
|
|
288
|
+
### 0.12
|
|
289
|
+
- 0.12.1
|
|
290
|
+
- [ ] sort tag / route names in left panel
|
|
291
|
+
- [ ] sort field name in nodes
|
|
292
|
+
- [ ] set max limit for fields in nodes
|
|
293
|
+
- [ ] upgrade network algorithm (optional)
|
|
294
|
+
- [ ] refactor render.py
|
|
282
295
|
|
|
283
|
-
#### 0.
|
|
284
|
-
-
|
|
285
|
-
- [ ] integration with pydantic-resolve
|
|
286
|
-
|
|
287
|
-
|
|
296
|
+
#### 0.13
|
|
297
|
+
- 0.12.0
|
|
298
|
+
- [ ] integration with pydantic-resolve
|
|
299
|
+
- [ ] show hint for resolve, post fields
|
|
300
|
+
- [ ] display loader as edges
|
|
301
|
+
- [ ] add tests
|
|
288
302
|
|
|
289
303
|
#### 0.13
|
|
290
|
-
|
|
304
|
+
placeholder
|
|
291
305
|
|
|
292
306
|
|
|
293
307
|
## About pydantic-resolve
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
fastapi_voyager/__init__.py,sha256=tZy0Nkj8kTaMgbvHy-mGxVcFGVX0Km-36dnzsAIG2uk,230
|
|
2
|
-
fastapi_voyager/cli.py,sha256=
|
|
3
|
-
fastapi_voyager/filter.py,sha256=
|
|
2
|
+
fastapi_voyager/cli.py,sha256=xK8DT-m2qP38FK2dGhLP-sHEuS29SBw6ACrnX9w85P0,10521
|
|
3
|
+
fastapi_voyager/filter.py,sha256=9Y-NepveIiCLOI-5eXk6DNK9H3dr5_h4xUbWYHkbo7M,11552
|
|
4
4
|
fastapi_voyager/module.py,sha256=Z2QHNmiLk6ZAJlm2nSmO875Q33TweSg8UxZSzIpU9zY,3499
|
|
5
|
-
fastapi_voyager/render.py,sha256=
|
|
5
|
+
fastapi_voyager/render.py,sha256=8hVsEDQi2-aP3QKN6KI3RnNz0uG-FuDr_k4D7QNsdQQ,9823
|
|
6
6
|
fastapi_voyager/server.py,sha256=G48St-leUcEwshIhTYVotxuFWXDAvzjthOCK9AKh9dI,6497
|
|
7
7
|
fastapi_voyager/type.py,sha256=VmcTB1G-LOT70EWCzi4LU_FUkSGWUIBJX15T_J5HnOo,1764
|
|
8
|
-
fastapi_voyager/type_helper.py,sha256=
|
|
9
|
-
fastapi_voyager/version.py,sha256
|
|
8
|
+
fastapi_voyager/type_helper.py,sha256=TqtYP2_54aar_iQjD0XhjJPXYhfi6icnPPrxkj0a4sk,9523
|
|
9
|
+
fastapi_voyager/version.py,sha256=PokgCRzjnyxlFdVF1p-f93rdL31U6_AVnSKLvGGed9Y,50
|
|
10
10
|
fastapi_voyager/voyager.py,sha256=nioo56oFDeZ8nwwPWDtaQbkpe4pVssFoBVHCWFhs0K4,13549
|
|
11
11
|
fastapi_voyager/web/graph-ui.js,sha256=DTedkpZNbtufexONVkJ8mOwF_-VnvxoReYHtox6IKR4,5842
|
|
12
12
|
fastapi_voyager/web/graphviz.svg.css,sha256=zDCjjpT0Idufu5YOiZI76PL70-avP3vTyzGPh9M85Do,1563
|
|
@@ -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.
|
|
30
|
-
fastapi_voyager-0.11.
|
|
31
|
-
fastapi_voyager-0.11.
|
|
32
|
-
fastapi_voyager-0.11.
|
|
33
|
-
fastapi_voyager-0.11.
|
|
29
|
+
fastapi_voyager-0.11.11.dist-info/METADATA,sha256=uufha8zMMchzxEm6ZWW5r4VUF26sKl2DQ9G14HefXgw,11451
|
|
30
|
+
fastapi_voyager-0.11.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
31
|
+
fastapi_voyager-0.11.11.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
|
|
32
|
+
fastapi_voyager-0.11.11.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
|
|
33
|
+
fastapi_voyager-0.11.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|