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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Style constants and configuration for rendering DOT graphs and HTML tables.
|
|
3
|
+
"""
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ColorScheme:
|
|
10
|
+
"""Color scheme for graph visualization."""
|
|
11
|
+
|
|
12
|
+
# Node colors
|
|
13
|
+
primary: str = '#009485'
|
|
14
|
+
highlight: str = 'tomato'
|
|
15
|
+
|
|
16
|
+
# Pydantic-resolve metadata colors
|
|
17
|
+
resolve: str = '#47a80f'
|
|
18
|
+
post: str = '#427fa4'
|
|
19
|
+
expose_as: str = '#895cb9'
|
|
20
|
+
send_to: str = '#ca6d6d'
|
|
21
|
+
collector: str = '#777'
|
|
22
|
+
|
|
23
|
+
# Link colors
|
|
24
|
+
inherit: str = 'purple'
|
|
25
|
+
subset: str = 'orange'
|
|
26
|
+
|
|
27
|
+
# Border colors
|
|
28
|
+
border: str = '#666'
|
|
29
|
+
cluster_border: str = '#ccc'
|
|
30
|
+
|
|
31
|
+
# Text colors
|
|
32
|
+
text_gray: str = '#999'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class GraphvizStyle:
|
|
37
|
+
"""Graphviz DOT style configuration."""
|
|
38
|
+
|
|
39
|
+
# Font settings
|
|
40
|
+
font: str = 'Helvetica,Arial,sans-serif'
|
|
41
|
+
node_fontsize: str = '16'
|
|
42
|
+
cluster_fontsize: str = '20'
|
|
43
|
+
|
|
44
|
+
# Layout settings
|
|
45
|
+
nodesep: str = '0.8'
|
|
46
|
+
pad: str = '0.5'
|
|
47
|
+
node_margin: str = '0.5,0.1'
|
|
48
|
+
cluster_margin: str = '18'
|
|
49
|
+
|
|
50
|
+
# Link styles configuration
|
|
51
|
+
LINK_STYLES: dict[str, dict] = field(default_factory=lambda: {
|
|
52
|
+
'tag_route': {
|
|
53
|
+
'style': 'solid',
|
|
54
|
+
'minlen': 3,
|
|
55
|
+
},
|
|
56
|
+
'route_to_schema': {
|
|
57
|
+
'style': 'solid',
|
|
58
|
+
'dir': 'back',
|
|
59
|
+
'arrowtail': 'odot',
|
|
60
|
+
'minlen': 3,
|
|
61
|
+
},
|
|
62
|
+
'schema': {
|
|
63
|
+
'style': 'solid',
|
|
64
|
+
'label': '',
|
|
65
|
+
'dir': 'back',
|
|
66
|
+
'minlen': 3,
|
|
67
|
+
'arrowtail': 'odot',
|
|
68
|
+
},
|
|
69
|
+
'parent': {
|
|
70
|
+
'style': 'solid,dashed',
|
|
71
|
+
'dir': 'back',
|
|
72
|
+
'minlen': 3,
|
|
73
|
+
'taillabel': '< inherit >',
|
|
74
|
+
'color': 'purple',
|
|
75
|
+
'tailport': 'n',
|
|
76
|
+
},
|
|
77
|
+
'subset': {
|
|
78
|
+
'style': 'solid,dashed',
|
|
79
|
+
'dir': 'back',
|
|
80
|
+
'minlen': 3,
|
|
81
|
+
'taillabel': '< subset >',
|
|
82
|
+
'color': 'orange',
|
|
83
|
+
'tailport': 'n',
|
|
84
|
+
},
|
|
85
|
+
'tag_to_schema': {
|
|
86
|
+
'style': 'solid',
|
|
87
|
+
'minlen': 3,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
def get_link_attributes(self, link_type: str) -> dict:
|
|
92
|
+
"""Get link style attributes for a given link type."""
|
|
93
|
+
return self.LINK_STYLES.get(link_type, {})
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class RenderConfig:
|
|
98
|
+
"""Complete rendering configuration."""
|
|
99
|
+
|
|
100
|
+
colors: ColorScheme = field(default_factory=ColorScheme)
|
|
101
|
+
style: GraphvizStyle = field(default_factory=GraphvizStyle)
|
|
102
|
+
|
|
103
|
+
# Field display settings
|
|
104
|
+
max_type_length: int = 25
|
|
105
|
+
type_suffix: str = '..'
|
fastapi_voyager/server.py
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
subgraph cluster_{{ cluster_id }} {
|
|
2
|
+
tooltip="{{ tooltip }}"
|
|
3
|
+
color = "{{ border_color }}"
|
|
4
|
+
style="rounded"
|
|
5
|
+
label = " {{ label }}"
|
|
6
|
+
labeljust = "l"
|
|
7
|
+
{% if pen_color %}pencolor = "{{ pen_color }}"{% endif %}
|
|
8
|
+
{% if pen_width %}penwidth = {{ pen_width }}{% endif %}
|
|
9
|
+
{{ content }}
|
|
10
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
digraph world {
|
|
2
|
+
pad="{{ pad }}"
|
|
3
|
+
nodesep={{ nodesep }}
|
|
4
|
+
{% if spline %}splines={{ spline }}{% endif %}
|
|
5
|
+
fontname="{{ font }}"
|
|
6
|
+
node [fontname="{{ font }}"]
|
|
7
|
+
edge [
|
|
8
|
+
fontname="{{ font }}"
|
|
9
|
+
color="gray"
|
|
10
|
+
]
|
|
11
|
+
graph [
|
|
12
|
+
rankdir = "LR"
|
|
13
|
+
];
|
|
14
|
+
node [
|
|
15
|
+
fontsize = {{ node_fontsize }}
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
{{ tags_cluster }}
|
|
19
|
+
|
|
20
|
+
{{ routes_cluster }}
|
|
21
|
+
|
|
22
|
+
{{ schemas_cluster }}
|
|
23
|
+
|
|
24
|
+
{{ links }}
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
digraph world {
|
|
2
|
+
pad="{{ pad }}"
|
|
3
|
+
nodesep={{ nodesep }}
|
|
4
|
+
{% if spline %}splines={{ spline }}{% endif %}
|
|
5
|
+
fontname="{{ font }}"
|
|
6
|
+
node [fontname="{{ font }}"]
|
|
7
|
+
edge [
|
|
8
|
+
fontname="{{ font }}"
|
|
9
|
+
color="gray"
|
|
10
|
+
]
|
|
11
|
+
graph [
|
|
12
|
+
rankdir = "LR"
|
|
13
|
+
];
|
|
14
|
+
node [
|
|
15
|
+
fontsize = {{ node_fontsize }}
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
subgraph cluster_schema {
|
|
19
|
+
color = "#aaa"
|
|
20
|
+
margin=18
|
|
21
|
+
style="dashed"
|
|
22
|
+
label=" ER Diagram"
|
|
23
|
+
labeljust="l"
|
|
24
|
+
fontsize="20"
|
|
25
|
+
{{ er_cluster }}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
{{ links }}
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{{ source }}:e -> {{ target }}:w [{{ attributes }}];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<font color="{{ color }}">{% if strikethrough %}<s>{{ text }}</s>{% else %}{{ text }}{% endif %}</font>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{% if meta_parts %}<br align="left"/><br align="left"/>{{ meta_parts | join('<br align="left"/>') }}<br align="left"/>{% endif %}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<tr><td align="{{ align }}" {% if port %}port="f{{ port }}"{% endif %} cellpadding="8">{{ content }}</td></tr>
|
fastapi_voyager/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.15.1"
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
const { defineComponent, computed } = window.Vue
|
|
1
|
+
const { defineComponent, computed } = window.Vue
|
|
2
2
|
|
|
3
|
-
import { store } from
|
|
3
|
+
import { store } from "../store.js"
|
|
4
4
|
|
|
5
5
|
export default defineComponent({
|
|
6
6
|
name: "Demo",
|
|
7
7
|
emits: ["close"],
|
|
8
8
|
setup() {
|
|
9
|
-
return { store }
|
|
9
|
+
return { store }
|
|
10
10
|
},
|
|
11
11
|
template: `
|
|
12
12
|
<div>
|
|
13
13
|
<p>Count: {{ store.state.item.count }}</p>
|
|
14
14
|
<button @click="store.mutations.increment()">Add</button>
|
|
15
15
|
</div>
|
|
16
|
-
|
|
17
|
-
})
|
|
16
|
+
`,
|
|
17
|
+
})
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
import { GraphUI } from "../graph-ui.js"
|
|
2
|
-
const { defineComponent, ref, onMounted, nextTick } = window.Vue
|
|
1
|
+
import { GraphUI } from "../graph-ui.js"
|
|
2
|
+
const { defineComponent, ref, onMounted, nextTick } = window.Vue
|
|
3
3
|
|
|
4
4
|
export default defineComponent({
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
name: "RenderGraph",
|
|
6
|
+
props: {
|
|
7
|
+
coreData: { type: [Object, Array], required: false, default: null },
|
|
8
|
+
},
|
|
9
|
+
emits: ["close"],
|
|
10
|
+
setup(props, { emit }) {
|
|
11
|
+
const containerId = `graph-render-${Math.random().toString(36).slice(2, 9)}`
|
|
12
|
+
const hasRendered = ref(false)
|
|
13
|
+
const loading = ref(false)
|
|
14
|
+
let graphInstance = null
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
async function ensureGraph() {
|
|
17
|
+
await nextTick()
|
|
18
|
+
if (!graphInstance) {
|
|
19
|
+
graphInstance = new GraphUI(`#${containerId}`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
async function renderFromDot(dotText) {
|
|
24
|
+
if (!dotText) return
|
|
25
|
+
await ensureGraph()
|
|
26
|
+
await graphInstance.render(dotText)
|
|
27
|
+
hasRendered.value = true
|
|
28
|
+
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
async function renderFromCoreData() {
|
|
31
|
+
if (!props.coreData) return
|
|
32
|
+
loading.value = true
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch("dot-render-core-data", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json" },
|
|
37
|
+
body: JSON.stringify(props.coreData),
|
|
38
|
+
})
|
|
39
|
+
const dotText = await res.text()
|
|
40
|
+
await renderFromDot(dotText)
|
|
41
|
+
if (window.Quasar?.Notify) {
|
|
42
|
+
window.Quasar.Notify.create({ type: "positive", message: "Rendered" })
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error("Render from core data failed", e)
|
|
46
|
+
if (window.Quasar?.Notify) {
|
|
47
|
+
window.Quasar.Notify.create({ type: "negative", message: "Render failed" })
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
loading.value = false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
async function reload() {
|
|
55
|
+
await renderFromCoreData()
|
|
56
|
+
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
onMounted(async () => {
|
|
59
|
+
await reload()
|
|
60
|
+
})
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
function close() {
|
|
63
|
+
emit("close")
|
|
64
|
+
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
return { containerId, close, hasRendered, reload, loading }
|
|
67
|
+
},
|
|
68
|
+
template: `
|
|
69
69
|
<div style="height:100%; position:relative; background:#fff;">
|
|
70
70
|
<q-btn
|
|
71
71
|
flat dense round icon="close"
|
|
@@ -83,5 +83,4 @@ export default defineComponent({
|
|
|
83
83
|
<div :id="containerId" style="width:100%; height:100%; overflow:auto; background:#fafafa"></div>
|
|
84
84
|
</div>
|
|
85
85
|
`,
|
|
86
|
-
})
|
|
87
|
-
|
|
86
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { defineComponent, ref, watch, onMounted } = window.Vue
|
|
1
|
+
const { defineComponent, ref, watch, onMounted } = window.Vue
|
|
2
2
|
|
|
3
3
|
// Component: RouteCodeDisplay
|
|
4
4
|
// Props:
|
|
@@ -10,45 +10,43 @@ export default defineComponent({
|
|
|
10
10
|
},
|
|
11
11
|
emits: ["close"],
|
|
12
12
|
setup(props, { emit }) {
|
|
13
|
-
const loading = ref(false)
|
|
14
|
-
const code = ref("")
|
|
15
|
-
const error = ref("")
|
|
16
|
-
const link = ref("")
|
|
13
|
+
const loading = ref(false)
|
|
14
|
+
const code = ref("")
|
|
15
|
+
const error = ref("")
|
|
16
|
+
const link = ref("")
|
|
17
17
|
|
|
18
18
|
function close() {
|
|
19
|
-
emit("close")
|
|
19
|
+
emit("close")
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function highlightLater() {
|
|
23
23
|
requestAnimationFrame(() => {
|
|
24
24
|
try {
|
|
25
25
|
if (window.hljs) {
|
|
26
|
-
const block = document.querySelector(
|
|
27
|
-
".frv-route-code-display pre code.language-python"
|
|
28
|
-
);
|
|
26
|
+
const block = document.querySelector(".frv-route-code-display pre code.language-python")
|
|
29
27
|
if (block) {
|
|
30
|
-
window.hljs.highlightElement(block)
|
|
28
|
+
window.hljs.highlightElement(block)
|
|
31
29
|
}
|
|
32
30
|
}
|
|
33
31
|
} catch (e) {
|
|
34
|
-
console.warn("highlight failed", e)
|
|
32
|
+
console.warn("highlight failed", e)
|
|
35
33
|
}
|
|
36
|
-
})
|
|
34
|
+
})
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
async function load() {
|
|
40
38
|
if (!props.routeId) {
|
|
41
|
-
code.value = ""
|
|
42
|
-
return
|
|
39
|
+
code.value = ""
|
|
40
|
+
return
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
loading.value = true
|
|
46
|
-
error.value = null
|
|
47
|
-
code.value = ""
|
|
48
|
-
link.value = ""
|
|
43
|
+
loading.value = true
|
|
44
|
+
error.value = null
|
|
45
|
+
code.value = ""
|
|
46
|
+
link.value = ""
|
|
49
47
|
|
|
50
48
|
// try to fetch from server: POST /source with { schema_name: routeId }
|
|
51
|
-
const payload = { schema_name: props.routeId }
|
|
49
|
+
const payload = { schema_name: props.routeId }
|
|
52
50
|
try {
|
|
53
51
|
const resp = await fetch(`source`, {
|
|
54
52
|
method: "POST",
|
|
@@ -57,18 +55,18 @@ export default defineComponent({
|
|
|
57
55
|
"Content-Type": "application/json",
|
|
58
56
|
},
|
|
59
57
|
body: JSON.stringify(payload),
|
|
60
|
-
})
|
|
58
|
+
})
|
|
61
59
|
|
|
62
|
-
const data = await resp.json().catch(() => ({}))
|
|
60
|
+
const data = await resp.json().catch(() => ({}))
|
|
63
61
|
if (resp.ok) {
|
|
64
|
-
code.value = data.source_code || "// no source code available"
|
|
62
|
+
code.value = data.source_code || "// no source code available"
|
|
65
63
|
} else {
|
|
66
|
-
error.value = (data && data.error) || "Failed to load source"
|
|
64
|
+
error.value = (data && data.error) || "Failed to load source"
|
|
67
65
|
}
|
|
68
66
|
} catch (e) {
|
|
69
|
-
error.value = e && e.message ? e.message : "Failed to load source"
|
|
67
|
+
error.value = e && e.message ? e.message : "Failed to load source"
|
|
70
68
|
} finally {
|
|
71
|
-
loading.value = false
|
|
69
|
+
loading.value = false
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
try {
|
|
@@ -79,36 +77,36 @@ export default defineComponent({
|
|
|
79
77
|
"Content-Type": "application/json",
|
|
80
78
|
},
|
|
81
79
|
body: JSON.stringify(payload),
|
|
82
|
-
})
|
|
80
|
+
})
|
|
83
81
|
|
|
84
|
-
const data = await resp.json().catch(() => ({}))
|
|
82
|
+
const data = await resp.json().catch(() => ({}))
|
|
85
83
|
if (resp.ok) {
|
|
86
|
-
link.value = data.link || "// no source code available"
|
|
84
|
+
link.value = data.link || "// no source code available"
|
|
87
85
|
} else {
|
|
88
|
-
error.value += (data && data.error) || "Failed to load vscode link"
|
|
86
|
+
error.value += (data && data.error) || "Failed to load vscode link"
|
|
89
87
|
}
|
|
90
88
|
} catch (e) {
|
|
91
89
|
} finally {
|
|
92
|
-
loading.value = false
|
|
90
|
+
loading.value = false
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
if (!error.value) {
|
|
96
|
-
highlightLater()
|
|
94
|
+
highlightLater()
|
|
97
95
|
}
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
watch(
|
|
101
99
|
() => props.routeId,
|
|
102
100
|
() => {
|
|
103
|
-
load()
|
|
101
|
+
load()
|
|
104
102
|
}
|
|
105
|
-
)
|
|
103
|
+
)
|
|
106
104
|
|
|
107
105
|
onMounted(() => {
|
|
108
|
-
load()
|
|
109
|
-
})
|
|
106
|
+
load()
|
|
107
|
+
})
|
|
110
108
|
|
|
111
|
-
return { loading, code, error, close, link }
|
|
109
|
+
return { loading, code, error, close, link }
|
|
112
110
|
},
|
|
113
111
|
template: `
|
|
114
112
|
<div class="frv-route-code-display" style="border:1px solid #ccc; position:relative; background:#fff;">
|
|
@@ -122,4 +120,4 @@ export default defineComponent({
|
|
|
122
120
|
<pre v-else style="margin:0;"><code class="language-python">{{ code }}</code></pre>
|
|
123
121
|
</div>
|
|
124
122
|
</div>`,
|
|
125
|
-
})
|
|
123
|
+
})
|