fastapi-voyager 0.16.0a2__tar.gz → 0.16.0a3__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.0a3}/PKG-INFO +9 -3
  2. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/README.md +8 -2
  3. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/docs/changelog.md +2 -0
  4. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/adapters/base.py +0 -10
  5. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/adapters/django_ninja_adapter.py +12 -11
  6. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/adapters/fastapi_adapter.py +3 -4
  7. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/adapters/litestar_adapter.py +3 -4
  8. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/cli.py +108 -62
  9. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/server.py +7 -0
  10. fastapi_voyager-0.16.0a3/src/fastapi_voyager/version.py +2 -0
  11. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/litestar/demo.py +7 -1
  12. fastapi_voyager-0.16.0a3/tests/litestar/embedding.py +53 -0
  13. fastapi_voyager-0.16.0a3/tests/test_adapter_interface.py +166 -0
  14. fastapi_voyager-0.16.0a2/src/fastapi_voyager/version.py +0 -2
  15. fastapi_voyager-0.16.0a2/tests/litestar/embedding.py +0 -89
  16. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.githooks/README.md +0 -0
  17. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.githooks/pre-commit +0 -0
  18. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.github/workflows/publish.yml +0 -0
  21. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.gitignore +0 -0
  22. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.prettierignore +0 -0
  23. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.prettierrc +0 -0
  24. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/.python-version +0 -0
  25. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/CONTRIBUTING.md +0 -0
  26. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/LICENSE +0 -0
  27. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/docs/claude/0_REFACTORING_RENDER_NOTES.md +0 -0
  28. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/docs/idea.md +0 -0
  29. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/package-lock.json +0 -0
  30. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/pyproject.toml +0 -0
  31. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/release.md +0 -0
  32. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/setup-django-ninja.sh +0 -0
  33. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/setup-fastapi.sh +0 -0
  34. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/setup-hooks.sh +0 -0
  35. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/setup-litestar.sh +0 -0
  36. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/__init__.py +0 -0
  37. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/adapters/__init__.py +0 -0
  38. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/adapters/common.py +0 -0
  39. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/er_diagram.py +0 -0
  40. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/filter.py +0 -0
  41. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/introspectors/__init__.py +0 -0
  42. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/introspectors/base.py +0 -0
  43. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/introspectors/detector.py +0 -0
  44. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/introspectors/django_ninja.py +0 -0
  45. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/introspectors/fastapi.py +0 -0
  46. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/introspectors/litestar.py +0 -0
  47. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/module.py +0 -0
  48. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/pydantic_resolve_util.py +0 -0
  49. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/render.py +0 -0
  50. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/render_style.py +0 -0
  51. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/dot/cluster.j2 +0 -0
  52. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/dot/cluster_container.j2 +0 -0
  53. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/dot/digraph.j2 +0 -0
  54. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/dot/er_diagram.j2 +0 -0
  55. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/dot/link.j2 +0 -0
  56. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/dot/route_node.j2 +0 -0
  57. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/dot/schema_node.j2 +0 -0
  58. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/dot/tag_node.j2 +0 -0
  59. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/html/colored_text.j2 +0 -0
  60. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/html/pydantic_meta.j2 +0 -0
  61. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/html/schema_field_row.j2 +0 -0
  62. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/html/schema_header.j2 +0 -0
  63. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/templates/html/schema_table.j2 +0 -0
  64. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/type.py +0 -0
  65. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/type_helper.py +0 -0
  66. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/voyager.py +0 -0
  67. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/component/demo.js +0 -0
  68. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/component/render-graph.js +0 -0
  69. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/component/route-code-display.js +0 -0
  70. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/component/schema-code-display.js +0 -0
  71. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/graph-ui.js +0 -0
  72. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
  73. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
  74. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
  75. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
  76. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
  77. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
  78. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
  79. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
  80. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
  81. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/index.html +0 -0
  82. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/quasar.min.css +0 -0
  83. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/quasar.min.js +0 -0
  84. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/store.js +0 -0
  85. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/src/fastapi_voyager/web/vue-main.js +0 -0
  86. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/README.md +0 -0
  87. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/__init__.py +0 -0
  88. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/django_ninja/__init__.py +0 -0
  89. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/django_ninja/demo.py +0 -0
  90. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/django_ninja/embedding.py +0 -0
  91. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/django_ninja/settings.py +0 -0
  92. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/django_ninja/urls.py +0 -0
  93. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/embedding_test_utils.py +0 -0
  94. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/fastapi/__init__.py +0 -0
  95. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/fastapi/demo.py +0 -0
  96. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/fastapi/demo_anno.py +0 -0
  97. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/fastapi/embedding.py +0 -0
  98. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/litestar/__init__.py +0 -0
  99. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/service/__init__.py +0 -0
  100. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/service/schema/__init__.py +0 -0
  101. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/service/schema/base_entity.py +0 -0
  102. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/service/schema/extra.py +0 -0
  103. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/service/schema/schema.py +0 -0
  104. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_analysis.py +0 -0
  105. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_embedding_django_ninja.py +0 -0
  106. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_embedding_fastapi.py +0 -0
  107. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_embedding_litestar.py +0 -0
  108. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_filter.py +0 -0
  109. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_generic.py +0 -0
  110. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_import.py +0 -0
  111. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_module.py +0 -0
  112. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_resolve_util_impl.py +0 -0
  113. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/tests/test_type_helper.py +0 -0
  114. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/uv.lock +0 -0
  115. {fastapi_voyager-0.16.0a2 → fastapi_voyager-0.16.0a3}/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.0a3
4
4
  Summary: Visualize FastAPI application's routing tree and dependencies
5
5
  Project-URL: Homepage, https://github.com/allmonday/fastapi-voyager
6
6
  Project-URL: Source, https://github.com/allmonday/fastapi-voyager
@@ -319,8 +319,14 @@ Set `enable_pydantic_resolve_meta=True` in `create_voyager`, then toggle the "py
319
319
  ### Start Server
320
320
 
321
321
  ```bash
322
- # Open in browser (default port 8000)
323
- voyager -m tests.demo --server
322
+ # FastAPI
323
+ voyager -m tests.demo --server --web fastapi
324
+
325
+ # Django Ninja
326
+ voyager -m tests.demo --server --web django-ninja
327
+
328
+ # Litestar
329
+ voyager -m tests.demo --server --web litestar
324
330
 
325
331
  # Custom port
326
332
  voyager -m tests.demo --server --port=8002
@@ -268,8 +268,14 @@ Set `enable_pydantic_resolve_meta=True` in `create_voyager`, then toggle the "py
268
268
  ### Start Server
269
269
 
270
270
  ```bash
271
- # Open in browser (default port 8000)
272
- voyager -m tests.demo --server
271
+ # FastAPI
272
+ voyager -m tests.demo --server --web fastapi
273
+
274
+ # Django Ninja
275
+ voyager -m tests.demo --server --web django-ninja
276
+
277
+ # Litestar
278
+ voyager -m tests.demo --server --web litestar
273
279
 
274
280
  # Custom port
275
281
  voyager -m tests.demo --server --port=8002
@@ -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
@@ -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
@@ -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()
@@ -0,0 +1,2 @@
1
+ __all__ = ["__version__"]
2
+ __version__ = "0.16.0alpha-3"
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Annotated, Generic, TypeVar
3
3
 
4
- from litestar import Controller, get
4
+ from litestar import Controller, Litestar, get
5
5
  from pydantic import BaseModel, Field
6
6
  from pydantic_resolve import Collector, DefineSubset, ExposeAs, Resolver, SendTo
7
7
 
@@ -130,3 +130,9 @@ class DemoController(Controller):
130
130
  @get("/page_test_5/", tags=['long_long_long_tag_name', 'group_b'], sync_to_thread=False)
131
131
  def get_page_test_3_no_response_model_long_long_long_name(self) -> bool:
132
132
  return True
133
+
134
+
135
+ # Create a Litestar app instance - this is the main app that can be run directly
136
+ app = Litestar(
137
+ route_handlers=[DemoController]
138
+ )
@@ -0,0 +1,53 @@
1
+ """
2
+ Litestar embedding example for fastapi-voyager.
3
+
4
+ This module demonstrates how to integrate voyager with a Litestar application.
5
+ """
6
+ from fastapi_voyager import create_voyager
7
+ from tests.litestar.demo import app, diagram
8
+
9
+ # Create voyager app for visualization
10
+ # Note: create_voyager automatically detects Litestar and returns a Litestar app
11
+ voyager_app = create_voyager(
12
+ app,
13
+ er_diagram=diagram,
14
+ module_color={"tests.service": "purple"},
15
+ module_prefix="tests.service",
16
+ swagger_url="/schema/swagger",
17
+ initial_page_policy='first',
18
+ ga_id='G-R64S7Q49VL',
19
+ online_repo_url="https://github.com/allmonday/fastapi-voyager/blob/main",
20
+ enable_pydantic_resolve_meta=True
21
+ )
22
+
23
+ # ASGI app that routes between main app and voyager
24
+ # This allows voyager to be accessed at /voyager while the main app handles other routes
25
+ async def asgi_app(scope, receive, send):
26
+ """
27
+ ASGI app that routes between main app and voyager.
28
+
29
+ Usage:
30
+ uvicorn tests.litestar.embedding:asgi_app --reload
31
+
32
+ Then access:
33
+ - http://localhost:8000/demo/* for the main app
34
+ - http://localhost:8000/voyager for voyager UI
35
+ """
36
+ if scope["type"] == "http" and scope["path"].startswith("/voyager"):
37
+ # Forward to voyager app
38
+ # Remove /voyager prefix for the voyager app (it expects root path)
39
+ new_scope = dict(scope)
40
+ new_scope["path"] = scope["path"][8:] # Remove '/voyager'
41
+ if new_scope["path"] == "":
42
+ new_scope["path"] = "/"
43
+ if "raw_path" in new_scope:
44
+ new_scope["raw_path"] = scope["raw_path"][8:]
45
+ await voyager_app(new_scope, receive, send)
46
+ else:
47
+ # Forward to main app
48
+ await app(scope, receive, send)
49
+
50
+ # Exports
51
+ # - Use `uvicorn tests.litestar.embedding:asgi_app --reload` for combined app (main + voyager at /voyager)
52
+ # - Use `uvicorn tests.litestar.embedding:app --reload` for main app only
53
+ # - Use `uvicorn tests.litestar.embedding:voyager_app --reload` for voyager only