fastapi-voyager 0.14.1__py3-none-any.whl → 0.15.1__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/er_diagram.py +57 -109
- fastapi_voyager/render.py +433 -203
- fastapi_voyager/render_style.py +105 -0
- fastapi_voyager/server.py +1 -0
- fastapi_voyager/templates/dot/cluster.j2 +10 -0
- fastapi_voyager/templates/dot/cluster_container.j2 +9 -0
- fastapi_voyager/templates/dot/digraph.j2 +25 -0
- fastapi_voyager/templates/dot/er_diagram.j2 +29 -0
- fastapi_voyager/templates/dot/link.j2 +1 -0
- fastapi_voyager/templates/dot/route_node.j2 +5 -0
- fastapi_voyager/templates/dot/schema_node.j2 +5 -0
- fastapi_voyager/templates/dot/tag_node.j2 +5 -0
- fastapi_voyager/templates/html/colored_text.j2 +1 -0
- fastapi_voyager/templates/html/pydantic_meta.j2 +1 -0
- fastapi_voyager/templates/html/schema_field_row.j2 +1 -0
- fastapi_voyager/templates/html/schema_header.j2 +2 -0
- fastapi_voyager/templates/html/schema_table.j2 +4 -0
- fastapi_voyager/version.py +1 -1
- fastapi_voyager/web/component/demo.js +5 -5
- fastapi_voyager/web/component/render-graph.js +60 -61
- fastapi_voyager/web/component/route-code-display.js +35 -37
- fastapi_voyager/web/component/schema-code-display.js +50 -53
- fastapi_voyager/web/graph-ui.js +90 -101
- fastapi_voyager/web/graphviz.svg.css +10 -10
- fastapi_voyager/web/graphviz.svg.js +306 -316
- fastapi_voyager/web/icon/site.webmanifest +11 -1
- fastapi_voyager/web/index.html +225 -109
- fastapi_voyager/web/store.js +107 -111
- fastapi_voyager/web/vue-main.js +287 -258
- {fastapi_voyager-0.14.1.dist-info → fastapi_voyager-0.15.1.dist-info}/METADATA +18 -5
- fastapi_voyager-0.15.1.dist-info/RECORD +50 -0
- fastapi_voyager-0.14.1.dist-info/RECORD +0 -36
- {fastapi_voyager-0.14.1.dist-info → fastapi_voyager-0.15.1.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.14.1.dist-info → fastapi_voyager-0.15.1.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.14.1.dist-info → fastapi_voyager-0.15.1.dist-info}/licenses/LICENSE +0 -0
fastapi_voyager/er_diagram.py
CHANGED
|
@@ -15,138 +15,86 @@ from fastapi_voyager.type import (
|
|
|
15
15
|
ModuleNode,
|
|
16
16
|
SchemaNode,
|
|
17
17
|
)
|
|
18
|
+
from fastapi_voyager.render import Renderer
|
|
19
|
+
from fastapi_voyager.render_style import RenderConfig
|
|
18
20
|
from pydantic import BaseModel
|
|
19
21
|
from pydantic_resolve import ErDiagram, Entity, Relationship, MultipleRelationship
|
|
20
22
|
from logging import getLogger
|
|
21
|
-
from fastapi_voyager.module import build_module_schema_tree
|
|
22
23
|
|
|
23
24
|
logger = getLogger(__name__)
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
class DiagramRenderer:
|
|
27
|
+
class DiagramRenderer(Renderer):
|
|
28
|
+
"""
|
|
29
|
+
Renderer for Entity-Relationship diagrams.
|
|
30
|
+
|
|
31
|
+
Inherits from Renderer to reuse template system and styling.
|
|
32
|
+
ER diagrams have simpler structure (no tags/routes), so we only
|
|
33
|
+
need to customize the top-level DOT structure.
|
|
34
|
+
"""
|
|
35
|
+
|
|
27
36
|
def __init__(
|
|
28
37
|
self,
|
|
29
38
|
*,
|
|
30
39
|
show_fields: FieldType = 'single',
|
|
31
40
|
show_module: bool = True
|
|
32
41
|
) -> None:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
# Initialize parent Renderer with shared config
|
|
43
|
+
super().__init__(
|
|
44
|
+
show_fields=show_fields,
|
|
45
|
+
show_module=show_module,
|
|
46
|
+
config=RenderConfig() # Use unified style configuration
|
|
47
|
+
)
|
|
36
48
|
logger.info(f'show_module: {self.show_module}')
|
|
37
49
|
|
|
38
|
-
def render_schema_label(self, node: SchemaNode, color: str | None=None) -> str:
|
|
39
|
-
has_base_fields = any(f.from_base for f in node.fields)
|
|
40
|
-
fields = [n for n in node.fields if n.from_base is False]
|
|
41
|
-
|
|
42
|
-
if self.show_fields == 'all':
|
|
43
|
-
_fields = fields
|
|
44
|
-
elif self.show_fields == 'object':
|
|
45
|
-
_fields = [f for f in fields if f.is_object is True]
|
|
46
|
-
else: # 'single'
|
|
47
|
-
_fields = []
|
|
48
|
-
|
|
49
|
-
fields_parts: list[str] = []
|
|
50
|
-
if self.show_fields == 'all' and has_base_fields:
|
|
51
|
-
fields_parts.append('<tr><td align="left" cellpadding="8"><font color="#999"> Inherited Fields ... </font></td></tr>')
|
|
52
|
-
|
|
53
|
-
for field in _fields:
|
|
54
|
-
type_name = field.type_name[:25] + '..' if len(field.type_name) > 25 else field.type_name
|
|
55
|
-
display_xml = f'<s align="left">{field.name}: {type_name}</s>' if field.is_exclude else f'{field.name}: {type_name}'
|
|
56
|
-
field_str = f"""<tr><td align="left" port="f{field.name}" cellpadding="8"><font> {display_xml} </font></td></tr>"""
|
|
57
|
-
fields_parts.append(field_str)
|
|
58
|
-
|
|
59
|
-
header_color = '#009485' if color is None else color
|
|
60
|
-
header = f"""<tr><td cellpadding="6" bgcolor="{header_color}" align="center" colspan="1" port="{PK}"> <font color="white"> {node.name} </font></td> </tr>"""
|
|
61
|
-
field_content = ''.join(fields_parts) if fields_parts else ''
|
|
62
|
-
return f"""<<table border="0" cellborder="1" cellpadding="0" cellspacing="0" bgcolor="white"> {header} {field_content} </table>>"""
|
|
63
|
-
|
|
64
|
-
def _handle_schema_anchor(self, source: str) -> str:
|
|
65
|
-
if '::' in source:
|
|
66
|
-
a, b = source.split('::', 1)
|
|
67
|
-
return f'"{a}":{b}'
|
|
68
|
-
return f'"{source}"'
|
|
69
|
-
|
|
70
50
|
def render_link(self, link: Link) -> str:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
"""Override to increase link length by 40% for ER diagrams."""
|
|
52
|
+
source = self._handle_schema_anchor(link.source)
|
|
53
|
+
target = self._handle_schema_anchor(link.target)
|
|
54
|
+
|
|
55
|
+
# Build link attributes
|
|
56
|
+
if link.style is not None:
|
|
57
|
+
attrs = {'style': link.style}
|
|
58
|
+
if link.label:
|
|
59
|
+
attrs['label'] = link.label
|
|
60
|
+
# Increase minlen by 40% (3 * 1.4 = 4.2, round to 4)
|
|
61
|
+
attrs['minlen'] = 4
|
|
74
62
|
else:
|
|
75
|
-
|
|
63
|
+
attrs = self.style.get_link_attributes(link.type)
|
|
64
|
+
if link.label:
|
|
65
|
+
attrs['label'] = link.label
|
|
76
66
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
margin="0.5,0.1"
|
|
84
|
-
];'''
|
|
85
|
-
|
|
86
|
-
def render_module_schema(mod: ModuleNode, show_cluster:bool=True) -> str:
|
|
87
|
-
inner_nodes = [ render_node(node) for node in mod.schema_nodes ]
|
|
88
|
-
inner_nodes_str = '\n'.join(inner_nodes)
|
|
89
|
-
child_str = '\n'.join(render_module_schema(mod=m, show_cluster=show_cluster) for m in mod.modules)
|
|
90
|
-
|
|
91
|
-
if show_cluster:
|
|
92
|
-
return f'''
|
|
93
|
-
subgraph cluster_module_{mod.fullname.replace('.', '_')} {{
|
|
94
|
-
tooltip="{mod.fullname}"
|
|
95
|
-
color = "#666"
|
|
96
|
-
style="rounded"
|
|
97
|
-
label = " {mod.name}"
|
|
98
|
-
labeljust = "l"
|
|
99
|
-
pencolor="#ccc"
|
|
100
|
-
penwidth=""
|
|
101
|
-
{inner_nodes_str}
|
|
102
|
-
{child_str}
|
|
103
|
-
}}'''
|
|
104
|
-
else:
|
|
105
|
-
return f'''
|
|
106
|
-
{inner_nodes_str}
|
|
107
|
-
{child_str}
|
|
108
|
-
'''
|
|
109
|
-
|
|
110
|
-
# if self.show_module:
|
|
111
|
-
module_schemas = build_module_schema_tree(nodes)
|
|
112
|
-
return '\n'.join(render_module_schema(mod=m, show_cluster=self.show_module) for m in module_schemas)
|
|
67
|
+
return self.template_renderer.render_template(
|
|
68
|
+
'dot/link.j2',
|
|
69
|
+
source=source,
|
|
70
|
+
target=target,
|
|
71
|
+
attributes=self._format_link_attributes(attrs)
|
|
72
|
+
)
|
|
113
73
|
|
|
114
74
|
def render_dot(self, nodes: list[SchemaNode], links: list[Link], spline_line=False) -> str:
|
|
115
|
-
|
|
116
|
-
|
|
75
|
+
"""
|
|
76
|
+
Render ER diagram as DOT format.
|
|
117
77
|
|
|
118
|
-
|
|
119
|
-
digraph
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
fontname="Helvetica,Arial,sans-serif"
|
|
124
|
-
node [fontname="Helvetica,Arial,sans-serif"]
|
|
125
|
-
edge [
|
|
126
|
-
fontname="Helvetica,Arial,sans-serif"
|
|
127
|
-
color="gray"
|
|
128
|
-
]
|
|
129
|
-
graph [
|
|
130
|
-
rankdir = "LR"
|
|
131
|
-
];
|
|
132
|
-
node [
|
|
133
|
-
fontsize = "16"
|
|
134
|
-
];
|
|
78
|
+
Reuses parent's render_module_schema_content and render_link methods.
|
|
79
|
+
Only customizes the top-level digraph structure.
|
|
80
|
+
"""
|
|
81
|
+
# Reuse parent's module schema rendering
|
|
82
|
+
module_schemas_str = self.render_module_schema_content(nodes)
|
|
135
83
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
margin=18
|
|
139
|
-
style="dashed"
|
|
140
|
-
label=" ER Diagram"
|
|
141
|
-
labeljust="l"
|
|
142
|
-
fontsize="20"
|
|
143
|
-
{module_schemas_str}
|
|
144
|
-
}}
|
|
84
|
+
# Reuse parent's link rendering
|
|
85
|
+
link_str = '\n'.join(self.render_link(link) for link in links)
|
|
145
86
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
87
|
+
# Render using ER diagram template
|
|
88
|
+
return self.template_renderer.render_template(
|
|
89
|
+
'dot/er_diagram.j2',
|
|
90
|
+
pad=self.style.pad,
|
|
91
|
+
nodesep=self.style.nodesep,
|
|
92
|
+
font=self.style.font,
|
|
93
|
+
node_fontsize=self.style.node_fontsize,
|
|
94
|
+
spline='line' if spline_line else None,
|
|
95
|
+
er_cluster=module_schemas_str,
|
|
96
|
+
links=link_str
|
|
97
|
+
)
|
|
150
98
|
|
|
151
99
|
|
|
152
100
|
class VoyagerErDiagram:
|