fastapi-voyager 0.8.3__py3-none-any.whl → 0.9.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/__init__.py +4 -0
- fastapi_voyager/cli.py +1 -1
- fastapi_voyager/server.py +89 -21
- fastapi_voyager/type.py +0 -4
- fastapi_voyager/version.py +1 -1
- fastapi_voyager/voyager.py +1 -7
- fastapi_voyager/web/component/route-code-display.js +88 -26
- fastapi_voyager/web/component/schema-code-display.js +50 -18
- fastapi_voyager/web/component/schema-field-filter.js +7 -7
- fastapi_voyager/web/vue-main.js +34 -33
- {fastapi_voyager-0.8.3.dist-info → fastapi_voyager-0.9.1.dist-info}/METADATA +58 -24
- {fastapi_voyager-0.8.3.dist-info → fastapi_voyager-0.9.1.dist-info}/RECORD +15 -15
- {fastapi_voyager-0.8.3.dist-info → fastapi_voyager-0.9.1.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.8.3.dist-info → fastapi_voyager-0.9.1.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.8.3.dist-info → fastapi_voyager-0.9.1.dist-info}/licenses/LICENSE +0 -0
fastapi_voyager/__init__.py
CHANGED
fastapi_voyager/cli.py
CHANGED
|
@@ -281,7 +281,7 @@ Examples:
|
|
|
281
281
|
except ImportError:
|
|
282
282
|
print("Error: uvicorn is required to run the server. Install via 'pip install uvicorn' or 'uv add uvicorn'.")
|
|
283
283
|
sys.exit(1)
|
|
284
|
-
app_server = viz_server.
|
|
284
|
+
app_server = viz_server.create_voyager(
|
|
285
285
|
app,
|
|
286
286
|
module_color=module_color,
|
|
287
287
|
module_prefix=args.module_prefix,
|
fastapi_voyager/server.py
CHANGED
|
@@ -6,23 +6,18 @@ from pydantic import BaseModel
|
|
|
6
6
|
from fastapi.responses import HTMLResponse, PlainTextResponse, JSONResponse
|
|
7
7
|
from fastapi.staticfiles import StaticFiles
|
|
8
8
|
from fastapi_voyager.voyager import Voyager
|
|
9
|
-
from fastapi_voyager.type import Tag, FieldInfo, CoreData
|
|
9
|
+
from fastapi_voyager.type import Tag, FieldInfo, CoreData, SchemaNode
|
|
10
10
|
from fastapi_voyager.render import Renderer
|
|
11
|
+
from fastapi_voyager.type_helper import get_source, get_vscode_link
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
WEB_DIR = Path(__file__).parent / "web"
|
|
14
15
|
WEB_DIR.mkdir(exist_ok=True)
|
|
15
16
|
|
|
16
|
-
class SchemaType(BaseModel):
|
|
17
|
-
name: str
|
|
18
|
-
fullname: str
|
|
19
|
-
source_code: str
|
|
20
|
-
vscode_link: str
|
|
21
|
-
fields: list[FieldInfo]
|
|
22
17
|
|
|
23
18
|
class OptionParam(BaseModel):
|
|
24
19
|
tags: list[Tag]
|
|
25
|
-
schemas: list[
|
|
20
|
+
schemas: list[SchemaNode]
|
|
26
21
|
dot: str
|
|
27
22
|
|
|
28
23
|
class Payload(BaseModel):
|
|
@@ -40,26 +35,22 @@ def create_route(
|
|
|
40
35
|
module_color: dict[str, str] | None = None,
|
|
41
36
|
module_prefix: Optional[str] = None,
|
|
42
37
|
):
|
|
38
|
+
"""
|
|
39
|
+
module_color: dict mapping module name to color string, e.g. {'models': 'lightblue'}
|
|
40
|
+
module_prefix: prefix string to define schemas show in brief mode
|
|
41
|
+
"""
|
|
43
42
|
router = APIRouter(tags=['fastapi-voyager'])
|
|
44
43
|
|
|
45
44
|
@router.get("/dot", response_model=OptionParam)
|
|
46
45
|
def get_dot() -> str:
|
|
47
|
-
voyager = Voyager(module_color=module_color
|
|
46
|
+
voyager = Voyager(module_color=module_color)
|
|
48
47
|
voyager.analysis(target_app)
|
|
49
48
|
dot = voyager.render_dot()
|
|
50
49
|
|
|
51
50
|
# include tags and their routes
|
|
52
51
|
tags = voyager.tags
|
|
53
52
|
|
|
54
|
-
schemas = [
|
|
55
|
-
SchemaType(
|
|
56
|
-
name=s.name,
|
|
57
|
-
fullname=s.id,
|
|
58
|
-
fields=s.fields,
|
|
59
|
-
source_code=s.source_code,
|
|
60
|
-
vscode_link=s.vscode_link
|
|
61
|
-
) for s in voyager.nodes
|
|
62
|
-
]
|
|
53
|
+
schemas = voyager.nodes[:]
|
|
63
54
|
schemas.sort(key=lambda s: s.name)
|
|
64
55
|
|
|
65
56
|
return OptionParam(tags=tags, schemas=schemas, dot=dot)
|
|
@@ -74,7 +65,6 @@ def create_route(
|
|
|
74
65
|
show_fields=payload.show_fields,
|
|
75
66
|
module_color=module_color,
|
|
76
67
|
route_name=payload.route_name,
|
|
77
|
-
load_meta=False,
|
|
78
68
|
hide_primitive_route=payload.hide_primitive_route,
|
|
79
69
|
)
|
|
80
70
|
voyager.analysis(target_app)
|
|
@@ -92,7 +82,6 @@ def create_route(
|
|
|
92
82
|
show_fields=payload.show_fields,
|
|
93
83
|
module_color=module_color,
|
|
94
84
|
route_name=payload.route_name,
|
|
95
|
-
load_meta=False,
|
|
96
85
|
)
|
|
97
86
|
voyager.analysis(target_app)
|
|
98
87
|
return voyager.dump_core_data()
|
|
@@ -117,11 +106,90 @@ def create_route(
|
|
|
117
106
|
</body>
|
|
118
107
|
</html>
|
|
119
108
|
"""
|
|
109
|
+
|
|
110
|
+
class SourcePayload(BaseModel):
|
|
111
|
+
schema_name: str
|
|
120
112
|
|
|
113
|
+
@router.post("/source")
|
|
114
|
+
def get_object_by_module_name(payload: SourcePayload):
|
|
115
|
+
"""
|
|
116
|
+
input: __module__ + __name__, eg: tests.demo.PageStories
|
|
117
|
+
output: source code of the object
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
components = payload.schema_name.split('.')
|
|
121
|
+
if len(components) < 2:
|
|
122
|
+
return JSONResponse(
|
|
123
|
+
status_code=400,
|
|
124
|
+
content={"error": "Invalid schema name format. Expected format: module.ClassName"}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
module_name = '.'.join(components[:-1])
|
|
128
|
+
class_name = components[-1]
|
|
129
|
+
|
|
130
|
+
mod = __import__(module_name, fromlist=[class_name])
|
|
131
|
+
obj = getattr(mod, class_name)
|
|
132
|
+
source_code = get_source(obj)
|
|
133
|
+
|
|
134
|
+
return JSONResponse(content={"source_code": source_code})
|
|
135
|
+
except ImportError as e:
|
|
136
|
+
return JSONResponse(
|
|
137
|
+
status_code=404,
|
|
138
|
+
content={"error": f"Module not found: {e}"}
|
|
139
|
+
)
|
|
140
|
+
except AttributeError as e:
|
|
141
|
+
return JSONResponse(
|
|
142
|
+
status_code=404,
|
|
143
|
+
content={"error": f"Class not found: {e}"}
|
|
144
|
+
)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
return JSONResponse(
|
|
147
|
+
status_code=500,
|
|
148
|
+
content={"error": f"Internal error: {str(e)}"}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@router.post("/vscode-link")
|
|
152
|
+
def get_vscode_link_by_module_name(payload: SourcePayload):
|
|
153
|
+
"""
|
|
154
|
+
input: __module__ + __name__, eg: tests.demo.PageStories
|
|
155
|
+
output: source path of the object
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
components = payload.schema_name.split('.')
|
|
159
|
+
if len(components) < 2:
|
|
160
|
+
return JSONResponse(
|
|
161
|
+
status_code=400,
|
|
162
|
+
content={"error": "Invalid schema name format. Expected format: module.ClassName"}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
module_name = '.'.join(components[:-1])
|
|
166
|
+
class_name = components[-1]
|
|
167
|
+
|
|
168
|
+
mod = __import__(module_name, fromlist=[class_name])
|
|
169
|
+
obj = getattr(mod, class_name)
|
|
170
|
+
link = get_vscode_link(obj)
|
|
171
|
+
|
|
172
|
+
return JSONResponse(content={"link": link})
|
|
173
|
+
except ImportError as e:
|
|
174
|
+
return JSONResponse(
|
|
175
|
+
status_code=404,
|
|
176
|
+
content={"error": f"Module not found: {e}"}
|
|
177
|
+
)
|
|
178
|
+
except AttributeError as e:
|
|
179
|
+
return JSONResponse(
|
|
180
|
+
status_code=404,
|
|
181
|
+
content={"error": f"Class not found: {e}"}
|
|
182
|
+
)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
return JSONResponse(
|
|
185
|
+
status_code=500,
|
|
186
|
+
content={"error": f"Internal error: {str(e)}"}
|
|
187
|
+
)
|
|
188
|
+
|
|
121
189
|
return router
|
|
122
190
|
|
|
123
191
|
|
|
124
|
-
def
|
|
192
|
+
def create_voyager(
|
|
125
193
|
target_app: FastAPI,
|
|
126
194
|
module_color: dict[str, str] | None = None,
|
|
127
195
|
gzip_minimum_size: int | None = 500,
|
fastapi_voyager/type.py
CHANGED
|
@@ -22,8 +22,6 @@ class Tag(NodeBase):
|
|
|
22
22
|
@dataclass
|
|
23
23
|
class Route(NodeBase):
|
|
24
24
|
module: str
|
|
25
|
-
source_code: str = ''
|
|
26
|
-
vscode_link: str = '' # optional vscode deep link
|
|
27
25
|
response_schema: str = ''
|
|
28
26
|
is_primitive: bool = True
|
|
29
27
|
|
|
@@ -37,8 +35,6 @@ class ModuleRoute:
|
|
|
37
35
|
@dataclass
|
|
38
36
|
class SchemaNode(NodeBase):
|
|
39
37
|
module: str
|
|
40
|
-
source_code: str = '' # optional for tests / backward compatibility
|
|
41
|
-
vscode_link: str = '' # optional vscode deep link
|
|
42
38
|
fields: list[FieldInfo] = field(default_factory=list)
|
|
43
39
|
|
|
44
40
|
@dataclass
|
fastapi_voyager/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.9.1"
|
fastapi_voyager/voyager.py
CHANGED
|
@@ -29,7 +29,6 @@ class Voyager:
|
|
|
29
29
|
module_color: dict[str, str] | None = None,
|
|
30
30
|
route_name: str | None = None,
|
|
31
31
|
hide_primitive_route: bool = False,
|
|
32
|
-
load_meta: bool = False
|
|
33
32
|
):
|
|
34
33
|
|
|
35
34
|
self.routes: list[Route] = []
|
|
@@ -50,7 +49,6 @@ class Voyager:
|
|
|
50
49
|
self.show_fields = show_fields if show_fields in ('single','object','all') else 'object'
|
|
51
50
|
self.module_color = module_color or {}
|
|
52
51
|
self.route_name = route_name
|
|
53
|
-
self.load_meta = load_meta
|
|
54
52
|
self.hide_primitive_route = hide_primitive_route
|
|
55
53
|
|
|
56
54
|
|
|
@@ -85,7 +83,7 @@ class Voyager:
|
|
|
85
83
|
self.tags.append(tag_obj)
|
|
86
84
|
|
|
87
85
|
# add route and create links
|
|
88
|
-
route_id =
|
|
86
|
+
route_id = full_class_name(route.endpoint)
|
|
89
87
|
route_name = route.endpoint.__name__
|
|
90
88
|
route_module = route.endpoint.__module__
|
|
91
89
|
|
|
@@ -110,8 +108,6 @@ class Voyager:
|
|
|
110
108
|
id=route_id,
|
|
111
109
|
name=route_name,
|
|
112
110
|
module=route_module,
|
|
113
|
-
vscode_link=get_vscode_link(route.endpoint) if self.load_meta else '',
|
|
114
|
-
source_code=inspect.getsource(route.endpoint) if self.load_meta else '',
|
|
115
111
|
response_schema=get_type_name(route.response_model),
|
|
116
112
|
is_primitive=is_primitive_response
|
|
117
113
|
)
|
|
@@ -154,8 +150,6 @@ class Voyager:
|
|
|
154
150
|
id=full_name,
|
|
155
151
|
module=schema.__module__,
|
|
156
152
|
name=schema.__name__,
|
|
157
|
-
source_code=get_source(schema) if self.load_meta else '',
|
|
158
|
-
vscode_link=get_vscode_link(schema) if self.load_meta else '',
|
|
159
153
|
fields=get_pydantic_fields(schema, bases_fields)
|
|
160
154
|
)
|
|
161
155
|
return full_name
|
|
@@ -6,58 +6,119 @@ const { defineComponent, ref, watch, onMounted } = window.Vue;
|
|
|
6
6
|
// modelValue: dialog visibility
|
|
7
7
|
// routes: object map { id: { id, name, source_code } }
|
|
8
8
|
export default defineComponent({
|
|
9
|
-
name:
|
|
9
|
+
name: "RouteCodeDisplay",
|
|
10
10
|
props: {
|
|
11
11
|
routeId: { type: String, required: true },
|
|
12
12
|
modelValue: { type: Boolean, default: false },
|
|
13
13
|
routes: { type: Object, default: () => ({}) },
|
|
14
14
|
},
|
|
15
|
-
emits: [
|
|
15
|
+
emits: ["close"],
|
|
16
16
|
setup(props, { emit }) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
17
|
+
const loading = ref(false);
|
|
18
|
+
const code = ref("");
|
|
19
|
+
const error = ref("");
|
|
20
|
+
const link = ref("");
|
|
20
21
|
|
|
21
|
-
function close() {
|
|
22
|
+
function close() {
|
|
23
|
+
emit("close");
|
|
24
|
+
}
|
|
22
25
|
|
|
23
26
|
function highlightLater() {
|
|
24
27
|
requestAnimationFrame(() => {
|
|
25
28
|
try {
|
|
26
29
|
if (window.hljs) {
|
|
27
|
-
const block = document.querySelector(
|
|
30
|
+
const block = document.querySelector(
|
|
31
|
+
".frv-route-code-display pre code.language-python"
|
|
32
|
+
);
|
|
28
33
|
if (block) {
|
|
29
34
|
window.hljs.highlightElement(block);
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
} catch (e) {
|
|
33
|
-
console.warn(
|
|
38
|
+
console.warn("highlight failed", e);
|
|
34
39
|
}
|
|
35
40
|
});
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
function load() {
|
|
43
|
+
async function load() {
|
|
44
|
+
if (!props.routeId) {
|
|
45
|
+
code.value = "";
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
loading.value = true;
|
|
39
50
|
error.value = null;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
code.value = "";
|
|
52
|
+
link.value = "";
|
|
53
|
+
|
|
54
|
+
// try to fetch from server: POST /source with { schema_name: routeId }
|
|
55
|
+
const payload = { schema_name: props.routeId };
|
|
56
|
+
try {
|
|
57
|
+
const resp = await fetch(`source`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: {
|
|
60
|
+
Accept: "application/json",
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify(payload),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const data = await resp.json().catch(() => ({}));
|
|
67
|
+
if (resp.ok) {
|
|
68
|
+
code.value = data.source_code || "// no source code available";
|
|
69
|
+
} else {
|
|
70
|
+
error.value = (data && data.error) || "Failed to load source";
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
error.value = e && e.message ? e.message : "Failed to load source";
|
|
74
|
+
} finally {
|
|
75
|
+
loading.value = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const resp = await fetch(`vscode-link`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
Accept: "application/json",
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify(payload),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const data = await resp.json().catch(() => ({}));
|
|
89
|
+
if (resp.ok) {
|
|
90
|
+
link.value = data.link || "// no source code available";
|
|
91
|
+
} else {
|
|
92
|
+
error.value += (data && data.error) || "Failed to load vscode link";
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
} finally {
|
|
96
|
+
loading.value = false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!error.value) {
|
|
45
100
|
highlightLater();
|
|
46
|
-
} else if (item) {
|
|
47
|
-
code.value = '// no source code available';
|
|
48
|
-
link.value = item.vscode_link || '';
|
|
49
|
-
} else {
|
|
50
|
-
error.value = 'Route not found';
|
|
51
|
-
link.value = '';
|
|
52
101
|
}
|
|
53
102
|
}
|
|
54
103
|
|
|
55
|
-
watch(
|
|
56
|
-
|
|
104
|
+
watch(
|
|
105
|
+
() => props.modelValue,
|
|
106
|
+
(v) => {
|
|
107
|
+
if (v) load();
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
watch(
|
|
111
|
+
() => props.routeId,
|
|
112
|
+
() => {
|
|
113
|
+
if (props.modelValue) load();
|
|
114
|
+
}
|
|
115
|
+
);
|
|
57
116
|
|
|
58
|
-
onMounted(() => {
|
|
117
|
+
onMounted(() => {
|
|
118
|
+
if (props.modelValue) load();
|
|
119
|
+
});
|
|
59
120
|
|
|
60
|
-
return { code, error, close, link };
|
|
121
|
+
return { loading, code, error, close, link };
|
|
61
122
|
},
|
|
62
123
|
template: `
|
|
63
124
|
<div class="frv-route-code-display" style="border:1px solid #ccc; position:relative; width:50vw; max-width:50vw; height:100%; background:#fff;">
|
|
@@ -66,8 +127,9 @@ export default defineComponent({
|
|
|
66
127
|
<a :href="link" target="_blank" rel="noopener" style="font-size:12px; color:#3b82f6;">Open in VSCode</a>
|
|
67
128
|
</div>
|
|
68
129
|
<div style="padding:40px 16px 16px 16px; height:100%; box-sizing:border-box; overflow:auto;">
|
|
69
|
-
<div v-if="
|
|
130
|
+
<div v-if="loading" style="font-family:Menlo, monospace; font-size:12px;">Loading source...</div>
|
|
131
|
+
<div v-else-if="error" style="color:#c10015; font-family:Menlo, monospace; font-size:12px;">{{ error }}</div>
|
|
70
132
|
<pre v-else style="margin:0;"><code class="language-python">{{ code }}</code></pre>
|
|
71
133
|
</div>
|
|
72
|
-
</div
|
|
134
|
+
</div>`,
|
|
73
135
|
});
|
|
@@ -14,7 +14,6 @@ export default defineComponent({
|
|
|
14
14
|
props: {
|
|
15
15
|
schemaName: { type: String, required: true },
|
|
16
16
|
modelValue: { type: Boolean, default: false },
|
|
17
|
-
source: { type: String, default: null },
|
|
18
17
|
schemas: { type: Array, default: () => [] },
|
|
19
18
|
},
|
|
20
19
|
emits: ["close"],
|
|
@@ -22,9 +21,9 @@ export default defineComponent({
|
|
|
22
21
|
const loading = ref(false);
|
|
23
22
|
const code = ref("");
|
|
24
23
|
const link = ref("");
|
|
25
|
-
const error = ref(
|
|
24
|
+
const error = ref("");
|
|
26
25
|
const fields = ref([]); // schema fields list
|
|
27
|
-
const tab = ref(
|
|
26
|
+
const tab = ref("source");
|
|
28
27
|
|
|
29
28
|
function close() {
|
|
30
29
|
emit("close");
|
|
@@ -48,38 +47,71 @@ export default defineComponent({
|
|
|
48
47
|
});
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
function loadSource() {
|
|
50
|
+
async function loadSource() {
|
|
52
51
|
if (!props.schemaName) return;
|
|
53
|
-
|
|
54
|
-
code.value = props.source;
|
|
55
|
-
highlightLater();
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
52
|
+
|
|
58
53
|
loading.value = true;
|
|
59
54
|
error.value = null;
|
|
55
|
+
code.value = "";
|
|
56
|
+
link.value = "";
|
|
57
|
+
|
|
58
|
+
// try to fetch from server: /source/{schema_name}
|
|
59
|
+
const payload = { schema_name: props.schemaName };
|
|
60
60
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
// validate input: ensure we have a non-empty schemaName
|
|
62
|
+
const resp = await fetch(`source`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
Accept: "application/json",
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify(payload),
|
|
69
|
+
});
|
|
70
|
+
// surface server-side validation message for bad request
|
|
71
|
+
const data = await resp.json().catch(() => ({}));
|
|
72
|
+
if (resp.ok) {
|
|
73
|
+
code.value = data.source_code || "// no source code available";
|
|
68
74
|
} else {
|
|
69
|
-
error.value = "
|
|
75
|
+
error.value = (data && data.error) || "Failed to load source";
|
|
70
76
|
}
|
|
71
77
|
} catch (e) {
|
|
72
78
|
error.value = "Failed to load source";
|
|
73
79
|
} finally {
|
|
74
80
|
loading.value = false;
|
|
75
81
|
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const resp = await fetch(`vscode-link`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
Accept: "application/json",
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify(payload),
|
|
91
|
+
});
|
|
92
|
+
// surface server-side validation message for bad request
|
|
93
|
+
const data = await resp.json().catch(() => ({}));
|
|
94
|
+
if (resp.ok) {
|
|
95
|
+
link.value = data.link || "// no vscode link available";
|
|
96
|
+
} else {
|
|
97
|
+
error.value += (data && data.error) || "Failed to load source";
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
error.value = "Failed to load source";
|
|
101
|
+
} finally {
|
|
102
|
+
loading.value = false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!error.value && tab.value === "source") {
|
|
106
|
+
highlightLater();
|
|
107
|
+
}
|
|
76
108
|
}
|
|
77
109
|
|
|
78
110
|
// re-highlight when switching back to source tab
|
|
79
111
|
watch(
|
|
80
112
|
() => tab.value,
|
|
81
113
|
(val) => {
|
|
82
|
-
if (val ===
|
|
114
|
+
if (val === "source") {
|
|
83
115
|
highlightLater();
|
|
84
116
|
}
|
|
85
117
|
}
|
|
@@ -43,8 +43,8 @@ export default defineComponent({
|
|
|
43
43
|
state.error = null;
|
|
44
44
|
state.schemas = Array.isArray(props.schemas) ? props.schemas : [];
|
|
45
45
|
state.schemaOptions = state.schemas.map((s) => ({
|
|
46
|
-
label: `${s.name} (${s.
|
|
47
|
-
value: s.
|
|
46
|
+
label: `${s.name} (${s.id})`,
|
|
47
|
+
value: s.id,
|
|
48
48
|
}));
|
|
49
49
|
// Maintain compatibility: loadingSchemas flag toggled quickly (no async work)
|
|
50
50
|
state.loadingSchemas = false;
|
|
@@ -54,8 +54,8 @@ export default defineComponent({
|
|
|
54
54
|
const needle = (val || "").toLowerCase();
|
|
55
55
|
update(() => {
|
|
56
56
|
let opts = state.schemas.map((s) => ({
|
|
57
|
-
label: `${s.name} (${s.
|
|
58
|
-
value: s.
|
|
57
|
+
label: `${s.name} (${s.id})`,
|
|
58
|
+
value: s.id,
|
|
59
59
|
}));
|
|
60
60
|
if (needle) {
|
|
61
61
|
opts = opts.filter((o) => o.label.toLowerCase().includes(needle));
|
|
@@ -67,7 +67,7 @@ export default defineComponent({
|
|
|
67
67
|
function onSchemaChange(val) {
|
|
68
68
|
state.schemaFullname = val;
|
|
69
69
|
state.fieldName = null;
|
|
70
|
-
const schema = state.schemas.find((s) => s.
|
|
70
|
+
const schema = state.schemas.find((s) => s.id === val);
|
|
71
71
|
state.fieldOptions = schema ? schema.fields.map((f) => f.name) : [];
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -106,9 +106,9 @@ export default defineComponent({
|
|
|
106
106
|
function applyExternalSchema(name) {
|
|
107
107
|
if (!name || !state.schemas.length) return;
|
|
108
108
|
if (lastAppliedExternal === name) return; // avoid duplicate
|
|
109
|
-
const schema = state.schemas.find((s) => s.
|
|
109
|
+
const schema = state.schemas.find((s) => s.id === name);
|
|
110
110
|
if (!schema) return;
|
|
111
|
-
state.schemaFullname = schema.
|
|
111
|
+
state.schemaFullname = schema.id;
|
|
112
112
|
state.fieldOptions = schema.fields.map((f) => f.name);
|
|
113
113
|
state.fieldName = null; // reset field for external injection
|
|
114
114
|
lastAppliedExternal = name;
|
fastapi_voyager/web/vue-main.js
CHANGED
|
@@ -13,7 +13,7 @@ const app = createApp({
|
|
|
13
13
|
tagOptions: [], // array of strings
|
|
14
14
|
routeId: null,
|
|
15
15
|
routeOptions: [], // [{ label, value }]
|
|
16
|
-
|
|
16
|
+
schemaId: null,
|
|
17
17
|
schemaOptions: [], // [{ label, value }]
|
|
18
18
|
routeItems: {}, // { id: { label, value } }
|
|
19
19
|
showFields: "object",
|
|
@@ -26,7 +26,7 @@ const app = createApp({
|
|
|
26
26
|
hidePrimitiveRoute: false,
|
|
27
27
|
generating: false,
|
|
28
28
|
rawTags: [], // [{ name, routes: [{ id, name }] }]
|
|
29
|
-
rawSchemas: [], // [{ name,
|
|
29
|
+
rawSchemas: [], // [{ name, id }]
|
|
30
30
|
rawSchemasFull: [], // full objects with source_code & fields
|
|
31
31
|
initializing: true,
|
|
32
32
|
// Splitter size (left panel width in px)
|
|
@@ -47,6 +47,7 @@ const app = createApp({
|
|
|
47
47
|
const schemaFieldFilterSchema = ref(null); // external schemaName for schema-field-filter
|
|
48
48
|
const schemaCodeName = ref("");
|
|
49
49
|
const routeCodeId = ref("");
|
|
50
|
+
|
|
50
51
|
function openDetail() {
|
|
51
52
|
showDetail.value = true;
|
|
52
53
|
}
|
|
@@ -70,10 +71,10 @@ const app = createApp({
|
|
|
70
71
|
function onFilterSchemas(val, update) {
|
|
71
72
|
const normalized = (val || "").toLowerCase();
|
|
72
73
|
update(() => {
|
|
73
|
-
const makeLabel = (s) => `${s.name} (${s.
|
|
74
|
+
const makeLabel = (s) => `${s.name} (${s.id})`;
|
|
74
75
|
let list = state.rawSchemas.map((s) => ({
|
|
75
76
|
label: makeLabel(s),
|
|
76
|
-
value: s.
|
|
77
|
+
value: s.id,
|
|
77
78
|
}));
|
|
78
79
|
if (normalized) {
|
|
79
80
|
list = list.filter((opt) =>
|
|
@@ -93,7 +94,7 @@ const app = createApp({
|
|
|
93
94
|
state.rawSchemasFull = Array.isArray(data.schemas) ? data.schemas : [];
|
|
94
95
|
state.rawSchemas = state.rawSchemasFull.map((s) => ({
|
|
95
96
|
name: s.name,
|
|
96
|
-
|
|
97
|
+
id: s.id,
|
|
97
98
|
}));
|
|
98
99
|
state.routeItems = data.tags
|
|
99
100
|
.map((t) => t.routes)
|
|
@@ -105,8 +106,8 @@ const app = createApp({
|
|
|
105
106
|
|
|
106
107
|
state.tagOptions = state.rawTags.map((t) => t.name);
|
|
107
108
|
state.schemaOptions = state.rawSchemas.map((s) => ({
|
|
108
|
-
label: `${s.name} (${s.
|
|
109
|
-
value: s.
|
|
109
|
+
label: `${s.name} (${s.id})`,
|
|
110
|
+
value: s.id,
|
|
110
111
|
}));
|
|
111
112
|
// default route options placeholder
|
|
112
113
|
state.routeOptions = [];
|
|
@@ -117,16 +118,16 @@ const app = createApp({
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
async function onGenerate(resetZoom=true) {
|
|
121
|
+
async function onGenerate(resetZoom = true) {
|
|
121
122
|
state.generating = true;
|
|
122
123
|
try {
|
|
123
124
|
const payload = {
|
|
124
125
|
tags: state.tag ? [state.tag] : null,
|
|
125
|
-
schema_name: state.
|
|
126
|
+
schema_name: state.schemaId || null,
|
|
126
127
|
route_name: state.routeId || null,
|
|
127
128
|
show_fields: state.showFields,
|
|
128
129
|
brief: state.brief,
|
|
129
|
-
hide_primitive_route: state.hidePrimitiveRoute
|
|
130
|
+
hide_primitive_route: state.hidePrimitiveRoute,
|
|
130
131
|
};
|
|
131
132
|
|
|
132
133
|
const res = await fetch("dot", {
|
|
@@ -138,21 +139,21 @@ const app = createApp({
|
|
|
138
139
|
|
|
139
140
|
// create graph instance once
|
|
140
141
|
const graphUI = new GraphUI("#graph", {
|
|
141
|
-
onSchemaClick: (
|
|
142
|
-
if (state.rawSchemas.find((s) => s.
|
|
143
|
-
schemaFieldFilterSchema.value =
|
|
142
|
+
onSchemaClick: (id) => {
|
|
143
|
+
if (state.rawSchemas.find((s) => s.id === id)) {
|
|
144
|
+
schemaFieldFilterSchema.value = id;
|
|
144
145
|
showSchemaFieldFilter.value = true;
|
|
145
146
|
}
|
|
146
147
|
},
|
|
147
|
-
onSchemaAltClick: (
|
|
148
|
-
// priority: schema
|
|
149
|
-
if (state.rawSchemas.find((s) => s.
|
|
150
|
-
schemaCodeName.value =
|
|
148
|
+
onSchemaAltClick: (id) => {
|
|
149
|
+
// priority: schema id; else route id
|
|
150
|
+
if (state.rawSchemas.find((s) => s.id === id)) {
|
|
151
|
+
schemaCodeName.value = id;
|
|
151
152
|
showSchemaCode.value = true;
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
|
-
if (
|
|
155
|
-
routeCodeId.value =
|
|
155
|
+
if (id in state.routeItems) {
|
|
156
|
+
routeCodeId.value = id;
|
|
156
157
|
showRouteCode.value = true;
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
@@ -171,7 +172,7 @@ const app = createApp({
|
|
|
171
172
|
try {
|
|
172
173
|
const payload = {
|
|
173
174
|
tags: state.tag ? [state.tag] : null,
|
|
174
|
-
schema_name: state.
|
|
175
|
+
schema_name: state.schemaId || null,
|
|
175
176
|
route_name: state.routeId || null,
|
|
176
177
|
show_fields: state.showFields,
|
|
177
178
|
brief: state.brief,
|
|
@@ -232,16 +233,16 @@ const app = createApp({
|
|
|
232
233
|
async function onReset() {
|
|
233
234
|
state.tag = null;
|
|
234
235
|
state.routeId = "";
|
|
235
|
-
state.
|
|
236
|
+
state.schemaId = null;
|
|
236
237
|
// state.showFields = "object";
|
|
237
238
|
state.brief = false;
|
|
238
|
-
onGenerate()
|
|
239
|
+
onGenerate();
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
function toggleTag(tagName, expanded = null) {
|
|
242
243
|
if (expanded === true) {
|
|
243
244
|
state.tag = tagName;
|
|
244
|
-
state.routeId =
|
|
245
|
+
state.routeId = "";
|
|
245
246
|
onGenerate();
|
|
246
247
|
return;
|
|
247
248
|
}
|
|
@@ -249,26 +250,26 @@ const app = createApp({
|
|
|
249
250
|
|
|
250
251
|
function selectRoute(routeId) {
|
|
251
252
|
if (state.routeId === routeId) {
|
|
252
|
-
state.routeId =
|
|
253
|
+
state.routeId = "";
|
|
253
254
|
} else {
|
|
254
|
-
state.routeId = routeId
|
|
255
|
+
state.routeId = routeId;
|
|
255
256
|
}
|
|
256
|
-
onGenerate()
|
|
257
|
+
onGenerate();
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
function toggleShowField(field) {
|
|
260
261
|
state.showFields = field;
|
|
261
|
-
onGenerate(false)
|
|
262
|
+
onGenerate(false);
|
|
262
263
|
}
|
|
263
264
|
|
|
264
265
|
function toggleBrief(val) {
|
|
265
266
|
state.brief = val;
|
|
266
|
-
onGenerate()
|
|
267
|
+
onGenerate();
|
|
267
268
|
}
|
|
268
|
-
|
|
269
|
+
|
|
269
270
|
function toggleHidePrimitiveRoute(val) {
|
|
270
271
|
state.hidePrimitiveRoute = val;
|
|
271
|
-
onGenerate(false)
|
|
272
|
+
onGenerate(false);
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
onMounted(async () => {
|
|
@@ -308,14 +309,14 @@ const app = createApp({
|
|
|
308
309
|
// render graph dialog
|
|
309
310
|
showRenderGraph,
|
|
310
311
|
renderCoreData,
|
|
311
|
-
toggleShowField
|
|
312
|
+
toggleShowField,
|
|
312
313
|
};
|
|
313
314
|
},
|
|
314
315
|
});
|
|
315
316
|
app.use(window.Quasar);
|
|
316
317
|
// Set Quasar primary theme color to green
|
|
317
|
-
if (window.Quasar && typeof window.Quasar.setCssVar ===
|
|
318
|
-
window.Quasar.setCssVar(
|
|
318
|
+
if (window.Quasar && typeof window.Quasar.setCssVar === "function") {
|
|
319
|
+
window.Quasar.setCssVar("primary", "#009485");
|
|
319
320
|
}
|
|
320
321
|
app.component("schema-field-filter", SchemaFieldFilter);
|
|
321
322
|
app.component("schema-code-display", SchemaCodeDisplay);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-voyager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -30,6 +30,10 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
|
|
31
31
|
<p align="center"><img src="./voyager.jpg" alt="" /></p>
|
|
32
32
|
|
|
33
|
+
|
|
34
|
+
[](https://www.youtube.com/watch?v=PGlbQq1M-n8 "FastAPI Voyager")
|
|
35
|
+
|
|
36
|
+
|
|
33
37
|
> This repo is still in early stage, currently it supports pydantic v2 only, previous name: fastapi-router-viz
|
|
34
38
|
|
|
35
39
|
Inspect your API interactively
|
|
@@ -105,10 +109,10 @@ click a node to highlight it's upperstream and downstream nodes. figure out the
|
|
|
105
109
|
|
|
106
110
|
```python
|
|
107
111
|
from fastapi import FastAPI
|
|
108
|
-
from fastapi_voyager
|
|
112
|
+
from fastapi_voyager import create_voyager
|
|
109
113
|
from tests.demo import app
|
|
110
114
|
|
|
111
|
-
app.mount('/voyager',
|
|
115
|
+
app.mount('/voyager', create_voyager(
|
|
112
116
|
app,
|
|
113
117
|
module_color={"tests.service": "red"},
|
|
114
118
|
module_prefix="tests.service"))
|
|
@@ -164,7 +168,7 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
|
|
|
164
168
|
|
|
165
169
|
## Plan before v1.0
|
|
166
170
|
|
|
167
|
-
features:
|
|
171
|
+
### features:
|
|
168
172
|
- [x] group schemas by module hierarchy
|
|
169
173
|
- [x] module-based coloring via Analytics(module_color={...})
|
|
170
174
|
- [x] view in web browser
|
|
@@ -183,38 +187,62 @@ features:
|
|
|
183
187
|
- [x] add tooltips
|
|
184
188
|
- [x] route
|
|
185
189
|
- [x] group routes by module hierarchy
|
|
186
|
-
- [
|
|
187
|
-
- [
|
|
188
|
-
- [
|
|
190
|
+
- [x] add response_model in route
|
|
191
|
+
- [x] fixed left bar show tag/ route
|
|
192
|
+
- [x] export voyager core data into json (for better debugging)
|
|
193
|
+
- [x] add api to rebuild core data from json, and render it
|
|
194
|
+
- [x] fix Generic case `test_generic.py`
|
|
195
|
+
- [x] show tips for routes not return pydantic type.
|
|
196
|
+
- [x] fix duplicated link from class and parent class, it also break clicking highlight
|
|
197
|
+
- [x] refactor: abstract render module
|
|
198
|
+
|
|
199
|
+
### backlog
|
|
189
200
|
- [ ] user can generate nodes/edges manually and connect to generated ones
|
|
190
201
|
- [ ] add owner
|
|
191
202
|
- [ ] add extra info for schema
|
|
192
|
-
- [ ]
|
|
193
|
-
|
|
194
|
-
- [x] fixed left bar show tag/ route
|
|
195
|
-
- [ ] display standard ER diagram `difference`
|
|
203
|
+
- [ ] fixed left/right bar show field information
|
|
204
|
+
- [ ] display standard ER diagram `hard`
|
|
196
205
|
- [ ] display potential invalid links
|
|
197
|
-
- [ ] integration with pydantic-resolve
|
|
198
|
-
- [ ] show difference between resolve, post fields
|
|
199
|
-
- [x] strikethrough for excluded fields
|
|
200
|
-
- [ ] display loader as edges
|
|
201
|
-
- [x] export voyager core data into json (for better debugging)
|
|
202
|
-
- [x] add api to rebuild core data from json, and render it
|
|
203
|
-
- [x] fix Generic case `test_generic.py`
|
|
204
|
-
- [ ] show tips for routes not return pydantic type.
|
|
205
|
-
- [ ] add http method for route
|
|
206
206
|
|
|
207
207
|
bugs & non feature:
|
|
208
|
-
- [x] fix duplicated link from class and parent class, it also break clicking highlight
|
|
209
208
|
- [ ] add tests
|
|
210
|
-
- [
|
|
211
|
-
|
|
209
|
+
- [ ] support dataclass (pending)
|
|
210
|
+
|
|
211
|
+
### in analysis
|
|
212
|
+
- [ ] click field to highlight links
|
|
213
|
+
- [ ] animation effect for edges
|
|
214
|
+
- [ ] customrized right click panel
|
|
215
|
+
- [ ] show own dependencies
|
|
216
|
+
|
|
217
|
+
### plan:
|
|
218
|
+
|
|
219
|
+
#### 0.9
|
|
220
|
+
- [x] refactor: server.py
|
|
221
|
+
- [x] rename create_app_with_fastapi -> create_voyager
|
|
222
|
+
- [x] add doc for parameters
|
|
223
|
+
- [x] improve initialization time cost
|
|
224
|
+
- [x] query route / schema info through realtime api
|
|
225
|
+
- [x] adjust fe
|
|
226
|
+
|
|
227
|
+
#### 0.10
|
|
228
|
+
- [ ] logging information
|
|
229
|
+
- [ ] open route in swagger
|
|
230
|
+
- config docs path
|
|
231
|
+
- [ ] add http method for route
|
|
232
|
+
- [ ] enable/disable module cluster (may save space)
|
|
233
|
+
|
|
234
|
+
#### 0.11
|
|
235
|
+
- [ ] integration with pydantic-resolve
|
|
236
|
+
- [ ] show hint for resolve, post fields
|
|
237
|
+
- [ ] display loader as edges
|
|
238
|
+
|
|
212
239
|
|
|
213
240
|
## Using with pydantic-resolve
|
|
214
241
|
|
|
242
|
+
WIP: ...
|
|
243
|
+
|
|
215
244
|
pydantic-resolve's @ensure_subset decorator is helpful to pick fields from `source class` in safe.
|
|
216
245
|
|
|
217
|
-
TODO: ...
|
|
218
246
|
|
|
219
247
|
|
|
220
248
|
## Credits
|
|
@@ -225,7 +253,13 @@ TODO: ...
|
|
|
225
253
|
|
|
226
254
|
## Changelog
|
|
227
255
|
|
|
256
|
+
- 0.9:
|
|
257
|
+
- 0.9.1:
|
|
258
|
+
- api change: from `create_app_with_fastapi` to `create_voyager`, and expose as `from fastapi_voyager import create_voyager`
|
|
259
|
+
- optimization: lazy load vscode link and source code, speed up the initialization.
|
|
228
260
|
- 0.8:
|
|
261
|
+
- 0.8.3
|
|
262
|
+
- upgrade theme
|
|
229
263
|
- 0.8.2
|
|
230
264
|
- fix silly typo.
|
|
231
265
|
- 0.8.1
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
fastapi_voyager/__init__.py,sha256=
|
|
2
|
-
fastapi_voyager/cli.py,sha256=
|
|
1
|
+
fastapi_voyager/__init__.py,sha256=tZy0Nkj8kTaMgbvHy-mGxVcFGVX0Km-36dnzsAIG2uk,230
|
|
2
|
+
fastapi_voyager/cli.py,sha256=kQb4g6JEGZR99e5r8LyFFEeb_-uT-n_gp_sDoYG3R7k,11118
|
|
3
3
|
fastapi_voyager/filter.py,sha256=2Yt37o8mhqSqleafO4YRrumh_ExYUqzXFOxQRPuTbAc,8078
|
|
4
4
|
fastapi_voyager/module.py,sha256=Z2QHNmiLk6ZAJlm2nSmO875Q33TweSg8UxZSzIpU9zY,3499
|
|
5
5
|
fastapi_voyager/render.py,sha256=qy3g1Rz1s8XkuR_6n1Q1YPwy_oMOjWjNswTHQjdz4N0,7765
|
|
6
|
-
fastapi_voyager/server.py,sha256=
|
|
7
|
-
fastapi_voyager/type.py,sha256=
|
|
6
|
+
fastapi_voyager/server.py,sha256=pg-LHDj4yU0usDA1b2X2Kt2_OCrexFA2G9ifGxb52Uc,6196
|
|
7
|
+
fastapi_voyager/type.py,sha256=pWYKmgb9e0W_JeD7k54Mr2lxUZV_Ir9TNpewGRwHyHQ,1629
|
|
8
8
|
fastapi_voyager/type_helper.py,sha256=hjBC4E0tgBpQDlYxGg74uK07SXjsrAgictEETJfIpYM,9231
|
|
9
|
-
fastapi_voyager/version.py,sha256=
|
|
10
|
-
fastapi_voyager/voyager.py,sha256=
|
|
9
|
+
fastapi_voyager/version.py,sha256=jjAALUE-D-m7hgihxqGZyrZQPe4_Ch5MQPu89WFuVmw,48
|
|
10
|
+
fastapi_voyager/voyager.py,sha256=pXvA3ye5Jq6aJ6YrZFbTHjJ9MzdydqWllIgY8RFK_4A,10793
|
|
11
11
|
fastapi_voyager/web/graph-ui.js,sha256=0hqMCzsPJfhgWDuwiXVXaGQL-_7urT_zryo4sXMN8jQ,4209
|
|
12
12
|
fastapi_voyager/web/graphviz.svg.css,sha256=zDCjjpT0Idufu5YOiZI76PL70-avP3vTyzGPh9M85Do,1563
|
|
13
13
|
fastapi_voyager/web/graphviz.svg.js,sha256=lvAdbjHc-lMSk4GQp-iqYA2PCFX4RKnW7dFaoe0LUHs,16005
|
|
14
14
|
fastapi_voyager/web/index.html,sha256=AnsqXfzDrUPO12EWtiT_XJdJ6Kx3qBsRn1IfX393G48,14298
|
|
15
15
|
fastapi_voyager/web/quasar.min.css,sha256=F5jQe7X2XT54VlvAaa2V3GsBFdVD-vxDZeaPLf6U9CU,203145
|
|
16
16
|
fastapi_voyager/web/quasar.min.js,sha256=h0ftyPMW_CRiyzeVfQqiup0vrVt4_QWojpqmpnpn07E,502974
|
|
17
|
-
fastapi_voyager/web/vue-main.js,sha256=
|
|
17
|
+
fastapi_voyager/web/vue-main.js,sha256=p8akwqNf9sDM71xio0SN-3JznUOIksoy9SGRiqAeg2s,9459
|
|
18
18
|
fastapi_voyager/web/component/render-graph.js,sha256=e8Xgh2Kl-nYU0P1gstEmAepCgFnk2J6UdxW8TlMafGs,2322
|
|
19
|
-
fastapi_voyager/web/component/route-code-display.js,sha256=
|
|
20
|
-
fastapi_voyager/web/component/schema-code-display.js,sha256=
|
|
21
|
-
fastapi_voyager/web/component/schema-field-filter.js,sha256=
|
|
19
|
+
fastapi_voyager/web/component/route-code-display.js,sha256=4VN88n_7FYHxvruP5AbgTdF6Lyj6v_7Ocqy8zOVMjVA,4065
|
|
20
|
+
fastapi_voyager/web/component/schema-code-display.js,sha256=_Okkr8ceJsA_Xngyj_GpxJqdN01GzCjJTeAZamSA9Ks,6514
|
|
21
|
+
fastapi_voyager/web/component/schema-field-filter.js,sha256=1llPkN8w-RrBPHVtjjMcObndaqPnmZ7e4iU6dMJMIaE,6224
|
|
22
22
|
fastapi_voyager/web/icon/android-chrome-192x192.png,sha256=35sBy6jmUFJCcquStaafHH1qClZIbd-X3PIKSeLkrNo,37285
|
|
23
23
|
fastapi_voyager/web/icon/android-chrome-512x512.png,sha256=eb2eDjCwIruc05029_0L9hcrkVkv8KceLn1DJMYU0zY,210789
|
|
24
24
|
fastapi_voyager/web/icon/apple-touch-icon.png,sha256=gnWK46tPnvSw1-oYZjgI02wpoO4OrIzsVzGHC5oKWO0,33187
|
|
@@ -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.
|
|
30
|
-
fastapi_voyager-0.
|
|
31
|
-
fastapi_voyager-0.
|
|
32
|
-
fastapi_voyager-0.
|
|
33
|
-
fastapi_voyager-0.
|
|
29
|
+
fastapi_voyager-0.9.1.dist-info/METADATA,sha256=7yR9Wj87QH6f2kxA4QiqDnwWJPQ2-rGOhpC6ZYYfHfE,8914
|
|
30
|
+
fastapi_voyager-0.9.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
31
|
+
fastapi_voyager-0.9.1.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
|
|
32
|
+
fastapi_voyager-0.9.1.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
|
|
33
|
+
fastapi_voyager-0.9.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|