fastapi-voyager 0.16.0a1__py3-none-any.whl → 0.16.0a3__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/adapters/base.py +0 -10
- fastapi_voyager/adapters/django_ninja_adapter.py +12 -11
- fastapi_voyager/adapters/fastapi_adapter.py +10 -9
- fastapi_voyager/adapters/litestar_adapter.py +7 -8
- fastapi_voyager/cli.py +108 -56
- fastapi_voyager/introspectors/fastapi.py +15 -4
- fastapi_voyager/server.py +7 -0
- fastapi_voyager/version.py +1 -1
- {fastapi_voyager-0.16.0a1.dist-info → fastapi_voyager-0.16.0a3.dist-info}/METADATA +31 -7
- {fastapi_voyager-0.16.0a1.dist-info → fastapi_voyager-0.16.0a3.dist-info}/RECORD +13 -13
- {fastapi_voyager-0.16.0a1.dist-info → fastapi_voyager-0.16.0a3.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.16.0a1.dist-info → fastapi_voyager-0.16.0a3.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.16.0a1.dist-info → fastapi_voyager-0.16.0a3.dist-info}/licenses/LICENSE +0 -0
fastapi_voyager/adapters/base.py
CHANGED
|
@@ -32,13 +32,3 @@ class VoyagerAdapter(ABC):
|
|
|
32
32
|
A framework-specific application object
|
|
33
33
|
"""
|
|
34
34
|
pass
|
|
35
|
-
|
|
36
|
-
@abstractmethod
|
|
37
|
-
def get_mount_path(self) -> str:
|
|
38
|
-
"""
|
|
39
|
-
Get the recommended mount path for the voyager UI.
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
The path where voyager should be mounted (e.g., "/voyager")
|
|
43
|
-
"""
|
|
44
|
-
pass
|
|
@@ -32,6 +32,7 @@ class DjangoNinjaAdapter(VoyagerAdapter):
|
|
|
32
32
|
ga_id: str | None = None,
|
|
33
33
|
er_diagram: Any = None,
|
|
34
34
|
enable_pydantic_resolve_meta: bool = False,
|
|
35
|
+
server_mode: bool = False,
|
|
35
36
|
):
|
|
36
37
|
self.ctx = VoyagerContext(
|
|
37
38
|
target_app=target_app,
|
|
@@ -45,6 +46,7 @@ class DjangoNinjaAdapter(VoyagerAdapter):
|
|
|
45
46
|
enable_pydantic_resolve_meta=enable_pydantic_resolve_meta,
|
|
46
47
|
framework_name="Django Ninja",
|
|
47
48
|
)
|
|
49
|
+
self.server_mode = server_mode
|
|
48
50
|
# Note: gzip should be handled by Django's middleware, not here
|
|
49
51
|
|
|
50
52
|
async def _handle_request(self, scope, receive, send):
|
|
@@ -55,8 +57,8 @@ class DjangoNinjaAdapter(VoyagerAdapter):
|
|
|
55
57
|
# Parse the request
|
|
56
58
|
method = scope["method"]
|
|
57
59
|
path = scope["path"]
|
|
58
|
-
# Remove /voyager prefix for internal routing
|
|
59
|
-
if path.startswith("/voyager"):
|
|
60
|
+
# Remove /voyager prefix for internal routing (unless in server_mode)
|
|
61
|
+
if not self.server_mode and path.startswith("/voyager"):
|
|
60
62
|
path = path[8:] # Remove '/voyager'
|
|
61
63
|
if path == "":
|
|
62
64
|
path = "/"
|
|
@@ -284,16 +286,15 @@ class DjangoNinjaAdapter(VoyagerAdapter):
|
|
|
284
286
|
"""Create and return an ASGI application."""
|
|
285
287
|
|
|
286
288
|
async def asgi_app(scope, receive, send):
|
|
287
|
-
#
|
|
288
|
-
if scope["type"] == "http"
|
|
289
|
-
|
|
289
|
+
# In server_mode, handle all paths; otherwise only handle /voyager/*
|
|
290
|
+
if scope["type"] == "http":
|
|
291
|
+
if self.server_mode or scope["path"].startswith("/voyager"):
|
|
292
|
+
await self._handle_request(scope, receive, send)
|
|
293
|
+
else:
|
|
294
|
+
# Return 404 for non-voyager paths
|
|
295
|
+
# (Django should handle these before they reach here)
|
|
296
|
+
await self._send_404(send)
|
|
290
297
|
else:
|
|
291
|
-
# Return 404 for non-voyager paths
|
|
292
|
-
# (Django should handle these before they reach here)
|
|
293
298
|
await self._send_404(send)
|
|
294
299
|
|
|
295
300
|
return asgi_app
|
|
296
|
-
|
|
297
|
-
def get_mount_path(self) -> str:
|
|
298
|
-
"""Get the recommended mount path for voyager."""
|
|
299
|
-
return "/voyager"
|
|
@@ -5,11 +5,7 @@ This module provides the FastAPI-specific implementation of the voyager server.
|
|
|
5
5
|
"""
|
|
6
6
|
from typing import Any, Literal
|
|
7
7
|
|
|
8
|
-
from fastapi import APIRouter, FastAPI
|
|
9
|
-
from fastapi.responses import HTMLResponse, JSONResponse, PlainTextResponse
|
|
10
|
-
from fastapi.staticfiles import StaticFiles
|
|
11
8
|
from pydantic import BaseModel
|
|
12
|
-
from starlette.middleware.gzip import GZipMiddleware
|
|
13
9
|
|
|
14
10
|
from fastapi_voyager.adapters.base import VoyagerAdapter
|
|
15
11
|
from fastapi_voyager.adapters.common import STATIC_FILES_PATH, VoyagerContext
|
|
@@ -83,6 +79,7 @@ class FastAPIAdapter(VoyagerAdapter):
|
|
|
83
79
|
ga_id: str | None = None,
|
|
84
80
|
er_diagram: Any = None,
|
|
85
81
|
enable_pydantic_resolve_meta: bool = False,
|
|
82
|
+
server_mode: bool = False,
|
|
86
83
|
):
|
|
87
84
|
self.ctx = VoyagerContext(
|
|
88
85
|
target_app=target_app,
|
|
@@ -97,9 +94,17 @@ class FastAPIAdapter(VoyagerAdapter):
|
|
|
97
94
|
framework_name="FastAPI",
|
|
98
95
|
)
|
|
99
96
|
self.gzip_minimum_size = gzip_minimum_size
|
|
97
|
+
# Note: server_mode is accepted for API consistency but not used
|
|
98
|
+
# since FastAPI apps are always standalone with routes at /
|
|
100
99
|
|
|
101
|
-
def create_app(self) ->
|
|
100
|
+
def create_app(self) -> Any:
|
|
102
101
|
"""Create and return a FastAPI application with voyager endpoints."""
|
|
102
|
+
# Lazy import FastAPI to avoid import errors when framework is not installed
|
|
103
|
+
from fastapi import APIRouter, FastAPI
|
|
104
|
+
from fastapi.responses import HTMLResponse, JSONResponse, PlainTextResponse
|
|
105
|
+
from fastapi.staticfiles import StaticFiles
|
|
106
|
+
from starlette.middleware.gzip import GZipMiddleware
|
|
107
|
+
|
|
103
108
|
router = APIRouter(tags=["fastapi-voyager"])
|
|
104
109
|
|
|
105
110
|
@router.post("/er-diagram", response_class=PlainTextResponse)
|
|
@@ -159,7 +164,3 @@ class FastAPIAdapter(VoyagerAdapter):
|
|
|
159
164
|
app.include_router(router)
|
|
160
165
|
|
|
161
166
|
return app
|
|
162
|
-
|
|
163
|
-
def get_mount_path(self) -> str:
|
|
164
|
-
"""Get the recommended mount path for voyager."""
|
|
165
|
-
return "/voyager"
|
|
@@ -5,9 +5,6 @@ This module provides the Litestar-specific implementation of the voyager server.
|
|
|
5
5
|
"""
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from litestar import Litestar, MediaType, Request, Response, get, post
|
|
9
|
-
from litestar.static_files import create_static_files_router
|
|
10
|
-
|
|
11
8
|
from fastapi_voyager.adapters.base import VoyagerAdapter
|
|
12
9
|
from fastapi_voyager.adapters.common import STATIC_FILES_PATH, WEB_DIR, VoyagerContext
|
|
13
10
|
from fastapi_voyager.type import CoreData, SchemaNode, Tag
|
|
@@ -32,6 +29,7 @@ class LitestarAdapter(VoyagerAdapter):
|
|
|
32
29
|
ga_id: str | None = None,
|
|
33
30
|
er_diagram: Any = None,
|
|
34
31
|
enable_pydantic_resolve_meta: bool = False,
|
|
32
|
+
server_mode: bool = False,
|
|
35
33
|
):
|
|
36
34
|
self.ctx = VoyagerContext(
|
|
37
35
|
target_app=target_app,
|
|
@@ -46,9 +44,14 @@ class LitestarAdapter(VoyagerAdapter):
|
|
|
46
44
|
framework_name="Litestar",
|
|
47
45
|
)
|
|
48
46
|
self.gzip_minimum_size = gzip_minimum_size
|
|
47
|
+
# Note: server_mode is accepted for API consistency but not used
|
|
48
|
+
# since Litestar apps are always standalone with routes at /
|
|
49
49
|
|
|
50
|
-
def create_app(self) ->
|
|
50
|
+
def create_app(self) -> Any:
|
|
51
51
|
"""Create and return a Litestar application with voyager endpoints."""
|
|
52
|
+
# Lazy import Litestar to avoid import errors when framework is not installed
|
|
53
|
+
from litestar import Litestar, MediaType, Request, Response, get, post
|
|
54
|
+
from litestar.static_files import create_static_files_router
|
|
52
55
|
|
|
53
56
|
@get("/er-diagram")
|
|
54
57
|
async def get_er_diagram(request: Request) -> str:
|
|
@@ -182,7 +185,3 @@ class LitestarAdapter(VoyagerAdapter):
|
|
|
182
185
|
for f in schema.fields
|
|
183
186
|
],
|
|
184
187
|
}
|
|
185
|
-
|
|
186
|
-
def get_mount_path(self) -> str:
|
|
187
|
-
"""Get the recommended mount path for voyager."""
|
|
188
|
-
return "/voyager"
|
fastapi_voyager/cli.py
CHANGED
|
@@ -5,8 +5,7 @@ import importlib.util
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
|
-
|
|
9
|
-
from fastapi import FastAPI
|
|
8
|
+
from typing import Any
|
|
10
9
|
|
|
11
10
|
from fastapi_voyager import server as viz_server
|
|
12
11
|
from fastapi_voyager.version import __version__
|
|
@@ -14,41 +13,49 @@ from fastapi_voyager.voyager import Voyager
|
|
|
14
13
|
|
|
15
14
|
logger = logging.getLogger(__name__)
|
|
16
15
|
|
|
16
|
+
# Framework type constants
|
|
17
|
+
SUPPORTED_FRAMEWORKS = ["fastapi", "litestar", "django-ninja"]
|
|
18
|
+
|
|
17
19
|
|
|
18
|
-
def
|
|
19
|
-
"""Load
|
|
20
|
+
def load_app_from_file(module_path: str, app_name: str = "app", framework: str | None = None) -> Any:
|
|
21
|
+
"""Load web framework app from a Python module file."""
|
|
20
22
|
try:
|
|
21
23
|
# Convert relative path to absolute path
|
|
22
24
|
if not os.path.isabs(module_path):
|
|
23
25
|
module_path = os.path.abspath(module_path)
|
|
24
|
-
|
|
26
|
+
|
|
25
27
|
# Load the module
|
|
26
28
|
spec = importlib.util.spec_from_file_location("app_module", module_path)
|
|
27
29
|
if spec is None or spec.loader is None:
|
|
28
30
|
logger.error(f"Could not load module from {module_path}")
|
|
29
31
|
return None
|
|
30
|
-
|
|
32
|
+
|
|
31
33
|
module = importlib.util.module_from_spec(spec)
|
|
32
34
|
sys.modules["app_module"] = module
|
|
33
35
|
spec.loader.exec_module(module)
|
|
34
|
-
|
|
35
|
-
# Get the
|
|
36
|
-
if hasattr(module, app_name):
|
|
37
|
-
|
|
38
|
-
if isinstance(app, FastAPI):
|
|
39
|
-
return app
|
|
40
|
-
logger.error(f"'{app_name}' is not a FastAPI instance")
|
|
36
|
+
|
|
37
|
+
# Get the app instance
|
|
38
|
+
if not hasattr(module, app_name):
|
|
39
|
+
logger.error(f"No attribute '{app_name}' found in the module")
|
|
41
40
|
return None
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
|
|
42
|
+
app = getattr(module, app_name)
|
|
43
|
+
|
|
44
|
+
# Verify app type if framework is specified
|
|
45
|
+
if framework is not None:
|
|
46
|
+
if not _validate_app_framework(app, framework):
|
|
47
|
+
logger.error(f"'{app_name}' is not a {framework} instance")
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
return app
|
|
51
|
+
|
|
45
52
|
except Exception as e:
|
|
46
|
-
logger.error(f"Error loading
|
|
53
|
+
logger.error(f"Error loading app: {e}")
|
|
47
54
|
return None
|
|
48
55
|
|
|
49
56
|
|
|
50
|
-
def
|
|
51
|
-
"""Load
|
|
57
|
+
def load_app_from_module(module_name: str, app_name: str = "app", framework: str | None = None) -> Any:
|
|
58
|
+
"""Load web framework app from a Python module name."""
|
|
52
59
|
try:
|
|
53
60
|
# Temporarily add the current working directory to sys.path
|
|
54
61
|
current_dir = os.getcwd()
|
|
@@ -57,35 +64,62 @@ def load_fastapi_app_from_module(module_name: str, app_name: str = "app") -> Fas
|
|
|
57
64
|
path_added = True
|
|
58
65
|
else:
|
|
59
66
|
path_added = False
|
|
60
|
-
|
|
67
|
+
|
|
61
68
|
try:
|
|
62
69
|
# Import the module by name
|
|
63
70
|
module = importlib.import_module(module_name)
|
|
64
|
-
|
|
65
|
-
# Get the
|
|
66
|
-
if hasattr(module, app_name):
|
|
67
|
-
|
|
68
|
-
if isinstance(app, FastAPI):
|
|
69
|
-
return app
|
|
70
|
-
logger.error(f"'{app_name}' is not a FastAPI instance")
|
|
71
|
+
|
|
72
|
+
# Get the app instance
|
|
73
|
+
if not hasattr(module, app_name):
|
|
74
|
+
logger.error(f"No attribute '{app_name}' found in module '{module_name}'")
|
|
71
75
|
return None
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
|
|
77
|
+
app = getattr(module, app_name)
|
|
78
|
+
|
|
79
|
+
# Verify app type if framework is specified
|
|
80
|
+
if framework is not None:
|
|
81
|
+
if not _validate_app_framework(app, framework):
|
|
82
|
+
logger.error(f"'{app_name}' is not a {framework} instance")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
return app
|
|
74
86
|
finally:
|
|
75
87
|
# Cleanup: if we added the path, remove it
|
|
76
88
|
if path_added and current_dir in sys.path:
|
|
77
89
|
sys.path.remove(current_dir)
|
|
78
|
-
|
|
90
|
+
|
|
79
91
|
except ImportError as e:
|
|
80
92
|
logger.error(f"Could not import module '{module_name}': {e}")
|
|
81
93
|
return None
|
|
82
94
|
except Exception as e:
|
|
83
|
-
logger.error(f"Error loading
|
|
95
|
+
logger.error(f"Error loading app from module '{module_name}': {e}")
|
|
84
96
|
return None
|
|
85
97
|
|
|
86
98
|
|
|
99
|
+
def _validate_app_framework(app: Any, framework: str) -> bool:
|
|
100
|
+
"""Validate that the app matches the expected framework type."""
|
|
101
|
+
try:
|
|
102
|
+
if framework == "fastapi":
|
|
103
|
+
from fastapi import FastAPI
|
|
104
|
+
return isinstance(app, FastAPI)
|
|
105
|
+
elif framework == "litestar":
|
|
106
|
+
from litestar import Litestar
|
|
107
|
+
return isinstance(app, Litestar)
|
|
108
|
+
elif framework == "django-ninja":
|
|
109
|
+
from ninja import NinjaAPI
|
|
110
|
+
return isinstance(app, NinjaAPI)
|
|
111
|
+
return False
|
|
112
|
+
except ImportError as e:
|
|
113
|
+
logger.error(
|
|
114
|
+
f"The {framework} package is not installed. "
|
|
115
|
+
f"Install it with: uv add fastapi-voyager[{framework}]"
|
|
116
|
+
)
|
|
117
|
+
logger.debug(f"Import error details: {e}")
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
87
121
|
def generate_visualization(
|
|
88
|
-
app:
|
|
122
|
+
app: Any,
|
|
89
123
|
output_file: str = "router_viz.dot", tags: list[str] | None = None,
|
|
90
124
|
schema: str | None = None,
|
|
91
125
|
show_fields: bool = False,
|
|
@@ -93,7 +127,7 @@ def generate_visualization(
|
|
|
93
127
|
route_name: str | None = None,
|
|
94
128
|
):
|
|
95
129
|
|
|
96
|
-
"""Generate DOT file for
|
|
130
|
+
"""Generate DOT file for API router visualization."""
|
|
97
131
|
analytics = Voyager(
|
|
98
132
|
include_tags=tags,
|
|
99
133
|
schema=schema,
|
|
@@ -117,39 +151,46 @@ def generate_visualization(
|
|
|
117
151
|
def main():
|
|
118
152
|
"""Main CLI entry point."""
|
|
119
153
|
parser = argparse.ArgumentParser(
|
|
120
|
-
description="Visualize
|
|
154
|
+
description="Visualize web application's routing tree and dependencies (supports FastAPI, Litestar, Django-Ninja)",
|
|
121
155
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
122
156
|
epilog="""
|
|
123
157
|
Examples:
|
|
124
|
-
voyager app.py
|
|
125
|
-
voyager
|
|
126
|
-
voyager -m tests.demo --
|
|
127
|
-
voyager -m tests.demo --
|
|
128
|
-
voyager -m tests.demo --
|
|
129
|
-
voyager -m tests.demo --
|
|
130
|
-
voyager -m tests.demo
|
|
131
|
-
voyager -m tests.demo --
|
|
132
|
-
voyager -m tests.demo --
|
|
158
|
+
voyager app.py --web fastapi # Load 'app' from app.py (FastAPI)
|
|
159
|
+
voyager app.py --web litestar # Load 'app' from app.py (Litestar)
|
|
160
|
+
voyager -m tests.demo --web django-ninja # Load 'app' from demo module (Django-Ninja)
|
|
161
|
+
voyager -m tests.demo --app=api --web fastapi # Load 'api' from tests.demo
|
|
162
|
+
voyager -m tests.demo --web fastapi --schema=NodeA # filter nodes by schema name
|
|
163
|
+
voyager -m tests.demo --web fastapi --tags=page restful # filter routes by tags
|
|
164
|
+
voyager -m tests.demo --web fastapi --module_color=tests.demo:red --module_color=tests.service:yellow
|
|
165
|
+
voyager -m tests.demo --web fastapi -o my_graph.dot # Output to my_graph.dot
|
|
166
|
+
voyager -m tests.demo --web fastapi --server # start a local server to preview
|
|
167
|
+
voyager -m tests.demo --web fastapi --server --port=8001 # start a local server to preview
|
|
133
168
|
"""
|
|
134
169
|
)
|
|
135
|
-
|
|
170
|
+
|
|
136
171
|
# Create mutually exclusive group for module loading options
|
|
137
172
|
group = parser.add_mutually_exclusive_group(required=False)
|
|
138
173
|
group.add_argument(
|
|
139
174
|
"module",
|
|
140
175
|
nargs="?",
|
|
141
|
-
help="Python file containing the
|
|
176
|
+
help="Python file containing the web application"
|
|
142
177
|
)
|
|
143
178
|
group.add_argument(
|
|
144
179
|
"-m", "--module",
|
|
145
180
|
dest="module_name",
|
|
146
|
-
help="Python module name containing the
|
|
181
|
+
help="Python module name containing the web application (like python -m)"
|
|
147
182
|
)
|
|
148
|
-
|
|
183
|
+
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--web",
|
|
186
|
+
choices=SUPPORTED_FRAMEWORKS,
|
|
187
|
+
help="Web framework type (required when using --server): fastapi, litestar, django-ninja"
|
|
188
|
+
)
|
|
189
|
+
|
|
149
190
|
parser.add_argument(
|
|
150
191
|
"--app", "-a",
|
|
151
192
|
default="app",
|
|
152
|
-
help="Name of the
|
|
193
|
+
help="Name of the app variable (default: app)"
|
|
153
194
|
)
|
|
154
195
|
|
|
155
196
|
parser.add_argument(
|
|
@@ -223,26 +264,34 @@ Examples:
|
|
|
223
264
|
)
|
|
224
265
|
|
|
225
266
|
args = parser.parse_args()
|
|
226
|
-
|
|
267
|
+
|
|
268
|
+
# Validate arguments
|
|
227
269
|
if args.module_prefix and not args.server:
|
|
228
270
|
parser.error("--module_prefix can only be used together with --server")
|
|
229
271
|
|
|
230
272
|
if not (args.module_name or args.module):
|
|
231
|
-
parser.error("You must provide a module file
|
|
273
|
+
parser.error("You must provide a module file or -m module name")
|
|
274
|
+
|
|
275
|
+
# When --server is used, --web is required
|
|
276
|
+
if args.server and not args.web:
|
|
277
|
+
parser.error("--web is required when using --server. Please specify: fastapi, litestar, or django-ninja")
|
|
278
|
+
|
|
279
|
+
# Determine the framework (default to the one specified, or None for non-server mode)
|
|
280
|
+
framework = args.web if args.server else None
|
|
232
281
|
|
|
233
282
|
# Configure logging based on --log-level
|
|
234
283
|
level_name = (args.log_level or "INFO").upper()
|
|
235
284
|
logging.basicConfig(level=level_name)
|
|
236
285
|
|
|
237
|
-
# Load
|
|
286
|
+
# Load app based on the input method (module_name takes precedence)
|
|
238
287
|
if args.module_name:
|
|
239
|
-
app =
|
|
288
|
+
app = load_app_from_module(args.module_name, args.app, framework)
|
|
240
289
|
else:
|
|
241
290
|
if not os.path.exists(args.module):
|
|
242
291
|
logger.error(f"File '{args.module}' not found")
|
|
243
292
|
sys.exit(1)
|
|
244
|
-
app =
|
|
245
|
-
|
|
293
|
+
app = load_app_from_file(args.module, args.app, framework)
|
|
294
|
+
|
|
246
295
|
if app is None:
|
|
247
296
|
sys.exit(1)
|
|
248
297
|
|
|
@@ -263,18 +312,21 @@ Examples:
|
|
|
263
312
|
try:
|
|
264
313
|
module_color = parse_kv_pairs(args.module_color)
|
|
265
314
|
if args.server:
|
|
266
|
-
# Build a preview server
|
|
315
|
+
# Build a preview server using the appropriate framework
|
|
267
316
|
try:
|
|
268
317
|
import uvicorn
|
|
269
318
|
except ImportError:
|
|
270
319
|
logger.info("uvicorn is required to run the server. Install via 'pip install uvicorn' or 'uv add uvicorn'.")
|
|
271
320
|
sys.exit(1)
|
|
321
|
+
|
|
322
|
+
# Create voyager app - it auto-detects framework and returns appropriate app type
|
|
272
323
|
app_server = viz_server.create_voyager(
|
|
273
324
|
app,
|
|
274
325
|
module_color=module_color,
|
|
275
326
|
module_prefix=args.module_prefix,
|
|
327
|
+
server_mode=True, # Enable server mode to serve at root path
|
|
276
328
|
)
|
|
277
|
-
logger.info(f"Starting preview server at http://{args.host}:{args.port} ... (Ctrl+C to stop)")
|
|
329
|
+
logger.info(f"Starting {args.web} preview server at http://{args.host}:{args.port} ... (Ctrl+C to stop)")
|
|
278
330
|
uvicorn.run(app_server, host=args.host, port=args.port, log_level=level_name.lower())
|
|
279
331
|
else:
|
|
280
332
|
# Generate and write dot file locally
|
|
@@ -4,11 +4,13 @@ FastAPI implementation of the AppIntrospector interface.
|
|
|
4
4
|
This module provides the adapter that allows fastapi-voyager to work with FastAPI applications.
|
|
5
5
|
"""
|
|
6
6
|
from collections.abc import Iterator
|
|
7
|
-
|
|
8
|
-
from fastapi import FastAPI, routing
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
9
8
|
|
|
10
9
|
from fastapi_voyager.introspectors.base import AppIntrospector, RouteInfo
|
|
11
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from fastapi import FastAPI
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class FastAPIIntrospector(AppIntrospector):
|
|
14
16
|
"""
|
|
@@ -18,7 +20,7 @@ class FastAPIIntrospector(AppIntrospector):
|
|
|
18
20
|
and converts it to the framework-agnostic RouteInfo format.
|
|
19
21
|
"""
|
|
20
22
|
|
|
21
|
-
def __init__(self, app: FastAPI, swagger_url: str | None = None):
|
|
23
|
+
def __init__(self, app: "FastAPI", swagger_url: str | None = None):
|
|
22
24
|
"""
|
|
23
25
|
Initialize the FastAPI introspector.
|
|
24
26
|
|
|
@@ -26,6 +28,12 @@ class FastAPIIntrospector(AppIntrospector):
|
|
|
26
28
|
app: The FastAPI application instance
|
|
27
29
|
swagger_url: Optional custom URL to Swagger documentation
|
|
28
30
|
"""
|
|
31
|
+
# Lazy import to avoid import errors when FastAPI is not installed
|
|
32
|
+
from fastapi import FastAPI
|
|
33
|
+
|
|
34
|
+
if not isinstance(app, FastAPI):
|
|
35
|
+
raise TypeError(f"Expected FastAPI instance, got {type(app)}")
|
|
36
|
+
|
|
29
37
|
self.app = app
|
|
30
38
|
self.swagger_url = swagger_url or "/docs"
|
|
31
39
|
|
|
@@ -36,6 +44,9 @@ class FastAPIIntrospector(AppIntrospector):
|
|
|
36
44
|
Yields:
|
|
37
45
|
RouteInfo: Standardized route information for each API route
|
|
38
46
|
"""
|
|
47
|
+
# Lazy import routing to avoid import errors when FastAPI is not installed
|
|
48
|
+
from fastapi import routing
|
|
49
|
+
|
|
39
50
|
for route in self.app.routes:
|
|
40
51
|
# Only process APIRoute instances (not static files, etc.)
|
|
41
52
|
if isinstance(route, routing.APIRoute):
|
|
@@ -66,7 +77,7 @@ class FastAPIIntrospector(AppIntrospector):
|
|
|
66
77
|
"""
|
|
67
78
|
return self.swagger_url
|
|
68
79
|
|
|
69
|
-
def _get_route_id(self, route:
|
|
80
|
+
def _get_route_id(self, route: Any) -> str:
|
|
70
81
|
"""
|
|
71
82
|
Generate a unique identifier for the route.
|
|
72
83
|
|
fastapi_voyager/server.py
CHANGED
|
@@ -25,6 +25,7 @@ def _get_adapter(
|
|
|
25
25
|
ga_id: str | None = None,
|
|
26
26
|
er_diagram: ErDiagram | None = None,
|
|
27
27
|
enable_pydantic_resolve_meta: bool = False,
|
|
28
|
+
server_mode: bool = False,
|
|
28
29
|
) -> Any:
|
|
29
30
|
"""
|
|
30
31
|
Get the appropriate adapter for the given target app.
|
|
@@ -64,6 +65,7 @@ def _get_adapter(
|
|
|
64
65
|
ga_id=ga_id,
|
|
65
66
|
er_diagram=er_diagram,
|
|
66
67
|
enable_pydantic_resolve_meta=enable_pydantic_resolve_meta,
|
|
68
|
+
server_mode=server_mode,
|
|
67
69
|
)
|
|
68
70
|
|
|
69
71
|
elif framework == FrameworkType.LITESTAR:
|
|
@@ -78,6 +80,7 @@ def _get_adapter(
|
|
|
78
80
|
ga_id=ga_id,
|
|
79
81
|
er_diagram=er_diagram,
|
|
80
82
|
enable_pydantic_resolve_meta=enable_pydantic_resolve_meta,
|
|
83
|
+
server_mode=server_mode,
|
|
81
84
|
)
|
|
82
85
|
|
|
83
86
|
elif framework == FrameworkType.DJANGO_NINJA:
|
|
@@ -92,6 +95,7 @@ def _get_adapter(
|
|
|
92
95
|
ga_id=ga_id,
|
|
93
96
|
er_diagram=er_diagram,
|
|
94
97
|
enable_pydantic_resolve_meta=enable_pydantic_resolve_meta,
|
|
98
|
+
server_mode=server_mode,
|
|
95
99
|
)
|
|
96
100
|
|
|
97
101
|
# If we get here, the app type is not supported
|
|
@@ -114,6 +118,7 @@ def create_voyager(
|
|
|
114
118
|
ga_id: str | None = None,
|
|
115
119
|
er_diagram: ErDiagram | None = None,
|
|
116
120
|
enable_pydantic_resolve_meta: bool = False,
|
|
121
|
+
server_mode: bool = False,
|
|
117
122
|
) -> Any:
|
|
118
123
|
"""
|
|
119
124
|
Create a voyager UI application for the given target app.
|
|
@@ -136,6 +141,7 @@ def create_voyager(
|
|
|
136
141
|
ga_id: Optional Google Analytics tracking ID
|
|
137
142
|
er_diagram: Optional ER diagram from pydantic-resolve
|
|
138
143
|
enable_pydantic_resolve_meta: Enable display of pydantic-resolve metadata
|
|
144
|
+
server_mode: If True, serve voyager UI at root path (for standalone preview mode)
|
|
139
145
|
|
|
140
146
|
Returns:
|
|
141
147
|
A framework-specific application object that provides the voyager UI
|
|
@@ -176,6 +182,7 @@ def create_voyager(
|
|
|
176
182
|
ga_id=ga_id,
|
|
177
183
|
er_diagram=er_diagram,
|
|
178
184
|
enable_pydantic_resolve_meta=enable_pydantic_resolve_meta,
|
|
185
|
+
server_mode=server_mode,
|
|
179
186
|
)
|
|
180
187
|
|
|
181
188
|
return adapter.create_app()
|
fastapi_voyager/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.16.0alpha-
|
|
2
|
+
__version__ = "0.16.0alpha-3"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-voyager
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.0a3
|
|
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
|
|
@@ -20,15 +20,33 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Requires-Dist: jinja2>=3.0.0
|
|
22
22
|
Requires-Dist: pydantic-resolve>=2.4.3
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: django-ninja>=1.5.3; extra == 'all'
|
|
25
|
+
Requires-Dist: django>=4.2; extra == 'all'
|
|
26
|
+
Requires-Dist: fastapi>=0.110; extra == 'all'
|
|
27
|
+
Requires-Dist: httpx; extra == 'all'
|
|
28
|
+
Requires-Dist: litestar>=2.19.0; extra == 'all'
|
|
29
|
+
Requires-Dist: pydantic>=2.0; extra == 'all'
|
|
30
|
+
Requires-Dist: pytest; extra == 'all'
|
|
31
|
+
Requires-Dist: pytest-asyncio; extra == 'all'
|
|
32
|
+
Requires-Dist: ruff; extra == 'all'
|
|
33
|
+
Requires-Dist: uvicorn; extra == 'all'
|
|
23
34
|
Provides-Extra: dev
|
|
24
|
-
Requires-Dist: django-ninja; extra == 'dev'
|
|
25
|
-
Requires-Dist: fastapi>=0.110; extra == 'dev'
|
|
26
35
|
Requires-Dist: httpx; extra == 'dev'
|
|
27
|
-
Requires-Dist: litestar; extra == 'dev'
|
|
28
36
|
Requires-Dist: pytest; extra == 'dev'
|
|
29
37
|
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
30
38
|
Requires-Dist: ruff; extra == 'dev'
|
|
31
|
-
|
|
39
|
+
Provides-Extra: django-ninja
|
|
40
|
+
Requires-Dist: django-ninja>=1.5.3; extra == 'django-ninja'
|
|
41
|
+
Requires-Dist: django>=4.2; extra == 'django-ninja'
|
|
42
|
+
Requires-Dist: uvicorn; extra == 'django-ninja'
|
|
43
|
+
Provides-Extra: fastapi
|
|
44
|
+
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
|
|
45
|
+
Requires-Dist: uvicorn; extra == 'fastapi'
|
|
46
|
+
Provides-Extra: litestar
|
|
47
|
+
Requires-Dist: litestar>=2.19.0; extra == 'litestar'
|
|
48
|
+
Requires-Dist: pydantic>=2.0; extra == 'litestar'
|
|
49
|
+
Requires-Dist: uvicorn; extra == 'litestar'
|
|
32
50
|
Description-Content-Type: text/markdown
|
|
33
51
|
|
|
34
52
|
[](https://pypi.python.org/pypi/fastapi-voyager)
|
|
@@ -301,8 +319,14 @@ Set `enable_pydantic_resolve_meta=True` in `create_voyager`, then toggle the "py
|
|
|
301
319
|
### Start Server
|
|
302
320
|
|
|
303
321
|
```bash
|
|
304
|
-
#
|
|
305
|
-
voyager -m tests.demo --server
|
|
322
|
+
# FastAPI
|
|
323
|
+
voyager -m tests.demo --server --web fastapi
|
|
324
|
+
|
|
325
|
+
# Django Ninja
|
|
326
|
+
voyager -m tests.demo --server --web django-ninja
|
|
327
|
+
|
|
328
|
+
# Litestar
|
|
329
|
+
voyager -m tests.demo --server --web litestar
|
|
306
330
|
|
|
307
331
|
# Custom port
|
|
308
332
|
voyager -m tests.demo --server --port=8002
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
fastapi_voyager/__init__.py,sha256=1IdDy6JUMgQqQo33qUe2znX9YeI7S35pjVrbt0QLOzY,228
|
|
2
|
-
fastapi_voyager/cli.py,sha256=
|
|
2
|
+
fastapi_voyager/cli.py,sha256=eD2QmfSVOQjF_Y9WpnQPUjlMpfMU83DfrHp0BYe9xcw,12439
|
|
3
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
6
|
fastapi_voyager/pydantic_resolve_util.py,sha256=0UfAp6Yi6FNpsI1bUu89hRVWFy6keBu1KtcZl-6NYso,3526
|
|
7
7
|
fastapi_voyager/render.py,sha256=A1jFDraQFOfnFHguYlsvBbGIDJ527VQH0jZ-xgTjqIk,17270
|
|
8
8
|
fastapi_voyager/render_style.py,sha256=1y3aRhBSJSWU-JuSgjn9il_xFEqjv6mJCoUzImLQT6M,2525
|
|
9
|
-
fastapi_voyager/server.py,sha256=
|
|
9
|
+
fastapi_voyager/server.py,sha256=qHCN5EqoFwqvIGsMX-ALiogUc4x0xWLpVFpgO06KZvA,7035
|
|
10
10
|
fastapi_voyager/type.py,sha256=zluWvh5vpnjXJ9aAmyNJTSmXZPjAHCvgRT5oQRAjHrg,2104
|
|
11
11
|
fastapi_voyager/type_helper.py,sha256=5HYUHdghTISZ44NvVsrMWlRGD5C9-xrGeNKuLYDMA6s,10209
|
|
12
|
-
fastapi_voyager/version.py,sha256=
|
|
12
|
+
fastapi_voyager/version.py,sha256=zh4QycBWD2nGHz_cZwy5vHQ1KDWxkhAdF_-JPiTVFzU,56
|
|
13
13
|
fastapi_voyager/voyager.py,sha256=S0cCMLFv_wPfEMVdp4cYqbc-Y207SjCTSmsYdqzIDHg,15307
|
|
14
14
|
fastapi_voyager/adapters/__init__.py,sha256=a95rBvV4QVcX_yAzjuJQKOW-EbJ79R--YjUJ3BGkC3k,517
|
|
15
|
-
fastapi_voyager/adapters/base.py,sha256=
|
|
15
|
+
fastapi_voyager/adapters/base.py,sha256=mTYCnYYR4VtA7a-cTJmhsT_Up0hgxLC0hBzUbYxQCJQ,1039
|
|
16
16
|
fastapi_voyager/adapters/common.py,sha256=DXVLsjLn65U3RR7YyKL_ELV74mnINWmZ4hQ6zISdV0E,9684
|
|
17
|
-
fastapi_voyager/adapters/django_ninja_adapter.py,sha256=
|
|
18
|
-
fastapi_voyager/adapters/fastapi_adapter.py,sha256
|
|
19
|
-
fastapi_voyager/adapters/litestar_adapter.py,sha256
|
|
17
|
+
fastapi_voyager/adapters/django_ninja_adapter.py,sha256=8-BMGxAKr8-ScrYB070cn4Te9caMvJhj5mta_YpOA3k,11573
|
|
18
|
+
fastapi_voyager/adapters/fastapi_adapter.py,sha256=qcbXTpaQYrFJJwCsxDx8LGAGKT_Os1I3ReBvLD39jEI,6038
|
|
19
|
+
fastapi_voyager/adapters/litestar_adapter.py,sha256=XGm-NUoD4a1sHl3zpDAj-Q6kzymANAIaV2GM5pgZ0tM,6963
|
|
20
20
|
fastapi_voyager/introspectors/__init__.py,sha256=HbmoUyM-BSwd4Rg2ptd9u-qvZSD3UykKyHYVoRg03OM,917
|
|
21
21
|
fastapi_voyager/introspectors/base.py,sha256=hMfka9gVXr-E8MA1rKSSmYk0OppqgiFPWavfgAkPmQI,2131
|
|
22
22
|
fastapi_voyager/introspectors/detector.py,sha256=rmlpQARJMrFXPty6OUdjmFRTtBYErGDH4l7Tivnu_FQ,3910
|
|
23
23
|
fastapi_voyager/introspectors/django_ninja.py,sha256=Ytneh_kpsakTo_Njv1e4nvqfErjT1PrYelLZ-8xX5Gg,4052
|
|
24
|
-
fastapi_voyager/introspectors/fastapi.py,sha256=
|
|
24
|
+
fastapi_voyager/introspectors/fastapi.py,sha256=TGr1VPO0f6y-w3ZvQpI15o4Jq5L7VcN_twqzY_33fz4,3085
|
|
25
25
|
fastapi_voyager/introspectors/litestar.py,sha256=QXnaT0-hCa_0sByKJoUWuu0vIzRpCCKLokCBDTtv_s4,6100
|
|
26
26
|
fastapi_voyager/templates/dot/cluster.j2,sha256=I2z9KkfCzmAtqXe0gXBnxnOfBXUSpdlATs3uf-O8_B8,307
|
|
27
27
|
fastapi_voyager/templates/dot/cluster_container.j2,sha256=2tH1mOJvPoVKE_aHVMR3t06TfH_dYa9OeH6DBqSHt_A,204
|
|
@@ -55,8 +55,8 @@ fastapi_voyager/web/icon/favicon-16x16.png,sha256=JC07jEzfIYxBIoQn_FHXvyHuxESdhW
|
|
|
55
55
|
fastapi_voyager/web/icon/favicon-32x32.png,sha256=C7v1h58cfWOsiLp9yOIZtlx-dLasBcq3NqpHVGRmpt4,1859
|
|
56
56
|
fastapi_voyager/web/icon/favicon.ico,sha256=tZolYIXkkBcFiYl1A8ksaXN2VjGamzcSdes838dLvNc,15406
|
|
57
57
|
fastapi_voyager/web/icon/site.webmanifest,sha256=GRozZ5suTykYcPMap1QhjrAB8PLW0mbT_phhzw_utvQ,316
|
|
58
|
-
fastapi_voyager-0.16.
|
|
59
|
-
fastapi_voyager-0.16.
|
|
60
|
-
fastapi_voyager-0.16.
|
|
61
|
-
fastapi_voyager-0.16.
|
|
62
|
-
fastapi_voyager-0.16.
|
|
58
|
+
fastapi_voyager-0.16.0a3.dist-info/METADATA,sha256=v39CCqmxr4ls6J4m9--3R1vPTTBFnAkEBX0BHY9Tgyo,13811
|
|
59
|
+
fastapi_voyager-0.16.0a3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
60
|
+
fastapi_voyager-0.16.0a3.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
|
|
61
|
+
fastapi_voyager-0.16.0a3.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
|
|
62
|
+
fastapi_voyager-0.16.0a3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|