fastapi-voyager 0.15.6__py3-none-any.whl → 0.16.0a1__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 +2 -2
- fastapi_voyager/adapters/__init__.py +16 -0
- fastapi_voyager/adapters/base.py +44 -0
- fastapi_voyager/adapters/common.py +260 -0
- fastapi_voyager/adapters/django_ninja_adapter.py +299 -0
- fastapi_voyager/adapters/fastapi_adapter.py +165 -0
- fastapi_voyager/adapters/litestar_adapter.py +188 -0
- fastapi_voyager/er_diagram.py +15 -14
- fastapi_voyager/introspectors/__init__.py +34 -0
- fastapi_voyager/introspectors/base.py +81 -0
- fastapi_voyager/introspectors/detector.py +123 -0
- fastapi_voyager/introspectors/django_ninja.py +114 -0
- fastapi_voyager/introspectors/fastapi.py +83 -0
- fastapi_voyager/introspectors/litestar.py +166 -0
- fastapi_voyager/pydantic_resolve_util.py +4 -2
- fastapi_voyager/render.py +2 -2
- fastapi_voyager/render_style.py +0 -1
- fastapi_voyager/server.py +174 -295
- fastapi_voyager/type_helper.py +2 -2
- fastapi_voyager/version.py +1 -1
- fastapi_voyager/voyager.py +75 -47
- fastapi_voyager/web/index.html +11 -14
- fastapi_voyager/web/store.js +2 -0
- fastapi_voyager/web/vue-main.js +4 -0
- {fastapi_voyager-0.15.6.dist-info → fastapi_voyager-0.16.0a1.dist-info}/METADATA +133 -7
- {fastapi_voyager-0.15.6.dist-info → fastapi_voyager-0.16.0a1.dist-info}/RECORD +29 -17
- {fastapi_voyager-0.15.6.dist-info → fastapi_voyager-0.16.0a1.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.15.6.dist-info → fastapi_voyager-0.16.0a1.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.15.6.dist-info → fastapi_voyager-0.16.0a1.dist-info}/licenses/LICENSE +0 -0
fastapi_voyager/voyager.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
|
|
2
2
|
import pydantic_resolve.constant as const
|
|
3
|
-
from fastapi import FastAPI, routing
|
|
4
3
|
from pydantic import BaseModel
|
|
5
4
|
|
|
6
5
|
from fastapi_voyager.filter import (
|
|
@@ -8,6 +7,7 @@ from fastapi_voyager.filter import (
|
|
|
8
7
|
filter_subgraph_by_module_prefix,
|
|
9
8
|
filter_subgraph_from_tag_to_schema_by_module_prefix,
|
|
10
9
|
)
|
|
10
|
+
from fastapi_voyager.introspectors import AppIntrospector, RouteInfo
|
|
11
11
|
from fastapi_voyager.render import Renderer
|
|
12
12
|
from fastapi_voyager.type import PK, CoreData, FieldType, Link, LinkType, Route, SchemaNode, Tag
|
|
13
13
|
from fastapi_voyager.type_helper import (
|
|
@@ -57,99 +57,127 @@ class Voyager:
|
|
|
57
57
|
self.hide_primitive_route = hide_primitive_route
|
|
58
58
|
self.show_module = show_module
|
|
59
59
|
self.show_pydantic_resolve_meta = show_pydantic_resolve_meta
|
|
60
|
-
|
|
61
60
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
def _get_introspector(self, app) -> AppIntrospector:
|
|
62
|
+
"""
|
|
63
|
+
Get the appropriate introspector for the given app.
|
|
64
|
+
|
|
65
|
+
Automatically detects the framework type and returns the matching introspector.
|
|
66
66
|
|
|
67
|
+
Args:
|
|
68
|
+
app: A web application instance or AppIntrospector
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
Returns:
|
|
71
|
+
An AppIntrospector instance
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
TypeError: If the app type is not supported
|
|
69
75
|
"""
|
|
76
|
+
from fastapi_voyager.introspectors import get_introspector
|
|
77
|
+
|
|
78
|
+
return get_introspector(app)
|
|
79
|
+
|
|
80
|
+
def analysis(self, app):
|
|
81
|
+
"""
|
|
82
|
+
Analyze routes and schemas from a web application.
|
|
83
|
+
|
|
84
|
+
This method automatically detects the framework type and uses the appropriate
|
|
85
|
+
introspector. Supported frameworks:
|
|
86
|
+
- FastAPI (built-in)
|
|
87
|
+
- Any framework with a custom AppIntrospector implementation
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
app: A web application instance (FastAPI, Django Ninja API, etc.)
|
|
91
|
+
or an AppIntrospector instance for custom frameworks.
|
|
92
|
+
|
|
70
93
|
1. get routes which return pydantic schema
|
|
71
94
|
1.1 collect tags and routes, add links tag-> route
|
|
72
95
|
1.2 collect response_model and links route -> response_model
|
|
73
96
|
|
|
74
97
|
2. iterate schemas, construct the schema/model nodes and their links
|
|
75
98
|
"""
|
|
99
|
+
introspector = self._get_introspector(app)
|
|
76
100
|
schemas: list[type[BaseModel]] = []
|
|
77
101
|
|
|
78
102
|
# First, group all routes by tag
|
|
79
|
-
routes_by_tag: dict[str, list] = {}
|
|
80
|
-
for
|
|
81
|
-
tags = getattr(route, 'tags', None)
|
|
82
|
-
|
|
103
|
+
routes_by_tag: dict[str, list[RouteInfo]] = {}
|
|
104
|
+
for route_info in introspector.get_routes():
|
|
83
105
|
# using multiple tags is harmful, it's not recommended and will not be supported
|
|
84
|
-
route_tag = tags[0] if tags else '__default__'
|
|
85
|
-
routes_by_tag.setdefault(route_tag, []).append(
|
|
106
|
+
route_tag = route_info.tags[0] if route_info.tags else '__default__'
|
|
107
|
+
routes_by_tag.setdefault(route_tag, []).append(route_info)
|
|
86
108
|
|
|
87
109
|
# Then filter by include_tags if provided
|
|
88
110
|
if self.include_tags:
|
|
89
|
-
filtered_routes_by_tag = {
|
|
90
|
-
|
|
111
|
+
filtered_routes_by_tag = {
|
|
112
|
+
tag: routes
|
|
113
|
+
for tag, routes in routes_by_tag.items()
|
|
114
|
+
if tag in self.include_tags
|
|
115
|
+
}
|
|
91
116
|
else:
|
|
92
117
|
filtered_routes_by_tag = routes_by_tag
|
|
93
118
|
|
|
94
119
|
# Process filtered routes
|
|
95
|
-
for route_tag,
|
|
96
|
-
|
|
120
|
+
for route_tag, route_infos in filtered_routes_by_tag.items():
|
|
97
121
|
tag_id = f'tag__{route_tag}'
|
|
98
122
|
tag_obj = Tag(id=tag_id, name=route_tag, routes=[])
|
|
99
123
|
self.tags.append(tag_obj)
|
|
100
124
|
|
|
101
|
-
for
|
|
102
|
-
# add route and create links
|
|
103
|
-
route_id = full_class_name(route.endpoint)
|
|
104
|
-
route_name = route.endpoint.__name__
|
|
105
|
-
route_module = route.endpoint.__module__
|
|
106
|
-
|
|
125
|
+
for route_info in route_infos:
|
|
107
126
|
# filter by route_name (route.id) if provided
|
|
108
|
-
if self.route_name is not None and
|
|
127
|
+
if self.route_name is not None and route_info.id != self.route_name:
|
|
109
128
|
continue
|
|
110
129
|
|
|
111
|
-
is_primitive_response = is_non_pydantic_type(
|
|
130
|
+
is_primitive_response = is_non_pydantic_type(route_info.response_model)
|
|
112
131
|
# filter primitive route if needed
|
|
113
132
|
if self.hide_primitive_route and is_primitive_response:
|
|
114
133
|
continue
|
|
115
134
|
|
|
116
|
-
self.links.append(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
self.links.append(
|
|
136
|
+
Link(
|
|
137
|
+
source=tag_id,
|
|
138
|
+
source_origin=tag_id,
|
|
139
|
+
target=route_info.id,
|
|
140
|
+
target_origin=route_info.id,
|
|
141
|
+
type='tag_route',
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Get unique_id from extra data if available
|
|
146
|
+
unique_id = route_info.operation_id
|
|
147
|
+
if route_info.extra and 'unique_id' in route_info.extra:
|
|
148
|
+
unique_id = unique_id or route_info.extra['unique_id']
|
|
123
149
|
|
|
124
150
|
route_obj = Route(
|
|
125
|
-
id=
|
|
126
|
-
name=
|
|
127
|
-
module=
|
|
128
|
-
unique_id=
|
|
129
|
-
response_schema=get_type_name(
|
|
130
|
-
is_primitive=is_primitive_response
|
|
151
|
+
id=route_info.id,
|
|
152
|
+
name=route_info.name,
|
|
153
|
+
module=route_info.module,
|
|
154
|
+
unique_id=unique_id,
|
|
155
|
+
response_schema=get_type_name(route_info.response_model),
|
|
156
|
+
is_primitive=is_primitive_response,
|
|
131
157
|
)
|
|
132
158
|
self.routes.append(route_obj)
|
|
133
159
|
tag_obj.routes.append(route_obj)
|
|
134
160
|
|
|
135
161
|
# add response_models and create links from route -> response_model
|
|
136
|
-
for schema in get_core_types(
|
|
162
|
+
for schema in get_core_types(route_info.response_model):
|
|
137
163
|
if schema and issubclass(schema, BaseModel):
|
|
138
164
|
is_primitive_response = False
|
|
139
165
|
target_name = full_class_name(schema)
|
|
140
|
-
self.links.append(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
self.links.append(
|
|
167
|
+
Link(
|
|
168
|
+
source=route_info.id,
|
|
169
|
+
source_origin=route_info.id,
|
|
170
|
+
target=self.generate_node_head(target_name),
|
|
171
|
+
target_origin=target_name,
|
|
172
|
+
type='route_to_schema',
|
|
173
|
+
)
|
|
174
|
+
)
|
|
147
175
|
|
|
148
176
|
schemas.append(schema)
|
|
149
177
|
|
|
150
178
|
for s in schemas:
|
|
151
179
|
self.analysis_schemas(s)
|
|
152
|
-
|
|
180
|
+
|
|
153
181
|
self.nodes = list(self.node_set.values())
|
|
154
182
|
|
|
155
183
|
|
fastapi_voyager/web/index.html
CHANGED
|
@@ -4,32 +4,29 @@
|
|
|
4
4
|
<meta name="theme-color" content="#ffffff" />
|
|
5
5
|
<link
|
|
6
6
|
rel="stylesheet"
|
|
7
|
-
href="
|
|
8
|
-
/>
|
|
9
|
-
<link
|
|
10
|
-
rel="stylesheet"
|
|
11
|
-
href="fastapi-voyager-static/quasar.min.css<!-- VERSION_PLACEHOLDER -->"
|
|
7
|
+
href="<!-- STATIC_PATH -->/graphviz.svg.css<!-- VERSION_PLACEHOLDER -->"
|
|
12
8
|
/>
|
|
9
|
+
<link rel="stylesheet" href="<!-- STATIC_PATH -->/quasar.min.css<!-- VERSION_PLACEHOLDER -->" />
|
|
13
10
|
<!-- App Icons / Favicons -->
|
|
14
11
|
<link
|
|
15
12
|
rel="apple-touch-icon"
|
|
16
13
|
sizes="180x180"
|
|
17
|
-
href="
|
|
14
|
+
href="<!-- STATIC_PATH -->/icon/apple-touch-icon.png"
|
|
18
15
|
/>
|
|
19
16
|
<link
|
|
20
17
|
rel="icon"
|
|
21
18
|
type="image/png"
|
|
22
19
|
sizes="32x32"
|
|
23
|
-
href="
|
|
20
|
+
href="<!-- STATIC_PATH -->/icon/favicon-32x32.png"
|
|
24
21
|
/>
|
|
25
22
|
<link
|
|
26
23
|
rel="icon"
|
|
27
24
|
type="image/png"
|
|
28
25
|
sizes="16x16"
|
|
29
|
-
href="
|
|
26
|
+
href="<!-- STATIC_PATH -->/icon/favicon-16x16.png"
|
|
30
27
|
/>
|
|
31
|
-
<link rel="icon" href="
|
|
32
|
-
<link rel="manifest" href="
|
|
28
|
+
<link rel="icon" href="<!-- STATIC_PATH -->/icon/favicon.ico" sizes="any" />
|
|
29
|
+
<link rel="manifest" href="<!-- STATIC_PATH -->/icon/site.webmanifest" />
|
|
33
30
|
<link
|
|
34
31
|
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
|
|
35
32
|
rel="stylesheet"
|
|
@@ -191,7 +188,7 @@
|
|
|
191
188
|
style="font-size: 18px; font-weight: bold; display: flex; align-items: baseline"
|
|
192
189
|
>
|
|
193
190
|
<q-icon class="q-mr-sm" name="satellite_alt"></q-icon>
|
|
194
|
-
<span>
|
|
191
|
+
<span> {{ store.state.framework_name }} Voyager </span>
|
|
195
192
|
<span
|
|
196
193
|
v-if="store.state.version"
|
|
197
194
|
style="font-size: 12px; margin-left: 8px; font-weight: normal"
|
|
@@ -580,7 +577,7 @@
|
|
|
580
577
|
</q-dialog>
|
|
581
578
|
</div>
|
|
582
579
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
|
583
|
-
<script src="
|
|
580
|
+
<script src="<!-- STATIC_PATH -->/quasar.min.js<!-- VERSION_PLACEHOLDER -->"></script>
|
|
584
581
|
<script
|
|
585
582
|
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"
|
|
586
583
|
integrity="sha512-egJ/Y+22P9NQ9aIyVCh0VCOsfydyn8eNmqBy+y2CnJG+fpRIxXMS6jbWP8tVKp0jp+NO5n8WtMUAnNnGoJKi4w=="
|
|
@@ -602,7 +599,7 @@
|
|
|
602
599
|
></script>
|
|
603
600
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"></script>
|
|
604
601
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-color/2.1.2/jquery.color.min.js"></script>
|
|
605
|
-
<script src="
|
|
602
|
+
<script src="<!-- STATIC_PATH -->/graphviz.svg.js<!-- VERSION_PLACEHOLDER -->"></script>
|
|
606
603
|
<!-- highlight.js minimal ES module load (python only) -->
|
|
607
604
|
<link
|
|
608
605
|
rel="stylesheet"
|
|
@@ -626,7 +623,7 @@
|
|
|
626
623
|
</script>
|
|
627
624
|
<script
|
|
628
625
|
type="module"
|
|
629
|
-
src="
|
|
626
|
+
src="<!-- STATIC_PATH -->/vue-main.js<!-- VERSION_PLACEHOLDER -->"
|
|
630
627
|
></script>
|
|
631
628
|
|
|
632
629
|
<!-- GA_SNIPPET -->
|
fastapi_voyager/web/store.js
CHANGED
|
@@ -2,6 +2,7 @@ const { reactive } = window.Vue
|
|
|
2
2
|
|
|
3
3
|
const state = reactive({
|
|
4
4
|
version: "",
|
|
5
|
+
framework_name: "",
|
|
5
6
|
config: {
|
|
6
7
|
initial_page_policy: "first",
|
|
7
8
|
has_er_diagram: false,
|
|
@@ -305,6 +306,7 @@ const actions = {
|
|
|
305
306
|
state.swagger.url = data.swagger_url || null
|
|
306
307
|
state.config.has_er_diagram = data.has_er_diagram || false
|
|
307
308
|
state.config.enable_pydantic_resolve_meta = data.enable_pydantic_resolve_meta || false
|
|
309
|
+
state.framework_name = data.framework_name || "API"
|
|
308
310
|
|
|
309
311
|
this.rebuildSchemaOptions()
|
|
310
312
|
|
fastapi_voyager/web/vue-main.js
CHANGED
|
@@ -291,6 +291,10 @@ const app = createApp({
|
|
|
291
291
|
onMounted(async () => {
|
|
292
292
|
document.body.classList.remove("app-loading")
|
|
293
293
|
await loadInitial()
|
|
294
|
+
// Update document title after framework_name is loaded
|
|
295
|
+
if (store.state.framework_name) {
|
|
296
|
+
document.title = `${store.state.framework_name} Voyager`
|
|
297
|
+
}
|
|
294
298
|
// Reveal app content only after initial JS/data is ready
|
|
295
299
|
})
|
|
296
300
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-voyager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0a1
|
|
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
|
|
@@ -18,11 +18,15 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
|
-
Requires-Dist: fastapi>=0.110
|
|
22
21
|
Requires-Dist: jinja2>=3.0.0
|
|
23
22
|
Requires-Dist: pydantic-resolve>=2.4.3
|
|
24
23
|
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: django-ninja; extra == 'dev'
|
|
25
|
+
Requires-Dist: fastapi>=0.110; extra == 'dev'
|
|
26
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
27
|
+
Requires-Dist: litestar; extra == 'dev'
|
|
25
28
|
Requires-Dist: pytest; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
26
30
|
Requires-Dist: ruff; extra == 'dev'
|
|
27
31
|
Requires-Dist: uvicorn; extra == 'dev'
|
|
28
32
|
Description-Content-Type: text/markdown
|
|
@@ -34,10 +38,12 @@ Description-Content-Type: text/markdown
|
|
|
34
38
|
|
|
35
39
|
# FastAPI Voyager
|
|
36
40
|
|
|
37
|
-
Visualize your
|
|
41
|
+
Visualize your API endpoints and explore them interactively.
|
|
38
42
|
|
|
39
43
|
Its vision is to make code easier to read and understand, serving as an ideal documentation tool.
|
|
40
44
|
|
|
45
|
+
**Now supports multiple frameworks:** FastAPI, Django Ninja, and Litestar.
|
|
46
|
+
|
|
41
47
|
> This repo is still in early stage, it supports Pydantic v2 only.
|
|
42
48
|
|
|
43
49
|
- **Live Demo**: https://www.newsyeah.fun/voyager/
|
|
@@ -49,6 +55,7 @@ Its vision is to make code easier to read and understand, serving as an ideal do
|
|
|
49
55
|
|
|
50
56
|
- [Quick Start](#quick-start)
|
|
51
57
|
- [Installation](#installation)
|
|
58
|
+
- [Supported Frameworks](#supported-frameworks)
|
|
52
59
|
- [Features](#features)
|
|
53
60
|
- [Command Line Usage](#command-line-usage)
|
|
54
61
|
- [About pydantic-resolve](#about-pydantic-resolve)
|
|
@@ -58,7 +65,7 @@ Its vision is to make code easier to read and understand, serving as an ideal do
|
|
|
58
65
|
|
|
59
66
|
## Quick Start
|
|
60
67
|
|
|
61
|
-
With simple configuration, fastapi-voyager can be embedded into
|
|
68
|
+
With simple configuration, fastapi-voyager can be embedded into your web application:
|
|
62
69
|
|
|
63
70
|
```python
|
|
64
71
|
from fastapi import FastAPI
|
|
@@ -66,6 +73,8 @@ from fastapi_voyager import create_voyager
|
|
|
66
73
|
|
|
67
74
|
app = FastAPI()
|
|
68
75
|
|
|
76
|
+
# ... define your routes ...
|
|
77
|
+
|
|
69
78
|
app.mount('/voyager',
|
|
70
79
|
create_voyager(
|
|
71
80
|
app,
|
|
@@ -74,12 +83,14 @@ app.mount('/voyager',
|
|
|
74
83
|
swagger_url="/docs",
|
|
75
84
|
ga_id="G-XXXXXXXXVL",
|
|
76
85
|
initial_page_policy='first',
|
|
77
|
-
online_repo_url='https://github.com/
|
|
86
|
+
online_repo_url='https://github.com/your-org/your-repo/blob/master',
|
|
78
87
|
enable_pydantic_resolve_meta=True))
|
|
79
88
|
```
|
|
80
89
|
|
|
81
90
|
Visit `http://localhost:8000/voyager` to explore your API visually.
|
|
82
91
|
|
|
92
|
+
For framework-specific examples (Django Ninja, Litestar), see [Supported Frameworks](#supported-frameworks).
|
|
93
|
+
|
|
83
94
|
[View full example](https://github.com/allmonday/composition-oriented-development-pattern/blob/master/src/main.py#L48)
|
|
84
95
|
|
|
85
96
|
## Installation
|
|
@@ -110,9 +121,106 @@ voyager -m path.to.your.app.module --server --app api
|
|
|
110
121
|
|
|
111
122
|
> **Note**: [Sub-Application mounts](https://fastapi.tiangolo.com/advanced/sub-applications/) are not supported yet, but you can specify the name of the FastAPI application with `--app`. Only a single application (default: `app`) can be selected.
|
|
112
123
|
|
|
124
|
+
## Supported Frameworks
|
|
125
|
+
|
|
126
|
+
fastapi-voyager automatically detects your framework and provides the appropriate integration. Currently supported frameworks:
|
|
127
|
+
|
|
128
|
+
### FastAPI
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from fastapi import FastAPI
|
|
132
|
+
from fastapi_voyager import create_voyager
|
|
133
|
+
|
|
134
|
+
app = FastAPI()
|
|
135
|
+
|
|
136
|
+
@app.get("/hello")
|
|
137
|
+
def hello():
|
|
138
|
+
return {"message": "Hello World"}
|
|
139
|
+
|
|
140
|
+
# Mount voyager
|
|
141
|
+
app.mount("/voyager", create_voyager(app))
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Start with:
|
|
145
|
+
```bash
|
|
146
|
+
uvicorn your_app:app --reload
|
|
147
|
+
# Visit http://localhost:8000/voyager
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Django Ninja
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import os
|
|
154
|
+
import django
|
|
155
|
+
from django.core.asgi import get_asgi_application
|
|
156
|
+
from ninja import NinjaAPI
|
|
157
|
+
from fastapi_voyager import create_voyager
|
|
158
|
+
|
|
159
|
+
# Configure Django
|
|
160
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings")
|
|
161
|
+
django.setup()
|
|
162
|
+
|
|
163
|
+
# Create Django Ninja API
|
|
164
|
+
api = NinjaAPI()
|
|
165
|
+
|
|
166
|
+
@api.get("/hello")
|
|
167
|
+
def hello(request):
|
|
168
|
+
return {"message": "Hello World"}
|
|
169
|
+
|
|
170
|
+
# Create voyager ASGI app
|
|
171
|
+
voyager_app = create_voyager(api)
|
|
172
|
+
|
|
173
|
+
# Create ASGI application that routes between Django and voyager
|
|
174
|
+
async def application(scope, receive, send):
|
|
175
|
+
if scope["type"] == "http" and scope["path"].startswith("/voyager"):
|
|
176
|
+
await voyager_app(scope, receive, send)
|
|
177
|
+
else:
|
|
178
|
+
django_app = get_asgi_application()
|
|
179
|
+
await django_app(scope, receive, send)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Start with:
|
|
183
|
+
```bash
|
|
184
|
+
uvicorn your_app:application --reload
|
|
185
|
+
# Visit http://localhost:8000/voyager
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Litestar
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from litestar import Litestar, get
|
|
192
|
+
from fastapi_voyager import create_voyager
|
|
193
|
+
|
|
194
|
+
# Create Litestar app
|
|
195
|
+
app = Litestar()
|
|
196
|
+
|
|
197
|
+
@get("/hello")
|
|
198
|
+
def hello() -> dict:
|
|
199
|
+
return {"message": "Hello World"}
|
|
200
|
+
|
|
201
|
+
# Create voyager app (returns a Litestar app)
|
|
202
|
+
voyager_app = create_voyager(app)
|
|
203
|
+
|
|
204
|
+
# Create ASGI application that routes between main app and voyager
|
|
205
|
+
async def asgi_app(scope, receive, send):
|
|
206
|
+
if scope["type"] == "http" and scope["path"].startswith("/voyager"):
|
|
207
|
+
# Remove /voyager prefix for voyager app
|
|
208
|
+
new_scope = dict(scope)
|
|
209
|
+
new_scope["path"] = scope["path"][8:] or "/"
|
|
210
|
+
await voyager_app(new_scope, receive, send)
|
|
211
|
+
else:
|
|
212
|
+
await app(scope, receive, send)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Start with:
|
|
216
|
+
```bash
|
|
217
|
+
uvicorn your_app:asgi_app --reload
|
|
218
|
+
# Visit http://localhost:8000/voyager
|
|
219
|
+
```
|
|
220
|
+
|
|
113
221
|
## Features
|
|
114
222
|
|
|
115
|
-
fastapi-voyager is designed for scenarios using
|
|
223
|
+
fastapi-voyager is designed for scenarios using web frameworks with Pydantic models (FastAPI, Django Ninja, Litestar). It helps visualize dependencies and serves as an architecture tool to identify implementation issues such as wrong relationships, overfetching, and more.
|
|
116
224
|
|
|
117
225
|
**Best Practice**: When building view models following the ER model pattern, fastapi-voyager can fully realize its potential - quickly identifying which APIs use specific entities and vice versa.
|
|
118
226
|
|
|
@@ -258,6 +366,21 @@ uv pip install ".[dev]"
|
|
|
258
366
|
uvicorn tests.programatic:app --reload
|
|
259
367
|
```
|
|
260
368
|
|
|
369
|
+
### Test Different Frameworks
|
|
370
|
+
|
|
371
|
+
You can test the framework-specific examples:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# FastAPI example
|
|
375
|
+
uvicorn tests.fastapi.embedding:app --reload
|
|
376
|
+
|
|
377
|
+
# Django Ninja example
|
|
378
|
+
uvicorn tests.django_ninja.embedding:app --reload
|
|
379
|
+
|
|
380
|
+
# Litestar example
|
|
381
|
+
uvicorn tests.litestar.embedding:asgi_app --reload
|
|
382
|
+
```
|
|
383
|
+
|
|
261
384
|
Visit `http://localhost:8000/voyager` to see changes.
|
|
262
385
|
|
|
263
386
|
### Setup Git Hooks (Optional)
|
|
@@ -289,9 +412,12 @@ This will run Prettier automatically before each commit. See [`.githooks/README.
|
|
|
289
412
|
|
|
290
413
|
## Dependencies
|
|
291
414
|
|
|
292
|
-
- [FastAPI](https://fastapi.tiangolo.com/)
|
|
293
415
|
- [pydantic-resolve](https://github.com/allmonday/pydantic-resolve)
|
|
294
416
|
- [Quasar Framework](https://quasar.dev/)
|
|
417
|
+
### Dev dependencies
|
|
418
|
+
- [FastAPI](https://fastapi.tiangolo.com/)
|
|
419
|
+
- [Django Ninja](https://django-ninja.rest-framework.com/)
|
|
420
|
+
- [Litestar](https://litestar.dev/)
|
|
295
421
|
|
|
296
422
|
## Credits
|
|
297
423
|
|
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
fastapi_voyager/__init__.py,sha256=
|
|
1
|
+
fastapi_voyager/__init__.py,sha256=1IdDy6JUMgQqQo33qUe2znX9YeI7S35pjVrbt0QLOzY,228
|
|
2
2
|
fastapi_voyager/cli.py,sha256=td3yIIigEomhSdDO-Xkh-CgpEwCafwlwnpvxnT9QsBo,10488
|
|
3
|
-
fastapi_voyager/er_diagram.py,sha256=
|
|
3
|
+
fastapi_voyager/er_diagram.py,sha256=4Ba5u-T7XmVmk9MltVMe-m-18mUAHMTybTdE-zNZLrU,7860
|
|
4
4
|
fastapi_voyager/filter.py,sha256=AN_HIu8-DtKisIq5mFt7CnqRHtxKewedNGyyaI82hSY,11529
|
|
5
5
|
fastapi_voyager/module.py,sha256=h9YR3BpS-CAcJW9WCdVkF4opqwY32w9T67g9GfdLytk,3425
|
|
6
|
-
fastapi_voyager/pydantic_resolve_util.py,sha256=
|
|
7
|
-
fastapi_voyager/render.py,sha256=
|
|
8
|
-
fastapi_voyager/render_style.py,sha256=
|
|
9
|
-
fastapi_voyager/server.py,sha256=
|
|
6
|
+
fastapi_voyager/pydantic_resolve_util.py,sha256=0UfAp6Yi6FNpsI1bUu89hRVWFy6keBu1KtcZl-6NYso,3526
|
|
7
|
+
fastapi_voyager/render.py,sha256=A1jFDraQFOfnFHguYlsvBbGIDJ527VQH0jZ-xgTjqIk,17270
|
|
8
|
+
fastapi_voyager/render_style.py,sha256=1y3aRhBSJSWU-JuSgjn9il_xFEqjv6mJCoUzImLQT6M,2525
|
|
9
|
+
fastapi_voyager/server.py,sha256=sgUUscbt736VNB6CN2J_6rJ6fpWO4vJFW7oP7FmAjvA,6739
|
|
10
10
|
fastapi_voyager/type.py,sha256=zluWvh5vpnjXJ9aAmyNJTSmXZPjAHCvgRT5oQRAjHrg,2104
|
|
11
|
-
fastapi_voyager/type_helper.py,sha256=
|
|
12
|
-
fastapi_voyager/version.py,sha256=
|
|
13
|
-
fastapi_voyager/voyager.py,sha256=
|
|
11
|
+
fastapi_voyager/type_helper.py,sha256=5HYUHdghTISZ44NvVsrMWlRGD5C9-xrGeNKuLYDMA6s,10209
|
|
12
|
+
fastapi_voyager/version.py,sha256=0KZ1gUzWpAnbQTYDMdcLc-qEsyVlYlG265SEa7S4Ij4,56
|
|
13
|
+
fastapi_voyager/voyager.py,sha256=S0cCMLFv_wPfEMVdp4cYqbc-Y207SjCTSmsYdqzIDHg,15307
|
|
14
|
+
fastapi_voyager/adapters/__init__.py,sha256=a95rBvV4QVcX_yAzjuJQKOW-EbJ79R--YjUJ3BGkC3k,517
|
|
15
|
+
fastapi_voyager/adapters/base.py,sha256=m-E74LlNgYCpj-gqfYsKl2mzWW5iFiaDTjiBri_5cfo,1283
|
|
16
|
+
fastapi_voyager/adapters/common.py,sha256=DXVLsjLn65U3RR7YyKL_ELV74mnINWmZ4hQ6zISdV0E,9684
|
|
17
|
+
fastapi_voyager/adapters/django_ninja_adapter.py,sha256=tl1rMcotAhOwBxT8poG8rTXm1v3wn_cpaoda79NEFxE,11424
|
|
18
|
+
fastapi_voyager/adapters/fastapi_adapter.py,sha256=-VvCXdMRsV_zZzEkplOGK8HjQ9ICoTyapG3ZluQ1Nvk,5870
|
|
19
|
+
fastapi_voyager/adapters/litestar_adapter.py,sha256=-ILD8jIb82_JKV87p4IfV9IXvhUJ6RSic6i_iHapo5w,6812
|
|
20
|
+
fastapi_voyager/introspectors/__init__.py,sha256=HbmoUyM-BSwd4Rg2ptd9u-qvZSD3UykKyHYVoRg03OM,917
|
|
21
|
+
fastapi_voyager/introspectors/base.py,sha256=hMfka9gVXr-E8MA1rKSSmYk0OppqgiFPWavfgAkPmQI,2131
|
|
22
|
+
fastapi_voyager/introspectors/detector.py,sha256=rmlpQARJMrFXPty6OUdjmFRTtBYErGDH4l7Tivnu_FQ,3910
|
|
23
|
+
fastapi_voyager/introspectors/django_ninja.py,sha256=Ytneh_kpsakTo_Njv1e4nvqfErjT1PrYelLZ-8xX5Gg,4052
|
|
24
|
+
fastapi_voyager/introspectors/fastapi.py,sha256=SyWGKAH8cM3CENY-Uu3YY6e8kFM5BE-jEBBZLoIF--A,2696
|
|
25
|
+
fastapi_voyager/introspectors/litestar.py,sha256=QXnaT0-hCa_0sByKJoUWuu0vIzRpCCKLokCBDTtv_s4,6100
|
|
14
26
|
fastapi_voyager/templates/dot/cluster.j2,sha256=I2z9KkfCzmAtqXe0gXBnxnOfBXUSpdlATs3uf-O8_B8,307
|
|
15
27
|
fastapi_voyager/templates/dot/cluster_container.j2,sha256=2tH1mOJvPoVKE_aHVMR3t06TfH_dYa9OeH6DBqSHt_A,204
|
|
16
28
|
fastapi_voyager/templates/dot/digraph.j2,sha256=wZuiO-vvZ-AJ1FcMQG4BLevUyxk6yA-yEpUa3Us05mE,435
|
|
@@ -27,11 +39,11 @@ fastapi_voyager/templates/html/schema_table.j2,sha256=rzphiGk1il7uv4Gr2p_HLPHqyL
|
|
|
27
39
|
fastapi_voyager/web/graph-ui.js,sha256=9b2auyGWEpUcF65YV231GNojQ9Uk6FsT1SlRR3lSYnc,7664
|
|
28
40
|
fastapi_voyager/web/graphviz.svg.css,sha256=K218ov_mdSe3ga4KwhiBB92ynVvm5zaAk9_D9a3d8hE,1546
|
|
29
41
|
fastapi_voyager/web/graphviz.svg.js,sha256=VokgCghvP4zm3SFiFVPIikdW6XzjkZJXQkBbCrEitug,19885
|
|
30
|
-
fastapi_voyager/web/index.html,sha256=
|
|
42
|
+
fastapi_voyager/web/index.html,sha256=4HGVavdd15u_BA7BwC-RJEeKal6hcYbiQaLcoOANQHI,23485
|
|
31
43
|
fastapi_voyager/web/quasar.min.css,sha256=F5jQe7X2XT54VlvAaa2V3GsBFdVD-vxDZeaPLf6U9CU,203145
|
|
32
44
|
fastapi_voyager/web/quasar.min.js,sha256=h0ftyPMW_CRiyzeVfQqiup0vrVt4_QWojpqmpnpn07E,502974
|
|
33
|
-
fastapi_voyager/web/store.js,sha256=
|
|
34
|
-
fastapi_voyager/web/vue-main.js,sha256=
|
|
45
|
+
fastapi_voyager/web/store.js,sha256=CKQz5tZtv6W70TDVqeQiOt2gUXzD0WIc0ufCa0dB8sU,15193
|
|
46
|
+
fastapi_voyager/web/vue-main.js,sha256=MedNrfgy2KJCFiyv8sczC8d8M2yytZDX58PQGvjUYdM,10825
|
|
35
47
|
fastapi_voyager/web/component/demo.js,sha256=sAklFGhKGmMy9-ofgOw2oPIidAoIOgHu6yvV51L_MAA,350
|
|
36
48
|
fastapi_voyager/web/component/render-graph.js,sha256=9wnO70n3eyPKTpa744idgs5PSwgvzbfv4InZ68eEOKs,2454
|
|
37
49
|
fastapi_voyager/web/component/route-code-display.js,sha256=a823nBz3EEjutW2pfi73rcF3hodCBmgYNmuZi94sXE4,3615
|
|
@@ -43,8 +55,8 @@ fastapi_voyager/web/icon/favicon-16x16.png,sha256=JC07jEzfIYxBIoQn_FHXvyHuxESdhW
|
|
|
43
55
|
fastapi_voyager/web/icon/favicon-32x32.png,sha256=C7v1h58cfWOsiLp9yOIZtlx-dLasBcq3NqpHVGRmpt4,1859
|
|
44
56
|
fastapi_voyager/web/icon/favicon.ico,sha256=tZolYIXkkBcFiYl1A8ksaXN2VjGamzcSdes838dLvNc,15406
|
|
45
57
|
fastapi_voyager/web/icon/site.webmanifest,sha256=GRozZ5suTykYcPMap1QhjrAB8PLW0mbT_phhzw_utvQ,316
|
|
46
|
-
fastapi_voyager-0.
|
|
47
|
-
fastapi_voyager-0.
|
|
48
|
-
fastapi_voyager-0.
|
|
49
|
-
fastapi_voyager-0.
|
|
50
|
-
fastapi_voyager-0.
|
|
58
|
+
fastapi_voyager-0.16.0a1.dist-info/METADATA,sha256=pGOq7n96lV1BKAXvCKPXUU8BvXN89HzgAMI90dmnH_Y,12943
|
|
59
|
+
fastapi_voyager-0.16.0a1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
60
|
+
fastapi_voyager-0.16.0a1.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
|
|
61
|
+
fastapi_voyager-0.16.0a1.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
|
|
62
|
+
fastapi_voyager-0.16.0a1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|