fastapi-voyager 0.16.0a2__tar.gz → 0.16.1__tar.gz

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 (115) hide show
  1. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/PKG-INFO +40 -22
  2. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/README.md +39 -21
  3. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/docs/changelog.md +2 -0
  4. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/setup-litestar.sh +2 -2
  5. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/adapters/base.py +0 -10
  6. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/adapters/django_ninja_adapter.py +12 -11
  7. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/adapters/fastapi_adapter.py +3 -4
  8. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/adapters/litestar_adapter.py +3 -4
  9. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/cli.py +108 -62
  10. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/server.py +7 -0
  11. fastapi_voyager-0.16.1/src/fastapi_voyager/version.py +2 -0
  12. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/litestar/demo.py +10 -1
  13. fastapi_voyager-0.16.1/tests/litestar/embedding.py +44 -0
  14. fastapi_voyager-0.16.1/tests/test_adapter_interface.py +166 -0
  15. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_embedding_litestar.py +3 -3
  16. fastapi_voyager-0.16.0a2/src/fastapi_voyager/version.py +0 -2
  17. fastapi_voyager-0.16.0a2/tests/litestar/embedding.py +0 -89
  18. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.githooks/README.md +0 -0
  19. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.githooks/pre-commit +0 -0
  20. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.github/workflows/publish.yml +0 -0
  23. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.gitignore +0 -0
  24. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.prettierignore +0 -0
  25. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.prettierrc +0 -0
  26. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/.python-version +0 -0
  27. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/CONTRIBUTING.md +0 -0
  28. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/LICENSE +0 -0
  29. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/docs/claude/0_REFACTORING_RENDER_NOTES.md +0 -0
  30. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/docs/idea.md +0 -0
  31. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/package-lock.json +0 -0
  32. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/pyproject.toml +0 -0
  33. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/release.md +0 -0
  34. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/setup-django-ninja.sh +0 -0
  35. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/setup-fastapi.sh +0 -0
  36. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/setup-hooks.sh +0 -0
  37. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/__init__.py +0 -0
  38. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/adapters/__init__.py +0 -0
  39. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/adapters/common.py +0 -0
  40. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/er_diagram.py +0 -0
  41. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/filter.py +0 -0
  42. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/introspectors/__init__.py +0 -0
  43. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/introspectors/base.py +0 -0
  44. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/introspectors/detector.py +0 -0
  45. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/introspectors/django_ninja.py +0 -0
  46. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/introspectors/fastapi.py +0 -0
  47. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/introspectors/litestar.py +0 -0
  48. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/module.py +0 -0
  49. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/pydantic_resolve_util.py +0 -0
  50. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/render.py +0 -0
  51. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/render_style.py +0 -0
  52. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/dot/cluster.j2 +0 -0
  53. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/dot/cluster_container.j2 +0 -0
  54. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/dot/digraph.j2 +0 -0
  55. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/dot/er_diagram.j2 +0 -0
  56. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/dot/link.j2 +0 -0
  57. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/dot/route_node.j2 +0 -0
  58. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/dot/schema_node.j2 +0 -0
  59. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/dot/tag_node.j2 +0 -0
  60. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/html/colored_text.j2 +0 -0
  61. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/html/pydantic_meta.j2 +0 -0
  62. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/html/schema_field_row.j2 +0 -0
  63. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/html/schema_header.j2 +0 -0
  64. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/templates/html/schema_table.j2 +0 -0
  65. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/type.py +0 -0
  66. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/type_helper.py +0 -0
  67. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/voyager.py +0 -0
  68. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/component/demo.js +0 -0
  69. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/component/render-graph.js +0 -0
  70. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/component/route-code-display.js +0 -0
  71. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/component/schema-code-display.js +0 -0
  72. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/graph-ui.js +0 -0
  73. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
  74. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
  75. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
  76. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
  77. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
  78. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
  79. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
  80. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
  81. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
  82. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/index.html +0 -0
  83. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/quasar.min.css +0 -0
  84. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/quasar.min.js +0 -0
  85. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/store.js +0 -0
  86. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/src/fastapi_voyager/web/vue-main.js +0 -0
  87. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/README.md +0 -0
  88. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/__init__.py +0 -0
  89. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/django_ninja/__init__.py +0 -0
  90. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/django_ninja/demo.py +0 -0
  91. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/django_ninja/embedding.py +0 -0
  92. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/django_ninja/settings.py +0 -0
  93. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/django_ninja/urls.py +0 -0
  94. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/embedding_test_utils.py +0 -0
  95. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/fastapi/__init__.py +0 -0
  96. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/fastapi/demo.py +0 -0
  97. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/fastapi/demo_anno.py +0 -0
  98. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/fastapi/embedding.py +0 -0
  99. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/litestar/__init__.py +0 -0
  100. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/service/__init__.py +0 -0
  101. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/service/schema/__init__.py +0 -0
  102. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/service/schema/base_entity.py +0 -0
  103. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/service/schema/extra.py +0 -0
  104. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/service/schema/schema.py +0 -0
  105. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_analysis.py +0 -0
  106. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_embedding_django_ninja.py +0 -0
  107. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_embedding_fastapi.py +0 -0
  108. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_filter.py +0 -0
  109. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_generic.py +0 -0
  110. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_import.py +0 -0
  111. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_module.py +0 -0
  112. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_resolve_util_impl.py +0 -0
  113. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/tests/test_type_helper.py +0 -0
  114. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/uv.lock +0 -0
  115. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.1}/voyager.jpg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-voyager
3
- Version: 0.16.0a2
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
- from litestar import Litestar, get
210
- from fastapi_voyager import create_voyager
211
+ # In your main app file (e.g., app.py)
212
+ from litestar import Litestar, Controller
211
213
 
212
- # Create Litestar app
213
- app = Litestar()
214
+ class MyController(Controller):
215
+ # ... your routes ...
214
216
 
215
- @get("/hello")
216
- def hello() -> dict:
217
- return {"message": "Hello World"}
217
+ ROUTE_HANDLERS = [MyController] # Export for extension
218
+ app = Litestar(route_handlers=ROUTE_HANDLERS)
219
+ ```
218
220
 
219
- # Create voyager app (returns a Litestar app)
220
- voyager_app = create_voyager(app)
221
+ Then create voyager by reusing `ROUTE_HANDLERS`:
221
222
 
222
- # Create ASGI application that routes between main app and voyager
223
- async def asgi_app(scope, receive, send):
224
- if scope["type"] == "http" and scope["path"].startswith("/voyager"):
225
- # Remove /voyager prefix for voyager app
226
- new_scope = dict(scope)
227
- new_scope["path"] = scope["path"][8:] or "/"
228
- await voyager_app(new_scope, receive, send)
229
- else:
230
- await app(scope, receive, send)
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:asgi_app --reload
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
- # Open in browser (default port 8000)
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
@@ -154,34 +154,44 @@ uvicorn your_app:application --reload
154
154
 
155
155
  ### Litestar
156
156
 
157
+ Litestar doesn't support mounting to an existing app like FastAPI. The recommended pattern is to export `ROUTE_HANDLERS` from your main app:
158
+
157
159
  ```python
158
- from litestar import Litestar, get
159
- from fastapi_voyager import create_voyager
160
+ # In your main app file (e.g., app.py)
161
+ from litestar import Litestar, Controller
160
162
 
161
- # Create Litestar app
162
- app = Litestar()
163
+ class MyController(Controller):
164
+ # ... your routes ...
163
165
 
164
- @get("/hello")
165
- def hello() -> dict:
166
- return {"message": "Hello World"}
166
+ ROUTE_HANDLERS = [MyController] # Export for extension
167
+ app = Litestar(route_handlers=ROUTE_HANDLERS)
168
+ ```
167
169
 
168
- # Create voyager app (returns a Litestar app)
169
- voyager_app = create_voyager(app)
170
+ Then create voyager by reusing `ROUTE_HANDLERS`:
170
171
 
171
- # Create ASGI application that routes between main app and voyager
172
- async def asgi_app(scope, receive, send):
173
- if scope["type"] == "http" and scope["path"].startswith("/voyager"):
174
- # Remove /voyager prefix for voyager app
175
- new_scope = dict(scope)
176
- new_scope["path"] = scope["path"][8:] or "/"
177
- await voyager_app(new_scope, receive, send)
178
- else:
179
- await app(scope, receive, send)
172
+ ```python
173
+ # In your voyager embedding file
174
+ from typing import Any, Awaitable, Callable
175
+ from litestar import Litestar, asgi
176
+ from fastapi_voyager import create_voyager
177
+ from your_app import ROUTE_HANDLERS, app as your_app
178
+
179
+ voyager_app = create_voyager(your_app)
180
+
181
+ @asgi("/voyager", is_mount=True, copy_scope=True)
182
+ async def voyager_mount(
183
+ scope: dict[str, Any],
184
+ receive: Callable[[], Awaitable[dict[str, Any]]],
185
+ send: Callable[[dict[str, Any]], Awaitable[None]]
186
+ ) -> None:
187
+ await voyager_app(scope, receive, send)
188
+
189
+ app = Litestar(route_handlers=ROUTE_HANDLERS + [voyager_mount])
180
190
  ```
181
191
 
182
192
  Start with:
183
193
  ```bash
184
- uvicorn your_app:asgi_app --reload
194
+ uvicorn your_app:app --reload
185
195
  # Visit http://localhost:8000/voyager
186
196
  ```
187
197
 
@@ -268,8 +278,14 @@ Set `enable_pydantic_resolve_meta=True` in `create_voyager`, then toggle the "py
268
278
  ### Start Server
269
279
 
270
280
  ```bash
271
- # Open in browser (default port 8000)
272
- voyager -m tests.demo --server
281
+ # FastAPI
282
+ voyager -m tests.demo --server --web fastapi
283
+
284
+ # Django Ninja
285
+ voyager -m tests.demo --server --web django-ninja
286
+
287
+ # Litestar
288
+ voyager -m tests.demo --server --web litestar
273
289
 
274
290
  # Custom port
275
291
  voyager -m tests.demo --server --port=8002
@@ -278,6 +294,8 @@ voyager -m tests.demo --server --port=8002
278
294
  voyager -m tests.demo --server --app my_app
279
295
  ```
280
296
 
297
+ > **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.
298
+
281
299
  ### Generate DOT File
282
300
 
283
301
  ```bash
@@ -181,6 +181,8 @@
181
181
  - [x] support django ninja and litestar
182
182
  - 0.16.0alpha-2
183
183
  - [x] fix import error
184
+ - 0.16.0alpha-3
185
+ - [x] fix voyager cli, add web parameter
184
186
 
185
187
  ## 0.17, enhance er diagram
186
188
  - 0.17.0
@@ -39,10 +39,10 @@ echo ""
39
39
 
40
40
  # Start Litestar server
41
41
  echo "🌟 Starting Litestar Voyager server..."
42
- echo " App: tests.litestar.embedding:asgi_app"
42
+ echo " App: tests.litestar.embedding:app"
43
43
  echo " URL: http://127.0.0.1:8000"
44
44
  echo ""
45
45
  echo "Press Ctrl+C to stop the server"
46
46
  echo ""
47
47
 
48
- uv run uvicorn tests.litestar.embedding:asgi_app --reload --host 127.0.0.1 --port 8000
48
+ uv run uvicorn tests.litestar.embedding:app --reload --host 127.0.0.1 --port 8000
@@ -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
- # Route /voyager/* to voyager handler
288
- if scope["type"] == "http" and scope["path"].startswith("/voyager"):
289
- await self._handle_request(scope, receive, send)
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"
@@ -5,54 +5,57 @@ import importlib.util
5
5
  import logging
6
6
  import os
7
7
  import sys
8
- from typing import TYPE_CHECKING
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 load_fastapi_app_from_file(module_path: str, app_name: str = "app") -> "FastAPI | None":
21
- """Load FastAPI app from a Python module file."""
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 FastAPI app instance
38
- if hasattr(module, app_name):
39
- app = getattr(module, app_name)
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
- logger.error(f"No attribute '{app_name}' found in the module")
47
- return None
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 FastAPI app: {e}")
53
+ logger.error(f"Error loading app: {e}")
51
54
  return None
52
55
 
53
56
 
54
- def load_fastapi_app_from_module(module_name: str, app_name: str = "app") -> "FastAPI | None":
55
- """Load FastAPI app from a Python module name."""
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 FastAPI app instance
70
- if hasattr(module, app_name):
71
- app = getattr(module, app_name)
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
- logger.error(f"No attribute '{app_name}' found in module '{module_name}'")
79
- return None
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 FastAPI app from module '{module_name}': {e}")
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: "FastAPI",
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 FastAPI router visualization."""
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 FastAPI application's routing tree and dependencies",
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 # Load 'app' from app.py
131
- voyager -m tests.demo # Load 'app' from demo module
132
- voyager -m tests.demo --app=app # Load 'app' from tests.demo
133
- voyager -m tests.demo --schema=NodeA # [str] filter nodes by schema name
134
- voyager -m tests.demo --tags=page restful # list[str] filter nodes route's tags
135
- voyager -m tests.demo --module_color=tests.demo:red --module_color=tests.service:yellow # list[str] filter nodes route's tags
136
- voyager -m tests.demo -o my_graph.dot # Output to my_graph.dot
137
- voyager -m tests.demo --server # start a local server to preview
138
- voyager -m tests.demo --server --port=8001 # start a local server to preview
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 FastAPI application"
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 FastAPI application (like python -m)"
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 FastAPI app variable (default: app)"
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, -m module name")
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 FastAPI app based on the input method (module_name takes precedence)
286
+ # Load app based on the input method (module_name takes precedence)
244
287
  if args.module_name:
245
- app = load_fastapi_app_from_module(args.module_name, args.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 = load_fastapi_app_from_file(args.module, args.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 which computes DOT via Analytics using closure state
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