fastapi-voyager 0.15.5__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/graph-ui.js +102 -69
- fastapi_voyager/web/graphviz.svg.js +79 -30
- 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.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/METADATA +133 -7
- {fastapi_voyager-0.15.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/RECORD +31 -19
- {fastapi_voyager-0.15.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.15.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.15.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django Ninja implementation of the AppIntrospector interface.
|
|
3
|
+
|
|
4
|
+
This module provides the adapter that allows fastapi-voyager to work with Django Ninja applications.
|
|
5
|
+
"""
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
|
|
8
|
+
from fastapi_voyager.introspectors.base import AppIntrospector, RouteInfo
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DjangoNinjaIntrospector(AppIntrospector):
|
|
12
|
+
"""
|
|
13
|
+
Django Ninja-specific implementation of AppIntrospector.
|
|
14
|
+
|
|
15
|
+
This class extracts route information from Django Ninja's internal structure
|
|
16
|
+
and converts it to the framework-agnostic RouteInfo format.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, ninja_api, swagger_url: str | None = None):
|
|
20
|
+
"""
|
|
21
|
+
Initialize the Django Ninja introspector.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
ninja_api: The Django Ninja API instance
|
|
25
|
+
swagger_url: Optional custom URL to Swagger documentation
|
|
26
|
+
"""
|
|
27
|
+
self.api = ninja_api
|
|
28
|
+
self.swagger_url = swagger_url or "/api/docs"
|
|
29
|
+
|
|
30
|
+
def get_routes(self) -> Iterator[RouteInfo]:
|
|
31
|
+
"""
|
|
32
|
+
Iterate over all API routes in the Django Ninja application.
|
|
33
|
+
|
|
34
|
+
Yields:
|
|
35
|
+
RouteInfo: Standardized route information for each API route
|
|
36
|
+
"""
|
|
37
|
+
# Access the internal router structure
|
|
38
|
+
if not hasattr(self.api, "default_router"):
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
router = self.api.default_router
|
|
42
|
+
|
|
43
|
+
# Iterate through all path operations registered in the router
|
|
44
|
+
if not hasattr(router, "path_operations"):
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
for path, path_view in router.path_operations.items():
|
|
48
|
+
# path_view is a PathView object with a list of operations
|
|
49
|
+
if not hasattr(path_view, "operations"):
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
for operation in path_view.operations:
|
|
53
|
+
try:
|
|
54
|
+
yield RouteInfo(
|
|
55
|
+
id=self._get_route_id(operation),
|
|
56
|
+
name=operation.view_func.__name__,
|
|
57
|
+
module=operation.view_func.__module__,
|
|
58
|
+
operation_id=operation.operation_id or operation.view_func.__name__,
|
|
59
|
+
tags=operation.tags or [],
|
|
60
|
+
endpoint=operation.view_func,
|
|
61
|
+
response_model=self._get_response_model(operation),
|
|
62
|
+
extra={
|
|
63
|
+
"methods": operation.methods, # This is a list
|
|
64
|
+
"path": path,
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
except (AttributeError, TypeError):
|
|
68
|
+
# Skip routes that don't have the expected structure
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
def get_swagger_url(self) -> str | None:
|
|
72
|
+
"""
|
|
73
|
+
Get the URL to the Swagger UI documentation.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The URL path to Swagger UI
|
|
77
|
+
"""
|
|
78
|
+
return self.swagger_url
|
|
79
|
+
|
|
80
|
+
def _get_route_id(self, operation) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Generate a unique identifier for the route.
|
|
83
|
+
|
|
84
|
+
Uses the full class path of the view function.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
operation: The Django Ninja operation object
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
A unique identifier string
|
|
91
|
+
"""
|
|
92
|
+
# Import here to avoid circular dependency
|
|
93
|
+
from fastapi_voyager.type_helper import full_class_name
|
|
94
|
+
return full_class_name(operation.view_func)
|
|
95
|
+
|
|
96
|
+
def _get_response_model(self, operation) -> type:
|
|
97
|
+
"""
|
|
98
|
+
Extract the response model from the operation.
|
|
99
|
+
|
|
100
|
+
Django Ninja infers response model from function's return type annotation.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
operation: The Django Ninja operation object
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The response model class, or type(None) if not found
|
|
107
|
+
"""
|
|
108
|
+
# Django Ninja uses type hints for response models
|
|
109
|
+
# The response_models field is always NOT_SET_TYPE, so we only check __annotations__
|
|
110
|
+
if hasattr(operation.view_func, "__annotations__") and "return" in operation.view_func.__annotations__:
|
|
111
|
+
return operation.view_func.__annotations__["return"]
|
|
112
|
+
|
|
113
|
+
# No response model found
|
|
114
|
+
return type(None) # type: ignore
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI implementation of the AppIntrospector interface.
|
|
3
|
+
|
|
4
|
+
This module provides the adapter that allows fastapi-voyager to work with FastAPI applications.
|
|
5
|
+
"""
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
|
|
8
|
+
from fastapi import FastAPI, routing
|
|
9
|
+
|
|
10
|
+
from fastapi_voyager.introspectors.base import AppIntrospector, RouteInfo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FastAPIIntrospector(AppIntrospector):
|
|
14
|
+
"""
|
|
15
|
+
FastAPI-specific implementation of AppIntrospector.
|
|
16
|
+
|
|
17
|
+
This class extracts route information from FastAPI's internal route structure
|
|
18
|
+
and converts it to the framework-agnostic RouteInfo format.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, app: FastAPI, swagger_url: str | None = None):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the FastAPI introspector.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
app: The FastAPI application instance
|
|
27
|
+
swagger_url: Optional custom URL to Swagger documentation
|
|
28
|
+
"""
|
|
29
|
+
self.app = app
|
|
30
|
+
self.swagger_url = swagger_url or "/docs"
|
|
31
|
+
|
|
32
|
+
def get_routes(self) -> Iterator[RouteInfo]:
|
|
33
|
+
"""
|
|
34
|
+
Iterate over all API routes in the FastAPI application.
|
|
35
|
+
|
|
36
|
+
Yields:
|
|
37
|
+
RouteInfo: Standardized route information for each API route
|
|
38
|
+
"""
|
|
39
|
+
for route in self.app.routes:
|
|
40
|
+
# Only process APIRoute instances (not static files, etc.)
|
|
41
|
+
if isinstance(route, routing.APIRoute):
|
|
42
|
+
# Extract tags from the route
|
|
43
|
+
tags = getattr(route, 'tags', None) or []
|
|
44
|
+
|
|
45
|
+
yield RouteInfo(
|
|
46
|
+
id=self._get_route_id(route),
|
|
47
|
+
name=route.endpoint.__name__,
|
|
48
|
+
module=route.endpoint.__module__,
|
|
49
|
+
operation_id=route.operation_id,
|
|
50
|
+
tags=tags,
|
|
51
|
+
endpoint=route.endpoint,
|
|
52
|
+
response_model=route.response_model,
|
|
53
|
+
extra={
|
|
54
|
+
'unique_id': route.unique_id,
|
|
55
|
+
'methods': route.methods,
|
|
56
|
+
'path': route.path,
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def get_swagger_url(self) -> str | None:
|
|
61
|
+
"""
|
|
62
|
+
Get the URL to the Swagger UI documentation.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
The URL path to Swagger UI
|
|
66
|
+
"""
|
|
67
|
+
return self.swagger_url
|
|
68
|
+
|
|
69
|
+
def _get_route_id(self, route: routing.APIRoute) -> str:
|
|
70
|
+
"""
|
|
71
|
+
Generate a unique identifier for the route.
|
|
72
|
+
|
|
73
|
+
Uses the full class path of the endpoint function.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
route: The FastAPI route object
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
A unique identifier string
|
|
80
|
+
"""
|
|
81
|
+
# Import here to avoid circular dependency
|
|
82
|
+
from fastapi_voyager.type_helper import full_class_name
|
|
83
|
+
return full_class_name(route.endpoint)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Litestar implementation of the AppIntrospector interface.
|
|
3
|
+
|
|
4
|
+
This module provides the adapter that allows fastapi-voyager to work with Litestar applications.
|
|
5
|
+
"""
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
|
|
8
|
+
from fastapi_voyager.introspectors.base import AppIntrospector, RouteInfo
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LitestarIntrospector(AppIntrospector):
|
|
12
|
+
"""
|
|
13
|
+
Litestar-specific implementation of AppIntrospector.
|
|
14
|
+
|
|
15
|
+
This class extracts route information from Litestar's internal structure
|
|
16
|
+
and converts it to the framework-agnostic RouteInfo format.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, app, swagger_url: str | None = None):
|
|
20
|
+
"""
|
|
21
|
+
Initialize the Litestar introspector.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
app: The Litestar application instance
|
|
25
|
+
swagger_url: Optional custom URL to Swagger/OpenAPI documentation
|
|
26
|
+
"""
|
|
27
|
+
self.app = app
|
|
28
|
+
self.swagger_url = swagger_url or "/schema/swagger"
|
|
29
|
+
|
|
30
|
+
def get_routes(self) -> Iterator[RouteInfo]:
|
|
31
|
+
"""
|
|
32
|
+
Iterate over all routes in the Litestar application.
|
|
33
|
+
|
|
34
|
+
Yields:
|
|
35
|
+
RouteInfo: Standardized route information for each route
|
|
36
|
+
"""
|
|
37
|
+
for route in self.app.routes:
|
|
38
|
+
try:
|
|
39
|
+
# Skip routes without path or methods
|
|
40
|
+
if not hasattr(route, "path") or not hasattr(route, "methods"):
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
# Skip Litestar's auto-generated schema routes
|
|
44
|
+
if hasattr(route, "path") and route.path.startswith("/schema"):
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Get the handler function from route_handlers
|
|
48
|
+
handler = None
|
|
49
|
+
handler_obj = None
|
|
50
|
+
if hasattr(route, "route_handlers") and route.route_handlers:
|
|
51
|
+
# Find the GET handler (or any non-OPTIONS handler)
|
|
52
|
+
for route_handler in route.route_handlers:
|
|
53
|
+
if hasattr(route_handler, "fn") and hasattr(route_handler.fn, "__name__"):
|
|
54
|
+
# Store the route handler object for tags
|
|
55
|
+
if hasattr(route_handler, "http_methods") and "GET" in route_handler.http_methods:
|
|
56
|
+
handler_obj = route_handler
|
|
57
|
+
handler = route_handler.fn
|
|
58
|
+
if handler_obj:
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
if not handler:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
# Skip handlers with names starting with _ (internal/private)
|
|
65
|
+
if hasattr(handler, "__name__") and handler.__name__.startswith("_"):
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Extract tags from the route handler object
|
|
69
|
+
tags = []
|
|
70
|
+
if handler_obj and hasattr(handler_obj, "tags") and handler_obj.tags:
|
|
71
|
+
tags = list(handler_obj.tags)
|
|
72
|
+
|
|
73
|
+
# Get return type from handler's annotations
|
|
74
|
+
return_model = type(None)
|
|
75
|
+
if hasattr(handler, "__annotations__") and "return" in handler.__annotations__:
|
|
76
|
+
return_model = handler.__annotations__["return"]
|
|
77
|
+
|
|
78
|
+
yield RouteInfo(
|
|
79
|
+
id=self._get_route_id(handler),
|
|
80
|
+
name=handler.__name__,
|
|
81
|
+
module=handler.__module__,
|
|
82
|
+
operation_id=self._get_operation_id(route, handler),
|
|
83
|
+
tags=tags,
|
|
84
|
+
endpoint=handler,
|
|
85
|
+
response_model=return_model,
|
|
86
|
+
extra={
|
|
87
|
+
"methods": list(route.methods) if hasattr(route, "methods") else [],
|
|
88
|
+
"path": route.path,
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
except (AttributeError, TypeError):
|
|
92
|
+
# Skip routes that don't have the expected structure
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
def get_swagger_url(self) -> str | None:
|
|
96
|
+
"""
|
|
97
|
+
Get the URL to the Swagger/OpenAPI documentation.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The URL path to Swagger UI
|
|
101
|
+
"""
|
|
102
|
+
return self.swagger_url
|
|
103
|
+
|
|
104
|
+
def _get_route_id(self, handler) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Generate a unique identifier for the route.
|
|
107
|
+
|
|
108
|
+
Uses the full module path of the handler function.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
handler: The route handler function
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
A unique identifier string
|
|
115
|
+
"""
|
|
116
|
+
# Import here to avoid circular dependency
|
|
117
|
+
from fastapi_voyager.type_helper import full_class_name
|
|
118
|
+
return full_class_name(handler)
|
|
119
|
+
|
|
120
|
+
def _get_operation_id(self, route, handler) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Extract or generate the operation ID for the route.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
route: The Litestar route object
|
|
126
|
+
handler: The handler function
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
An operation ID string
|
|
130
|
+
"""
|
|
131
|
+
# Litestar might not have operation_id, so we generate one
|
|
132
|
+
if hasattr(route, "operation_id"):
|
|
133
|
+
return route.operation_id
|
|
134
|
+
# Fallback to using the handler function name
|
|
135
|
+
if hasattr(handler, "__name__"):
|
|
136
|
+
return handler.__name__
|
|
137
|
+
# Fallback to using the path
|
|
138
|
+
if hasattr(route, "path"):
|
|
139
|
+
return route.path
|
|
140
|
+
return ""
|
|
141
|
+
|
|
142
|
+
def _get_response_model(self, route) -> type:
|
|
143
|
+
"""
|
|
144
|
+
Extract the response model from the route.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
route: The Litestar route object
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
The response model class
|
|
151
|
+
"""
|
|
152
|
+
# Try to get response model from route
|
|
153
|
+
if hasattr(route, "responses"):
|
|
154
|
+
responses = route.responses
|
|
155
|
+
if responses and "200" in responses:
|
|
156
|
+
response_200 = responses["200"]
|
|
157
|
+
if hasattr(response_200, "model"):
|
|
158
|
+
return response_200.model
|
|
159
|
+
|
|
160
|
+
# Fallback: check if handler has return annotation
|
|
161
|
+
handler = route.handler if hasattr(route, "handler") else None
|
|
162
|
+
if handler and hasattr(handler, "__annotations__") and "return" in handler.__annotations__:
|
|
163
|
+
return handler.__annotations__["return"]
|
|
164
|
+
|
|
165
|
+
# Return None if no response model found
|
|
166
|
+
return type(None) # type: ignore
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
|
|
3
|
+
import pydantic_resolve.constant as const
|
|
2
4
|
from pydantic import BaseModel
|
|
3
5
|
from pydantic.fields import FieldInfo
|
|
6
|
+
from pydantic_resolve.utils.collector import ICollector, SendToInfo
|
|
4
7
|
from pydantic_resolve.utils.er_diagram import LoaderInfo
|
|
5
|
-
import pydantic_resolve.constant as const
|
|
6
8
|
from pydantic_resolve.utils.expose import ExposeInfo
|
|
7
|
-
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
def analysis_pydantic_resolve_fields(schema: type[BaseModel], field: str):
|
|
10
12
|
"""
|
fastapi_voyager/render.py
CHANGED
|
@@ -10,8 +10,8 @@ from fastapi_voyager.module import build_module_route_tree, build_module_schema_
|
|
|
10
10
|
from fastapi_voyager.render_style import RenderConfig
|
|
11
11
|
from fastapi_voyager.type import (
|
|
12
12
|
PK,
|
|
13
|
-
FieldType,
|
|
14
13
|
FieldInfo,
|
|
14
|
+
FieldType,
|
|
15
15
|
Link,
|
|
16
16
|
ModuleNode,
|
|
17
17
|
ModuleRoute,
|
|
@@ -361,7 +361,7 @@ class Renderer:
|
|
|
361
361
|
|
|
362
362
|
if cluster_color:
|
|
363
363
|
pen_style = f'pencolor = "{cluster_color}"'
|
|
364
|
-
pen_style += '\n' +
|
|
364
|
+
pen_style += '\n' + 'penwidth = 3' if color else ''
|
|
365
365
|
else:
|
|
366
366
|
pen_style = 'pencolor="#ccc"'
|
|
367
367
|
|