fastapi-voyager 0.16.0a2__py3-none-any.whl → 0.16.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/adapters/base.py +0 -10
- fastapi_voyager/adapters/django_ninja_adapter.py +12 -11
- fastapi_voyager/adapters/fastapi_adapter.py +3 -4
- fastapi_voyager/adapters/litestar_adapter.py +3 -4
- fastapi_voyager/cli.py +108 -62
- fastapi_voyager/server.py +7 -0
- fastapi_voyager/version.py +1 -1
- {fastapi_voyager-0.16.0a2.dist-info → fastapi_voyager-0.16.1.dist-info}/METADATA +40 -22
- {fastapi_voyager-0.16.0a2.dist-info → fastapi_voyager-0.16.1.dist-info}/RECORD +12 -12
- {fastapi_voyager-0.16.0a2.dist-info → fastapi_voyager-0.16.1.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.16.0a2.dist-info → fastapi_voyager-0.16.1.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.16.0a2.dist-info → fastapi_voyager-0.16.1.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"
|
|
@@ -79,6 +79,7 @@ class FastAPIAdapter(VoyagerAdapter):
|
|
|
79
79
|
ga_id: str | None = None,
|
|
80
80
|
er_diagram: Any = None,
|
|
81
81
|
enable_pydantic_resolve_meta: bool = False,
|
|
82
|
+
server_mode: bool = False,
|
|
82
83
|
):
|
|
83
84
|
self.ctx = VoyagerContext(
|
|
84
85
|
target_app=target_app,
|
|
@@ -93,6 +94,8 @@ class FastAPIAdapter(VoyagerAdapter):
|
|
|
93
94
|
framework_name="FastAPI",
|
|
94
95
|
)
|
|
95
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 /
|
|
96
99
|
|
|
97
100
|
def create_app(self) -> Any:
|
|
98
101
|
"""Create and return a FastAPI application with voyager endpoints."""
|
|
@@ -161,7 +164,3 @@ class FastAPIAdapter(VoyagerAdapter):
|
|
|
161
164
|
app.include_router(router)
|
|
162
165
|
|
|
163
166
|
return app
|
|
164
|
-
|
|
165
|
-
def get_mount_path(self) -> str:
|
|
166
|
-
"""Get the recommended mount path for voyager."""
|
|
167
|
-
return "/voyager"
|
|
@@ -29,6 +29,7 @@ class LitestarAdapter(VoyagerAdapter):
|
|
|
29
29
|
ga_id: str | None = None,
|
|
30
30
|
er_diagram: Any = None,
|
|
31
31
|
enable_pydantic_resolve_meta: bool = False,
|
|
32
|
+
server_mode: bool = False,
|
|
32
33
|
):
|
|
33
34
|
self.ctx = VoyagerContext(
|
|
34
35
|
target_app=target_app,
|
|
@@ -43,6 +44,8 @@ class LitestarAdapter(VoyagerAdapter):
|
|
|
43
44
|
framework_name="Litestar",
|
|
44
45
|
)
|
|
45
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 /
|
|
46
49
|
|
|
47
50
|
def create_app(self) -> Any:
|
|
48
51
|
"""Create and return a Litestar application with voyager endpoints."""
|
|
@@ -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,54 +5,57 @@ import importlib.util
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
10
|
from fastapi_voyager import server as viz_server
|
|
11
11
|
from fastapi_voyager.version import __version__
|
|
12
12
|
from fastapi_voyager.voyager import Voyager
|
|
13
13
|
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from fastapi import FastAPI
|
|
16
|
-
|
|
17
14
|
logger = logging.getLogger(__name__)
|
|
18
15
|
|
|
16
|
+
# Framework type constants
|
|
17
|
+
SUPPORTED_FRAMEWORKS = ["fastapi", "litestar", "django-ninja"]
|
|
18
|
+
|
|
19
19
|
|
|
20
|
-
def
|
|
21
|
-
"""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."""
|
|
22
22
|
try:
|
|
23
23
|
# Convert relative path to absolute path
|
|
24
24
|
if not os.path.isabs(module_path):
|
|
25
25
|
module_path = os.path.abspath(module_path)
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
# Load the module
|
|
28
28
|
spec = importlib.util.spec_from_file_location("app_module", module_path)
|
|
29
29
|
if spec is None or spec.loader is None:
|
|
30
30
|
logger.error(f"Could not load module from {module_path}")
|
|
31
31
|
return None
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
module = importlib.util.module_from_spec(spec)
|
|
34
34
|
sys.modules["app_module"] = module
|
|
35
35
|
spec.loader.exec_module(module)
|
|
36
|
-
|
|
37
|
-
# Get the
|
|
38
|
-
if hasattr(module, app_name):
|
|
39
|
-
|
|
40
|
-
# Lazy import to avoid import errors when FastAPI is not installed
|
|
41
|
-
from fastapi import FastAPI
|
|
42
|
-
if isinstance(app, FastAPI):
|
|
43
|
-
return app
|
|
44
|
-
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")
|
|
45
40
|
return None
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
|
|
49
52
|
except Exception as e:
|
|
50
|
-
logger.error(f"Error loading
|
|
53
|
+
logger.error(f"Error loading app: {e}")
|
|
51
54
|
return None
|
|
52
55
|
|
|
53
56
|
|
|
54
|
-
def
|
|
55
|
-
"""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."""
|
|
56
59
|
try:
|
|
57
60
|
# Temporarily add the current working directory to sys.path
|
|
58
61
|
current_dir = os.getcwd()
|
|
@@ -61,37 +64,62 @@ def load_fastapi_app_from_module(module_name: str, app_name: str = "app") -> "Fa
|
|
|
61
64
|
path_added = True
|
|
62
65
|
else:
|
|
63
66
|
path_added = False
|
|
64
|
-
|
|
67
|
+
|
|
65
68
|
try:
|
|
66
69
|
# Import the module by name
|
|
67
70
|
module = importlib.import_module(module_name)
|
|
68
|
-
|
|
69
|
-
# Get the
|
|
70
|
-
if hasattr(module, app_name):
|
|
71
|
-
|
|
72
|
-
# Lazy import to avoid import errors when FastAPI is not installed
|
|
73
|
-
from fastapi import FastAPI
|
|
74
|
-
if isinstance(app, FastAPI):
|
|
75
|
-
return app
|
|
76
|
-
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}'")
|
|
77
75
|
return None
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
80
86
|
finally:
|
|
81
87
|
# Cleanup: if we added the path, remove it
|
|
82
88
|
if path_added and current_dir in sys.path:
|
|
83
89
|
sys.path.remove(current_dir)
|
|
84
|
-
|
|
90
|
+
|
|
85
91
|
except ImportError as e:
|
|
86
92
|
logger.error(f"Could not import module '{module_name}': {e}")
|
|
87
93
|
return None
|
|
88
94
|
except Exception as e:
|
|
89
|
-
logger.error(f"Error loading
|
|
95
|
+
logger.error(f"Error loading app from module '{module_name}': {e}")
|
|
90
96
|
return None
|
|
91
97
|
|
|
92
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
|
+
|
|
93
121
|
def generate_visualization(
|
|
94
|
-
app:
|
|
122
|
+
app: Any,
|
|
95
123
|
output_file: str = "router_viz.dot", tags: list[str] | None = None,
|
|
96
124
|
schema: str | None = None,
|
|
97
125
|
show_fields: bool = False,
|
|
@@ -99,7 +127,7 @@ def generate_visualization(
|
|
|
99
127
|
route_name: str | None = None,
|
|
100
128
|
):
|
|
101
129
|
|
|
102
|
-
"""Generate DOT file for
|
|
130
|
+
"""Generate DOT file for API router visualization."""
|
|
103
131
|
analytics = Voyager(
|
|
104
132
|
include_tags=tags,
|
|
105
133
|
schema=schema,
|
|
@@ -123,39 +151,46 @@ def generate_visualization(
|
|
|
123
151
|
def main():
|
|
124
152
|
"""Main CLI entry point."""
|
|
125
153
|
parser = argparse.ArgumentParser(
|
|
126
|
-
description="Visualize
|
|
154
|
+
description="Visualize web application's routing tree and dependencies (supports FastAPI, Litestar, Django-Ninja)",
|
|
127
155
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
128
156
|
epilog="""
|
|
129
157
|
Examples:
|
|
130
|
-
voyager app.py
|
|
131
|
-
voyager
|
|
132
|
-
voyager -m tests.demo --
|
|
133
|
-
voyager -m tests.demo --
|
|
134
|
-
voyager -m tests.demo --
|
|
135
|
-
voyager -m tests.demo --
|
|
136
|
-
voyager -m tests.demo
|
|
137
|
-
voyager -m tests.demo --
|
|
138
|
-
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
|
|
139
168
|
"""
|
|
140
169
|
)
|
|
141
|
-
|
|
170
|
+
|
|
142
171
|
# Create mutually exclusive group for module loading options
|
|
143
172
|
group = parser.add_mutually_exclusive_group(required=False)
|
|
144
173
|
group.add_argument(
|
|
145
174
|
"module",
|
|
146
175
|
nargs="?",
|
|
147
|
-
help="Python file containing the
|
|
176
|
+
help="Python file containing the web application"
|
|
148
177
|
)
|
|
149
178
|
group.add_argument(
|
|
150
179
|
"-m", "--module",
|
|
151
180
|
dest="module_name",
|
|
152
|
-
help="Python module name containing the
|
|
181
|
+
help="Python module name containing the web application (like python -m)"
|
|
153
182
|
)
|
|
154
|
-
|
|
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
|
+
|
|
155
190
|
parser.add_argument(
|
|
156
191
|
"--app", "-a",
|
|
157
192
|
default="app",
|
|
158
|
-
help="Name of the
|
|
193
|
+
help="Name of the app variable (default: app)"
|
|
159
194
|
)
|
|
160
195
|
|
|
161
196
|
parser.add_argument(
|
|
@@ -229,26 +264,34 @@ Examples:
|
|
|
229
264
|
)
|
|
230
265
|
|
|
231
266
|
args = parser.parse_args()
|
|
232
|
-
|
|
267
|
+
|
|
268
|
+
# Validate arguments
|
|
233
269
|
if args.module_prefix and not args.server:
|
|
234
270
|
parser.error("--module_prefix can only be used together with --server")
|
|
235
271
|
|
|
236
272
|
if not (args.module_name or args.module):
|
|
237
|
-
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
|
|
238
281
|
|
|
239
282
|
# Configure logging based on --log-level
|
|
240
283
|
level_name = (args.log_level or "INFO").upper()
|
|
241
284
|
logging.basicConfig(level=level_name)
|
|
242
285
|
|
|
243
|
-
# Load
|
|
286
|
+
# Load app based on the input method (module_name takes precedence)
|
|
244
287
|
if args.module_name:
|
|
245
|
-
app =
|
|
288
|
+
app = load_app_from_module(args.module_name, args.app, framework)
|
|
246
289
|
else:
|
|
247
290
|
if not os.path.exists(args.module):
|
|
248
291
|
logger.error(f"File '{args.module}' not found")
|
|
249
292
|
sys.exit(1)
|
|
250
|
-
app =
|
|
251
|
-
|
|
293
|
+
app = load_app_from_file(args.module, args.app, framework)
|
|
294
|
+
|
|
252
295
|
if app is None:
|
|
253
296
|
sys.exit(1)
|
|
254
297
|
|
|
@@ -269,18 +312,21 @@ Examples:
|
|
|
269
312
|
try:
|
|
270
313
|
module_color = parse_kv_pairs(args.module_color)
|
|
271
314
|
if args.server:
|
|
272
|
-
# Build a preview server
|
|
315
|
+
# Build a preview server using the appropriate framework
|
|
273
316
|
try:
|
|
274
317
|
import uvicorn
|
|
275
318
|
except ImportError:
|
|
276
319
|
logger.info("uvicorn is required to run the server. Install via 'pip install uvicorn' or 'uv add uvicorn'.")
|
|
277
320
|
sys.exit(1)
|
|
321
|
+
|
|
322
|
+
# Create voyager app - it auto-detects framework and returns appropriate app type
|
|
278
323
|
app_server = viz_server.create_voyager(
|
|
279
324
|
app,
|
|
280
325
|
module_color=module_color,
|
|
281
326
|
module_prefix=args.module_prefix,
|
|
327
|
+
server_mode=True, # Enable server mode to serve at root path
|
|
282
328
|
)
|
|
283
|
-
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)")
|
|
284
330
|
uvicorn.run(app_server, host=args.host, port=args.port, log_level=level_name.lower())
|
|
285
331
|
else:
|
|
286
332
|
# Generate and write dot file locally
|
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.
|
|
2
|
+
__version__ = "0.16.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-voyager
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.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
|
|
@@ -205,34 +205,44 @@ uvicorn your_app:application --reload
|
|
|
205
205
|
|
|
206
206
|
### Litestar
|
|
207
207
|
|
|
208
|
+
Litestar doesn't support mounting to an existing app like FastAPI. The recommended pattern is to export `ROUTE_HANDLERS` from your main app:
|
|
209
|
+
|
|
208
210
|
```python
|
|
209
|
-
|
|
210
|
-
from
|
|
211
|
+
# In your main app file (e.g., app.py)
|
|
212
|
+
from litestar import Litestar, Controller
|
|
211
213
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
class MyController(Controller):
|
|
215
|
+
# ... your routes ...
|
|
214
216
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
ROUTE_HANDLERS = [MyController] # Export for extension
|
|
218
|
+
app = Litestar(route_handlers=ROUTE_HANDLERS)
|
|
219
|
+
```
|
|
218
220
|
|
|
219
|
-
|
|
220
|
-
voyager_app = create_voyager(app)
|
|
221
|
+
Then create voyager by reusing `ROUTE_HANDLERS`:
|
|
221
222
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
```python
|
|
224
|
+
# In your voyager embedding file
|
|
225
|
+
from typing import Any, Awaitable, Callable
|
|
226
|
+
from litestar import Litestar, asgi
|
|
227
|
+
from fastapi_voyager import create_voyager
|
|
228
|
+
from your_app import ROUTE_HANDLERS, app as your_app
|
|
229
|
+
|
|
230
|
+
voyager_app = create_voyager(your_app)
|
|
231
|
+
|
|
232
|
+
@asgi("/voyager", is_mount=True, copy_scope=True)
|
|
233
|
+
async def voyager_mount(
|
|
234
|
+
scope: dict[str, Any],
|
|
235
|
+
receive: Callable[[], Awaitable[dict[str, Any]]],
|
|
236
|
+
send: Callable[[dict[str, Any]], Awaitable[None]]
|
|
237
|
+
) -> None:
|
|
238
|
+
await voyager_app(scope, receive, send)
|
|
239
|
+
|
|
240
|
+
app = Litestar(route_handlers=ROUTE_HANDLERS + [voyager_mount])
|
|
231
241
|
```
|
|
232
242
|
|
|
233
243
|
Start with:
|
|
234
244
|
```bash
|
|
235
|
-
uvicorn your_app:
|
|
245
|
+
uvicorn your_app:app --reload
|
|
236
246
|
# Visit http://localhost:8000/voyager
|
|
237
247
|
```
|
|
238
248
|
|
|
@@ -319,8 +329,14 @@ Set `enable_pydantic_resolve_meta=True` in `create_voyager`, then toggle the "py
|
|
|
319
329
|
### Start Server
|
|
320
330
|
|
|
321
331
|
```bash
|
|
322
|
-
#
|
|
323
|
-
voyager -m tests.demo --server
|
|
332
|
+
# FastAPI
|
|
333
|
+
voyager -m tests.demo --server --web fastapi
|
|
334
|
+
|
|
335
|
+
# Django Ninja
|
|
336
|
+
voyager -m tests.demo --server --web django-ninja
|
|
337
|
+
|
|
338
|
+
# Litestar
|
|
339
|
+
voyager -m tests.demo --server --web litestar
|
|
324
340
|
|
|
325
341
|
# Custom port
|
|
326
342
|
voyager -m tests.demo --server --port=8002
|
|
@@ -329,6 +345,8 @@ voyager -m tests.demo --server --port=8002
|
|
|
329
345
|
voyager -m tests.demo --server --app my_app
|
|
330
346
|
```
|
|
331
347
|
|
|
348
|
+
> **Note**: Server mode does not support ER diagram or pydantic-resolve metadata configuration. Use `create_voyager()` in your code with `er_diagram` and `enable_pydantic_resolve_meta` parameters to enable these features.
|
|
349
|
+
|
|
332
350
|
### Generate DOT File
|
|
333
351
|
|
|
334
352
|
```bash
|
|
@@ -1,22 +1,22 @@
|
|
|
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=KQqsioyKo8ILOoP_53tP9pUeioVRilkmAOxElLW67O4,49
|
|
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
|
|
@@ -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.1.dist-info/METADATA,sha256=g9vjbDlp4LoZjeWk_Od6CliTYNJV1XCzHaAElEEit4s,14364
|
|
59
|
+
fastapi_voyager-0.16.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
60
|
+
fastapi_voyager-0.16.1.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
|
|
61
|
+
fastapi_voyager-0.16.1.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
|
|
62
|
+
fastapi_voyager-0.16.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|