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.
Files changed (31) hide show
  1. fastapi_voyager/__init__.py +2 -2
  2. fastapi_voyager/adapters/__init__.py +16 -0
  3. fastapi_voyager/adapters/base.py +44 -0
  4. fastapi_voyager/adapters/common.py +260 -0
  5. fastapi_voyager/adapters/django_ninja_adapter.py +299 -0
  6. fastapi_voyager/adapters/fastapi_adapter.py +165 -0
  7. fastapi_voyager/adapters/litestar_adapter.py +188 -0
  8. fastapi_voyager/er_diagram.py +15 -14
  9. fastapi_voyager/introspectors/__init__.py +34 -0
  10. fastapi_voyager/introspectors/base.py +81 -0
  11. fastapi_voyager/introspectors/detector.py +123 -0
  12. fastapi_voyager/introspectors/django_ninja.py +114 -0
  13. fastapi_voyager/introspectors/fastapi.py +83 -0
  14. fastapi_voyager/introspectors/litestar.py +166 -0
  15. fastapi_voyager/pydantic_resolve_util.py +4 -2
  16. fastapi_voyager/render.py +2 -2
  17. fastapi_voyager/render_style.py +0 -1
  18. fastapi_voyager/server.py +174 -295
  19. fastapi_voyager/type_helper.py +2 -2
  20. fastapi_voyager/version.py +1 -1
  21. fastapi_voyager/voyager.py +75 -47
  22. fastapi_voyager/web/graph-ui.js +102 -69
  23. fastapi_voyager/web/graphviz.svg.js +79 -30
  24. fastapi_voyager/web/index.html +11 -14
  25. fastapi_voyager/web/store.js +2 -0
  26. fastapi_voyager/web/vue-main.js +4 -0
  27. {fastapi_voyager-0.15.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/METADATA +133 -7
  28. {fastapi_voyager-0.15.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/RECORD +31 -19
  29. {fastapi_voyager-0.15.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/WHEEL +0 -0
  30. {fastapi_voyager-0.15.5.dist-info → fastapi_voyager-0.16.0a1.dist-info}/entry_points.txt +0 -0
  31. {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
- from pydantic_resolve.utils.collector import SendToInfo, ICollector
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' + f'penwidth = 3' if color else ''
364
+ pen_style += '\n' + 'penwidth = 3' if color else ''
365
365
  else:
366
366
  pen_style = 'pencolor="#ccc"'
367
367
 
@@ -2,7 +2,6 @@
2
2
  Style constants and configuration for rendering DOT graphs and HTML tables.
3
3
  """
4
4
  from dataclasses import dataclass, field
5
- from typing import Literal
6
5
 
7
6
 
8
7
  @dataclass