fastapi-voyager 0.13.0__tar.gz → 0.13.1__tar.gz

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 (61) hide show
  1. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/PKG-INFO +1 -1
  2. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/docs/changelog.md +3 -1
  3. fastapi_voyager-0.13.1/src/fastapi_voyager/er_diagram.py +273 -0
  4. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/type.py +1 -0
  5. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/version.py +1 -1
  6. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/service/schema/schema.py +5 -2
  7. fastapi_voyager-0.13.0/src/fastapi_voyager/er_diagram.py +0 -119
  8. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  9. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  10. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/.github/workflows/publish.yml +0 -0
  11. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/.gitignore +0 -0
  12. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/.python-version +0 -0
  13. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/CONTRIBUTING.md +0 -0
  14. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/LICENSE +0 -0
  15. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/README.md +0 -0
  16. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/docs/idea.md +0 -0
  17. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/pyproject.toml +0 -0
  18. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/release.md +0 -0
  19. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/__init__.py +0 -0
  20. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/cli.py +0 -0
  21. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/filter.py +0 -0
  22. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/module.py +0 -0
  23. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/render.py +0 -0
  24. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/server.py +0 -0
  25. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/type_helper.py +0 -0
  26. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/voyager.py +0 -0
  27. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/component/demo.js +0 -0
  28. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/component/render-graph.js +0 -0
  29. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/component/route-code-display.js +0 -0
  30. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/component/schema-code-display.js +0 -0
  31. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/graph-ui.js +0 -0
  32. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
  33. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
  34. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
  35. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
  36. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
  37. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
  38. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
  39. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
  40. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
  41. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/index.html +0 -0
  42. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/quasar.min.css +0 -0
  43. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/quasar.min.js +0 -0
  44. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/store.js +0 -0
  45. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/src/fastapi_voyager/web/vue-main.js +0 -0
  46. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/__init__.py +0 -0
  47. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/demo.py +0 -0
  48. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/demo_anno.py +0 -0
  49. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/programatic.py +0 -0
  50. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/service/__init__.py +0 -0
  51. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/service/schema/__init__.py +0 -0
  52. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/service/schema/base_entity.py +0 -0
  53. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/service/schema/extra.py +0 -0
  54. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/test_analysis.py +0 -0
  55. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/test_filter.py +0 -0
  56. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/test_generic.py +0 -0
  57. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/test_import.py +0 -0
  58. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/test_module.py +0 -0
  59. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/tests/test_type_helper.py +0 -0
  60. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/uv.lock +0 -0
  61. {fastapi_voyager-0.13.0 → fastapi_voyager-0.13.1}/voyager.jpg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-voyager
3
- Version: 0.13.0
3
+ Version: 0.13.1
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
@@ -139,11 +139,13 @@
139
139
  - 0.13.0
140
140
  - [x] if er diagram is provided, show it first.
141
141
  - 0.13.1
142
+ - [x] show more details in er diagram
143
+ - 0.13.2
142
144
  - [ ] integration with pydantic-resolve
143
145
  - [ ] show hint for resolve, post fields
144
146
  - [ ] display loader as edges
145
147
  - [ ] add tests
146
- - 0.13.2
148
+ - 0.13.3
147
149
  - [ ] refactor vue-main.js, move methods to store
148
150
  - [ ] refactor render.py
149
151
 
@@ -0,0 +1,273 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi_voyager.type_helper import (
4
+ update_forward_refs,
5
+ full_class_name,
6
+ get_core_types,
7
+ get_type_name
8
+ )
9
+ from fastapi_voyager.type import (
10
+ FieldInfo,
11
+ PK,
12
+ FieldType,
13
+ LinkType,
14
+ Link,
15
+ ModuleNode,
16
+ SchemaNode,
17
+ )
18
+ from pydantic import BaseModel
19
+ from pydantic_resolve import ErDiagram, Entity, Relationship, MultipleRelationship
20
+ from logging import getLogger
21
+ from fastapi_voyager.module import build_module_schema_tree
22
+
23
+ logger = getLogger(__name__)
24
+
25
+
26
+ class DiagramRenderer:
27
+ def __init__(
28
+ self,
29
+ *,
30
+ show_fields: FieldType = 'single',
31
+ show_module: bool = True
32
+ ) -> None:
33
+ self.show_fields = show_fields if show_fields in ('single', 'object', 'all') else 'single'
34
+ self.show_module = show_module
35
+
36
+ logger.info(f'show_module: {self.show_module}')
37
+
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
+ 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 = "solid", label = "{link.label}", minlen=3];"""
74
+ else:
75
+ raise ValueError(f'Unknown link type: {link.type}')
76
+
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)
113
+
114
+ 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)
117
+
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
+ ];
135
+
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
+ }}
145
+
146
+ {link_str}
147
+ }}
148
+ '''
149
+ return dot_str
150
+
151
+
152
+ class VoyagerErDiagram:
153
+ def __init__(self,
154
+ er_diagram: ErDiagram,
155
+ show_fields: FieldType = 'single',
156
+ show_module: bool = False):
157
+ self.er_diagram = er_diagram
158
+ self.nodes: list[SchemaNode] = []
159
+ self.node_set: dict[str, SchemaNode] = {}
160
+
161
+ self.links: list[Link] = []
162
+ self.link_set: set[tuple[str, str]] = set()
163
+
164
+ self.fk_set: dict[str, set[str]] = {}
165
+
166
+ self.show_field = show_fields
167
+ self.show_module = show_module
168
+
169
+ def generate_node_head(self, link_name: str):
170
+ return f'{link_name}::{PK}'
171
+
172
+ def analysis_entity(self, entity: Entity):
173
+ schema = entity.kls
174
+ update_forward_refs(schema)
175
+ self.add_to_node_set(schema, fk_set=self.fk_set.get(full_class_name(schema)))
176
+
177
+ for relationship in entity.relationships:
178
+ annos = get_core_types(relationship.target_kls)
179
+ for anno in annos:
180
+ self.add_to_node_set(anno, fk_set=self.fk_set.get(full_class_name(anno)))
181
+ source_name = f'{full_class_name(schema)}::f{relationship.field}'
182
+ if isinstance(relationship, Relationship):
183
+ self.add_to_link_set(
184
+ source=source_name,
185
+ source_origin=full_class_name(schema),
186
+ target=self.generate_node_head(full_class_name(anno)),
187
+ target_origin=full_class_name(anno),
188
+ type='schema',
189
+ label=get_type_name(relationship.target_kls))
190
+
191
+ elif isinstance(relationship, MultipleRelationship):
192
+ for link in relationship.links:
193
+ self.add_to_link_set(
194
+ source=source_name,
195
+ source_origin=full_class_name(schema),
196
+ target=self.generate_node_head(full_class_name(anno)),
197
+ target_origin=full_class_name(anno),
198
+ type='schema',
199
+ biz=link.biz,
200
+ label=f'{get_type_name(relationship.target_kls)} / {link.biz} '
201
+ )
202
+
203
+ def add_to_node_set(self, schema, fk_set: set[str] | None = None) -> str:
204
+ """
205
+ 1. calc full_path, add to node_set
206
+ 2. if duplicated, do nothing, else insert
207
+ 2. return the full_path
208
+ """
209
+ full_name = full_class_name(schema)
210
+
211
+ if full_name not in self.node_set:
212
+ # skip meta info for normal queries
213
+ self.node_set[full_name] = SchemaNode(
214
+ id=full_name,
215
+ module=schema.__module__,
216
+ name=schema.__name__,
217
+ fields=get_fields(schema, fk_set)
218
+ )
219
+ return full_name
220
+
221
+ def add_to_link_set(
222
+ self,
223
+ source: str,
224
+ source_origin: str,
225
+ target: str,
226
+ target_origin: str,
227
+ type: LinkType,
228
+ label: str,
229
+ biz: str | None = None
230
+ ) -> bool:
231
+ """
232
+ 1. add link to link_set
233
+ 2. if duplicated, do nothing, else insert
234
+ """
235
+ pair = (source, target, biz)
236
+ if result := pair not in self.link_set:
237
+ self.link_set.add(pair)
238
+ self.links.append(Link(
239
+ source=source,
240
+ source_origin=source_origin,
241
+ target=target,
242
+ target_origin=target_origin,
243
+ type=type,
244
+ label=label
245
+ ))
246
+ return result
247
+
248
+
249
+ def render_dot(self):
250
+ self.fk_set = {
251
+ full_class_name(entity.kls): set([rel.field for rel in entity.relationships])
252
+ for entity in self.er_diagram.configs
253
+ }
254
+
255
+ for entity in self.er_diagram.configs:
256
+ self.analysis_entity(entity)
257
+ renderer = DiagramRenderer(show_fields=self.show_field, show_module=self.show_module)
258
+ return renderer.render_dot(list(self.node_set.values()), self.links)
259
+
260
+
261
+ def get_fields(schema: type[BaseModel], fk_set: set[str] | None = None) -> list[FieldInfo]:
262
+
263
+ fields: list[FieldInfo] = []
264
+ for k, v in schema.model_fields.items():
265
+ anno = v.annotation
266
+ fields.append(FieldInfo(
267
+ is_object=k in fk_set if fk_set is not None else False,
268
+ name=k,
269
+ from_base=False,
270
+ type_name=get_type_name(anno),
271
+ is_exclude=bool(v.exclude)
272
+ ))
273
+ return fields
@@ -67,6 +67,7 @@ class Link:
67
67
  source_origin: str
68
68
  target_origin: str
69
69
  type: LinkType
70
+ label: str | None = None
70
71
 
71
72
  FieldType = Literal['single', 'object', 'all']
72
73
  PK = "PK"
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.13.0"
2
+ __version__ = "0.13.1"
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
  from typing import Literal
3
3
  from pydantic import BaseModel
4
- from pydantic_resolve import Relationship
4
+ from pydantic_resolve import Relationship, MultipleRelationship, Link
5
5
  from .base_entity import BaseEntity
6
6
 
7
7
 
@@ -34,7 +34,10 @@ class Story(BaseModel, BaseEntity):
34
34
 
35
35
  class Sprint(BaseModel, BaseEntity):
36
36
  __pydantic_resolve_relationships__ = [
37
- Relationship(field='id', target_kls=list[Story])
37
+ MultipleRelationship(field='id', target_kls=list[Story], links=[
38
+ Link(biz='all'),
39
+ Link(biz='done'),
40
+ ])
38
41
  ]
39
42
  id: int
40
43
  name: str
@@ -1,119 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from fastapi_voyager.type import PK, FieldType, Link, LinkType, SchemaNode
4
- from fastapi_voyager.type_helper import (
5
- update_forward_refs,
6
- full_class_name,
7
- get_core_types,
8
- get_type_name
9
- )
10
- from fastapi_voyager.render import Renderer
11
- from fastapi_voyager.type import FieldInfo
12
- from pydantic import BaseModel
13
- from pydantic_resolve import ErDiagram, Entity
14
-
15
- class VoyagerErDiagram:
16
- def __init__(self,
17
- er_diagram: ErDiagram,
18
- show_fields: FieldType = 'single',
19
- show_module: bool = False):
20
- self.er_diagram = er_diagram
21
- self.nodes: list[SchemaNode] = []
22
- self.node_set: dict[str, SchemaNode] = {}
23
-
24
- self.links: list[Link] = []
25
- self.link_set: set[tuple[str, str]] = set()
26
-
27
- self.fk_set: dict[str, set[str]] = {}
28
-
29
- self.show_field = show_fields
30
- self.show_module = show_module
31
-
32
- def generate_node_head(self, link_name: str):
33
- return f'{link_name}::{PK}'
34
-
35
- def analysis_entity(self, entity: Entity):
36
- schema = entity.kls
37
- update_forward_refs(schema)
38
- self.add_to_node_set(schema, fk_set=self.fk_set.get(full_class_name(schema)))
39
-
40
- for relationship in entity.relationships:
41
- annos = get_core_types(relationship.target_kls)
42
- for anno in annos:
43
- self.add_to_node_set(anno, fk_set=self.fk_set.get(full_class_name(anno)))
44
- source_name = f'{full_class_name(schema)}::f{relationship.field}'
45
- self.add_to_link_set(
46
- source=source_name,
47
- source_origin=full_class_name(schema),
48
- target=self.generate_node_head(full_class_name(anno)),
49
- target_origin=full_class_name(anno),
50
- type='schema')
51
-
52
- def add_to_node_set(self, schema, fk_set: set[str] | None = None) -> str:
53
- """
54
- 1. calc full_path, add to node_set
55
- 2. if duplicated, do nothing, else insert
56
- 2. return the full_path
57
- """
58
- full_name = full_class_name(schema)
59
-
60
- if full_name not in self.node_set:
61
- # skip meta info for normal queries
62
- self.node_set[full_name] = SchemaNode(
63
- id=full_name,
64
- module=schema.__module__,
65
- name=schema.__name__,
66
- fields=get_fields(schema, fk_set)
67
- )
68
- return full_name
69
-
70
- def add_to_link_set(
71
- self,
72
- source: str,
73
- source_origin: str,
74
- target: str,
75
- target_origin: str,
76
- type: LinkType
77
- ) -> bool:
78
- """
79
- 1. add link to link_set
80
- 2. if duplicated, do nothing, else insert
81
- """
82
- pair = (source, target)
83
- if result := pair not in self.link_set:
84
- self.link_set.add(pair)
85
- self.links.append(Link(
86
- source=source,
87
- source_origin=source_origin,
88
- target=target,
89
- target_origin=target_origin,
90
- type=type
91
- ))
92
- return result
93
-
94
-
95
- def render_dot(self):
96
- self.fk_set = {
97
- full_class_name(entity.kls): set([rel.field for rel in entity.relationships])
98
- for entity in self.er_diagram.configs
99
- }
100
-
101
- for entity in self.er_diagram.configs:
102
- self.analysis_entity(entity)
103
- renderer = Renderer(show_fields=self.show_field, show_module=self.show_module)
104
- return renderer.render_dot([], [], list(self.node_set.values()), self.links)
105
-
106
-
107
- def get_fields(schema: type[BaseModel], fk_set: set[str] | None = None) -> list[FieldInfo]:
108
-
109
- fields: list[FieldInfo] = []
110
- for k, v in schema.model_fields.items():
111
- anno = v.annotation
112
- fields.append(FieldInfo(
113
- is_object=k in fk_set if fk_set is not None else False,
114
- name=k,
115
- from_base=False,
116
- type_name=get_type_name(anno),
117
- is_exclude=bool(v.exclude)
118
- ))
119
- return fields