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.
Files changed (35) hide show
  1. fastapi_voyager/er_diagram.py +57 -109
  2. fastapi_voyager/render.py +433 -203
  3. fastapi_voyager/render_style.py +105 -0
  4. fastapi_voyager/server.py +1 -0
  5. fastapi_voyager/templates/dot/cluster.j2 +10 -0
  6. fastapi_voyager/templates/dot/cluster_container.j2 +9 -0
  7. fastapi_voyager/templates/dot/digraph.j2 +25 -0
  8. fastapi_voyager/templates/dot/er_diagram.j2 +29 -0
  9. fastapi_voyager/templates/dot/link.j2 +1 -0
  10. fastapi_voyager/templates/dot/route_node.j2 +5 -0
  11. fastapi_voyager/templates/dot/schema_node.j2 +5 -0
  12. fastapi_voyager/templates/dot/tag_node.j2 +5 -0
  13. fastapi_voyager/templates/html/colored_text.j2 +1 -0
  14. fastapi_voyager/templates/html/pydantic_meta.j2 +1 -0
  15. fastapi_voyager/templates/html/schema_field_row.j2 +1 -0
  16. fastapi_voyager/templates/html/schema_header.j2 +2 -0
  17. fastapi_voyager/templates/html/schema_table.j2 +4 -0
  18. fastapi_voyager/version.py +1 -1
  19. fastapi_voyager/web/component/demo.js +5 -5
  20. fastapi_voyager/web/component/render-graph.js +60 -61
  21. fastapi_voyager/web/component/route-code-display.js +35 -37
  22. fastapi_voyager/web/component/schema-code-display.js +50 -53
  23. fastapi_voyager/web/graph-ui.js +90 -101
  24. fastapi_voyager/web/graphviz.svg.css +10 -10
  25. fastapi_voyager/web/graphviz.svg.js +306 -316
  26. fastapi_voyager/web/icon/site.webmanifest +11 -1
  27. fastapi_voyager/web/index.html +225 -109
  28. fastapi_voyager/web/store.js +107 -111
  29. fastapi_voyager/web/vue-main.js +287 -258
  30. {fastapi_voyager-0.14.1.dist-info → fastapi_voyager-0.15.1.dist-info}/METADATA +18 -5
  31. fastapi_voyager-0.15.1.dist-info/RECORD +50 -0
  32. fastapi_voyager-0.14.1.dist-info/RECORD +0 -36
  33. {fastapi_voyager-0.14.1.dist-info → fastapi_voyager-0.15.1.dist-info}/WHEEL +0 -0
  34. {fastapi_voyager-0.14.1.dist-info → fastapi_voyager-0.15.1.dist-info}/entry_points.txt +0 -0
  35. {fastapi_voyager-0.14.1.dist-info → fastapi_voyager-0.15.1.dist-info}/licenses/LICENSE +0 -0
@@ -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
- self.show_fields = show_fields if show_fields in ('single', 'object', 'all') else 'single'
34
- self.show_module = show_module
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
- h = self._handle_schema_anchor
72
- if link.type == 'schema':
73
- return f"""{h(link.source)}:e -> {h(link.target)}:w [style = "{link.style}", label = "{link.label}", minlen=3];"""
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
- raise ValueError(f'Unknown link type: {link.type}')
63
+ attrs = self.style.get_link_attributes(link.type)
64
+ if link.label:
65
+ attrs['label'] = link.label
76
66
 
77
- def render_module_schema_content(self, nodes: list[SchemaNode]) -> str:
78
- def render_node(node: SchemaNode, color: str | None=None) -> str:
79
- return f'''
80
- "{node.id}" [
81
- label = {self.render_schema_label(node, color)}
82
- shape = "plain"
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
- module_schemas_str = self.render_module_schema_content(nodes)
116
- link_str = '\n'.join(self.render_link(link) for link in links)
75
+ """
76
+ Render ER diagram as DOT format.
117
77
 
118
- dot_str = f'''
119
- digraph world {{
120
- pad="0.5"
121
- nodesep=0.8
122
- {'splines=line' if spline_line else ''}
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
- subgraph cluster_schema {{
137
- color = "#aaa"
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
- {link_str}
147
- }}
148
- '''
149
- return dot_str
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: