fastapi-voyager 0.16.1__tar.gz → 0.17.0__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 (114) hide show
  1. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/PKG-INFO +1 -1
  2. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/docs/changelog.md +17 -3
  3. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/adapters/common.py +40 -1
  4. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/adapters/litestar_adapter.py +1 -1
  5. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/er_diagram.py +17 -8
  6. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/render.py +7 -2
  7. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/render_style.py +13 -0
  8. fastapi_voyager-0.17.0/src/fastapi_voyager/templates/html/schema_header.j2 +1 -0
  9. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/type.py +1 -0
  10. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/type_helper.py +20 -0
  11. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/version.py +1 -1
  12. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/voyager.py +28 -11
  13. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/index.html +13 -6
  14. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/store.js +5 -0
  15. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/vue-main.js +17 -3
  16. fastapi_voyager-0.16.1/src/fastapi_voyager/templates/html/schema_header.j2 +0 -2
  17. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.githooks/README.md +0 -0
  18. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.githooks/pre-commit +0 -0
  19. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  20. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  21. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.github/workflows/publish.yml +0 -0
  22. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.gitignore +0 -0
  23. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.prettierignore +0 -0
  24. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.prettierrc +0 -0
  25. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/.python-version +0 -0
  26. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/CONTRIBUTING.md +0 -0
  27. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/LICENSE +0 -0
  28. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/README.md +0 -0
  29. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/docs/claude/0_REFACTORING_RENDER_NOTES.md +0 -0
  30. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/docs/idea.md +0 -0
  31. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/package-lock.json +0 -0
  32. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/pyproject.toml +0 -0
  33. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/release.md +0 -0
  34. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/setup-django-ninja.sh +0 -0
  35. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/setup-fastapi.sh +0 -0
  36. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/setup-hooks.sh +0 -0
  37. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/setup-litestar.sh +0 -0
  38. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/__init__.py +0 -0
  39. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/adapters/__init__.py +0 -0
  40. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/adapters/base.py +0 -0
  41. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/adapters/django_ninja_adapter.py +0 -0
  42. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/adapters/fastapi_adapter.py +0 -0
  43. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/cli.py +0 -0
  44. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/filter.py +0 -0
  45. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/introspectors/__init__.py +0 -0
  46. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/introspectors/base.py +0 -0
  47. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/introspectors/detector.py +0 -0
  48. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/introspectors/django_ninja.py +0 -0
  49. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/introspectors/fastapi.py +0 -0
  50. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/introspectors/litestar.py +0 -0
  51. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/module.py +0 -0
  52. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/pydantic_resolve_util.py +0 -0
  53. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/server.py +0 -0
  54. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/dot/cluster.j2 +0 -0
  55. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/dot/cluster_container.j2 +0 -0
  56. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/dot/digraph.j2 +0 -0
  57. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/dot/er_diagram.j2 +0 -0
  58. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/dot/link.j2 +0 -0
  59. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/dot/route_node.j2 +0 -0
  60. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/dot/schema_node.j2 +0 -0
  61. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/dot/tag_node.j2 +0 -0
  62. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/html/colored_text.j2 +0 -0
  63. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/html/pydantic_meta.j2 +0 -0
  64. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/html/schema_field_row.j2 +0 -0
  65. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/templates/html/schema_table.j2 +0 -0
  66. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/component/demo.js +0 -0
  67. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/component/render-graph.js +0 -0
  68. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/component/route-code-display.js +0 -0
  69. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/component/schema-code-display.js +0 -0
  70. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/graph-ui.js +0 -0
  71. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
  72. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
  73. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
  74. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
  75. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
  76. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
  77. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
  78. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
  79. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
  80. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/quasar.min.css +0 -0
  81. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/src/fastapi_voyager/web/quasar.min.js +0 -0
  82. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/README.md +0 -0
  83. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/__init__.py +0 -0
  84. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/django_ninja/__init__.py +0 -0
  85. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/django_ninja/demo.py +0 -0
  86. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/django_ninja/embedding.py +0 -0
  87. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/django_ninja/settings.py +0 -0
  88. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/django_ninja/urls.py +0 -0
  89. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/embedding_test_utils.py +0 -0
  90. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/fastapi/__init__.py +0 -0
  91. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/fastapi/demo.py +0 -0
  92. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/fastapi/demo_anno.py +0 -0
  93. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/fastapi/embedding.py +0 -0
  94. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/litestar/__init__.py +0 -0
  95. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/litestar/demo.py +0 -0
  96. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/litestar/embedding.py +0 -0
  97. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/service/__init__.py +0 -0
  98. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/service/schema/__init__.py +0 -0
  99. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/service/schema/base_entity.py +0 -0
  100. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/service/schema/extra.py +0 -0
  101. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/service/schema/schema.py +0 -0
  102. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_adapter_interface.py +0 -0
  103. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_analysis.py +0 -0
  104. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_embedding_django_ninja.py +0 -0
  105. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_embedding_fastapi.py +0 -0
  106. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_embedding_litestar.py +0 -0
  107. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_filter.py +0 -0
  108. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_generic.py +0 -0
  109. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_import.py +0 -0
  110. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_module.py +0 -0
  111. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_resolve_util_impl.py +0 -0
  112. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/tests/test_type_helper.py +0 -0
  113. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/uv.lock +0 -0
  114. {fastapi_voyager-0.16.1 → fastapi_voyager-0.17.0}/voyager.jpg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-voyager
3
- Version: 0.16.1
3
+ Version: 0.17.0
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
@@ -183,14 +183,28 @@
183
183
  - [x] fix import error
184
184
  - 0.16.0alpha-3
185
185
  - [x] fix voyager cli, add web parameter
186
+ - 0.16.1
187
+ - [x] improve litestar support
186
188
 
187
189
  ## 0.17, enhance er diagram
188
190
  - 0.17.0
189
- - [ ] show loader name
190
- - [ ] show relationship list when double click entity in er diagram
191
- - [ ] highlight entity in use case
191
+ - [x] 1.different theme color for frameworks
192
+ - fastapi, keep current
193
+ - django-ninja, #4cae4f
194
+ - litestar, rgb(237, 182, 65)
195
+ - [x] 2.highight entity classes
196
+ - enable if er diagram is enabled
197
+ - entities in er diagram should be labeled as "Entity" after the title, and title should be bold
198
+ - [x] 3.click esc to cancel search
199
+ - 0.17.1
200
+ - [ ] 1.show loader name
201
+ - [ ] 2.show relationship list when double click entity in er diagram
202
+ - [ ] 3.highlight entity in use case
203
+ - [ ] 4.change cli -m param, use `path.to.module:app` instead.
192
204
 
193
205
  ## 1.0, release
194
206
  - [ ] add tests
195
207
 
208
+ ## 1.1 future
209
+
196
210
 
@@ -9,7 +9,9 @@ from typing import Any
9
9
  from pydantic_resolve import ErDiagram
10
10
 
11
11
  from fastapi_voyager.er_diagram import VoyagerErDiagram
12
+ from fastapi_voyager.introspectors.detector import FrameworkType, detect_framework
12
13
  from fastapi_voyager.render import Renderer
14
+ from fastapi_voyager.render_style import RenderConfig
13
15
  from fastapi_voyager.type import CoreData, SchemaNode, Tag
14
16
  from fastapi_voyager.type_helper import get_source, get_vscode_link
15
17
  from fastapi_voyager.version import __version__
@@ -23,6 +25,7 @@ STATIC_FILES_PATH = "/fastapi-voyager-static"
23
25
  GA_PLACEHOLDER = "<!-- GA_SNIPPET -->"
24
26
  VERSION_PLACEHOLDER = "<!-- VERSION_PLACEHOLDER -->"
25
27
  STATIC_PATH_PLACEHOLDER = "<!-- STATIC_PATH -->"
28
+ THEME_COLOR_PLACEHOLDER = "<!-- THEME_COLOR -->"
26
29
 
27
30
 
28
31
  def build_ga_snippet(ga_id: str | None) -> str:
@@ -70,13 +73,45 @@ class VoyagerContext:
70
73
  self.ga_id = ga_id
71
74
  self.er_diagram = er_diagram
72
75
  self.enable_pydantic_resolve_meta = enable_pydantic_resolve_meta
73
- self.framework_name = framework_name or "API"
76
+
77
+ # Detect and store framework type (single source of truth)
78
+ self._framework_type = detect_framework(target_app)
79
+ # Display name for frontend (backward compatible)
80
+ self.framework_name = framework_name or self._get_display_name()
81
+
82
+ def _get_display_name(self) -> str:
83
+ """Get display name for the detected framework type."""
84
+ display_names = {
85
+ FrameworkType.FASTAPI: "FastAPI",
86
+ FrameworkType.DJANGO_NINJA: "Django Ninja",
87
+ FrameworkType.LITESTAR: "Litestar",
88
+ }
89
+ return display_names.get(self._framework_type, "API")
90
+
91
+ def _get_theme_color(self) -> str:
92
+ """Get theme color for the current framework."""
93
+ config = RenderConfig()
94
+ return config.colors.get_framework_color(self._framework_type)
95
+
96
+ def _get_entity_class_names(self) -> set[str] | None:
97
+ """Extract entity class names from er_diagram."""
98
+ if not self.er_diagram:
99
+ return None
100
+
101
+ from fastapi_voyager.type_helper import full_class_name
102
+
103
+ return {
104
+ full_class_name(entity.kls)
105
+ for entity in self.er_diagram.configs
106
+ }
74
107
 
75
108
  def get_voyager(self, **kwargs) -> Voyager:
76
109
  """Create a Voyager instance with common configuration."""
77
110
  config = {
78
111
  "module_color": self.module_color,
79
112
  "show_pydantic_resolve_meta": self.enable_pydantic_resolve_meta,
113
+ "theme_color": self._get_theme_color(),
114
+ "entity_class_names": self._get_entity_class_names(),
80
115
  }
81
116
  config.update(kwargs)
82
117
  return Voyager(**config)
@@ -179,6 +214,7 @@ class VoyagerContext:
179
214
  show_fields=core_data.show_fields,
180
215
  module_color=core_data.module_color,
181
216
  schema=core_data.schema,
217
+ theme_color=self._get_theme_color(),
182
218
  )
183
219
  return renderer.render_dot(
184
220
  core_data.tags, core_data.routes, core_data.nodes, core_data.links
@@ -191,6 +227,7 @@ class VoyagerContext:
191
227
  self.er_diagram,
192
228
  show_fields=payload.get("show_fields", "object"),
193
229
  show_module=payload.get("show_module", True),
230
+ theme_color=self._get_theme_color(),
194
231
  ).render_dot()
195
232
  return ""
196
233
 
@@ -203,6 +240,8 @@ class VoyagerContext:
203
240
  content = content.replace(VERSION_PLACEHOLDER, f"?v={__version__}")
204
241
  # Replace static files path placeholder with actual path (without leading slash)
205
242
  content = content.replace(STATIC_PATH_PLACEHOLDER, STATIC_FILES_PATH.lstrip("/"))
243
+ # Replace theme color placeholder with framework-specific color
244
+ content = content.replace(THEME_COLOR_PLACEHOLDER, self._get_theme_color())
206
245
  return content
207
246
  # fallback simple page if index.html missing
208
247
  return """
@@ -53,7 +53,7 @@ class LitestarAdapter(VoyagerAdapter):
53
53
  from litestar import Litestar, MediaType, Request, Response, get, post
54
54
  from litestar.static_files import create_static_files_router
55
55
 
56
- @get("/er-diagram")
56
+ @post("/er-diagram")
57
57
  async def get_er_diagram(request: Request) -> str:
58
58
  payload = await request.json()
59
59
  return self.ctx.get_er_diagram_dot(payload)
@@ -38,13 +38,15 @@ class DiagramRenderer(Renderer):
38
38
  self,
39
39
  *,
40
40
  show_fields: FieldType = 'single',
41
- show_module: bool = True
41
+ show_module: bool = True,
42
+ theme_color: str | None = None
42
43
  ) -> None:
43
44
  # Initialize parent Renderer with shared config
44
45
  super().__init__(
45
46
  show_fields=show_fields,
46
47
  show_module=show_module,
47
- config=RenderConfig() # Use unified style configuration
48
+ config=RenderConfig(), # Use unified style configuration
49
+ theme_color=theme_color,
48
50
  )
49
51
  logger.info(f'show_module: {self.show_module}')
50
52
 
@@ -99,10 +101,11 @@ class DiagramRenderer(Renderer):
99
101
 
100
102
 
101
103
  class VoyagerErDiagram:
102
- def __init__(self,
103
- er_diagram: ErDiagram,
104
+ def __init__(self,
105
+ er_diagram: ErDiagram,
104
106
  show_fields: FieldType = 'single',
105
- show_module: bool = False):
107
+ show_module: bool = False,
108
+ theme_color: str | None = None):
106
109
 
107
110
  self.er_diagram = er_diagram
108
111
  self.nodes: list[SchemaNode] = []
@@ -115,6 +118,7 @@ class VoyagerErDiagram:
115
118
 
116
119
  self.show_field = show_fields
117
120
  self.show_module = show_module
121
+ self.theme_color = theme_color
118
122
 
119
123
  def generate_node_head(self, link_name: str):
120
124
  return f'{link_name}::{PK}'
@@ -164,10 +168,11 @@ class VoyagerErDiagram:
164
168
  if full_name not in self.node_set:
165
169
  # skip meta info for normal queries
166
170
  self.node_set[full_name] = SchemaNode(
167
- id=full_name,
171
+ id=full_name,
168
172
  module=schema.__module__,
169
173
  name=schema.__name__,
170
- fields=get_fields(schema, fk_set)
174
+ fields=get_fields(schema, fk_set),
175
+ is_entity=False # Don't mark in ER diagram
171
176
  )
172
177
  return full_name
173
178
 
@@ -209,7 +214,11 @@ class VoyagerErDiagram:
209
214
 
210
215
  for entity in self.er_diagram.configs:
211
216
  self.analysis_entity(entity)
212
- renderer = DiagramRenderer(show_fields=self.show_field, show_module=self.show_module)
217
+ renderer = DiagramRenderer(
218
+ show_fields=self.show_field,
219
+ show_module=self.show_module,
220
+ theme_color=self.theme_color
221
+ )
213
222
  return renderer.render_dot(list(self.node_set.values()), self.links)
214
223
 
215
224
 
@@ -63,6 +63,7 @@ class Renderer:
63
63
  show_module: bool = True,
64
64
  show_pydantic_resolve_meta: bool = False,
65
65
  config: RenderConfig | None = None,
66
+ theme_color: str | None = None,
66
67
  ) -> None:
67
68
  self.show_fields = show_fields if show_fields in ('single', 'object', 'all') else 'single'
68
69
  self.module_color = module_color or {}
@@ -75,6 +76,9 @@ class Renderer:
75
76
  self.colors = self.config.colors
76
77
  self.style = self.config.style
77
78
 
79
+ # Framework theme color (overrides default primary color)
80
+ self.theme_color = theme_color or self.colors.primary
81
+
78
82
  # Initialize template renderer
79
83
  self.template_renderer = TemplateRenderer()
80
84
 
@@ -228,7 +232,7 @@ class Renderer:
228
232
  rows.append(self._render_schema_field(field))
229
233
 
230
234
  # Determine header color
231
- default_color = self.colors.primary if color is None else color
235
+ default_color = self.theme_color if color is None else color
232
236
  header_color = self.colors.highlight if node.id == self.schema else default_color
233
237
 
234
238
  # Render header
@@ -236,7 +240,8 @@ class Renderer:
236
240
  'html/schema_header.j2',
237
241
  text=node.name,
238
242
  bg_color=header_color,
239
- port=PK
243
+ port=PK,
244
+ is_entity=node.is_entity
240
245
  )
241
246
 
242
247
  # Render complete table
@@ -3,11 +3,20 @@ Style constants and configuration for rendering DOT graphs and HTML tables.
3
3
  """
4
4
  from dataclasses import dataclass, field
5
5
 
6
+ from fastapi_voyager.introspectors.detector import FrameworkType
7
+
6
8
 
7
9
  @dataclass
8
10
  class ColorScheme:
9
11
  """Color scheme for graph visualization."""
10
12
 
13
+ # Framework-specific theme colors (single source of truth)
14
+ FRAMEWORK_COLORS: dict[FrameworkType, str] = field(default_factory=lambda: {
15
+ FrameworkType.FASTAPI: '#009485',
16
+ FrameworkType.DJANGO_NINJA: '#4cae4f',
17
+ FrameworkType.LITESTAR: '#edb641',
18
+ })
19
+
11
20
  # Node colors
12
21
  primary: str = '#009485'
13
22
  highlight: str = 'tomato'
@@ -30,6 +39,10 @@ class ColorScheme:
30
39
  # Text colors
31
40
  text_gray: str = '#999'
32
41
 
42
+ def get_framework_color(self, framework_type: FrameworkType) -> str:
43
+ """Get theme color for a specific framework type."""
44
+ return self.FRAMEWORK_COLORS.get(framework_type, self.primary)
45
+
33
46
 
34
47
  @dataclass
35
48
  class GraphvizStyle:
@@ -0,0 +1 @@
1
+ <tr><td cellpadding="6" bgcolor="{{ bg_color }}" align="center" colspan="1" {% if port %}port="{{ port }}"{% endif %}><font color="white">{% if is_entity %}<b>{{ text }} (E)</b>{% else %}{{ text }}{% endif %}</font></td></tr>
@@ -48,6 +48,7 @@ class ModuleRoute:
48
48
  class SchemaNode(NodeBase):
49
49
  module: str
50
50
  fields: list[FieldInfo] = field(default_factory=list)
51
+ is_entity: bool = False # Mark if this is an ER diagram entity
51
52
 
52
53
  @dataclass
53
54
  class ModuleNode:
@@ -29,6 +29,26 @@ def full_class_name(cls):
29
29
  return f"{cls.__module__}.{cls.__qualname__}"
30
30
 
31
31
 
32
+ def is_base_entity_subclass(schema, entity_class_names: set[str] | None = None) -> bool:
33
+ """
34
+ Check if a schema is a pydantic-resolve BaseEntity entity.
35
+
36
+ Checks if the class's full name is in the entity_class_names set.
37
+
38
+ Args:
39
+ schema: The schema class to check
40
+ entity_class_names: Optional set of full class names from er_diagram.configs
41
+
42
+ Returns:
43
+ True if the schema is an entity, False otherwise
44
+ """
45
+ if not entity_class_names:
46
+ return False
47
+
48
+ schema_full_name = full_class_name(schema)
49
+ return schema_full_name in entity_class_names
50
+
51
+
32
52
  def get_core_types(tp):
33
53
  """
34
54
  - get the core type
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.16.1"
2
+ __version__ = "0.17.0"
@@ -16,6 +16,7 @@ from fastapi_voyager.type_helper import (
16
16
  get_core_types,
17
17
  get_pydantic_fields,
18
18
  get_type_name,
19
+ is_base_entity_subclass,
19
20
  is_inheritance_of_pydantic_base,
20
21
  is_non_pydantic_type,
21
22
  update_forward_refs,
@@ -24,8 +25,8 @@ from fastapi_voyager.type_helper import (
24
25
 
25
26
  class Voyager:
26
27
  def __init__(
27
- self,
28
- schema: str | None = None,
28
+ self,
29
+ schema: str | None = None,
29
30
  schema_field: str | None = None,
30
31
  show_fields: FieldType = 'single',
31
32
  include_tags: list[str] | None = None,
@@ -34,6 +35,8 @@ class Voyager:
34
35
  hide_primitive_route: bool = False,
35
36
  show_module: bool = True,
36
37
  show_pydantic_resolve_meta: bool = False,
38
+ theme_color: str | None = None,
39
+ entity_class_names: set[str] | None = None,
37
40
  ):
38
41
 
39
42
  self.routes: list[Route] = []
@@ -57,6 +60,8 @@ class Voyager:
57
60
  self.hide_primitive_route = hide_primitive_route
58
61
  self.show_module = show_module
59
62
  self.show_pydantic_resolve_meta = show_pydantic_resolve_meta
63
+ self.theme_color = theme_color
64
+ self.entity_class_names = entity_class_names
60
65
 
61
66
  def _get_introspector(self, app) -> AppIntrospector:
62
67
  """
@@ -189,7 +194,7 @@ class Voyager:
189
194
  """
190
195
  full_name = full_class_name(schema)
191
196
  bases_fields = get_bases_fields([s for s in schema.__bases__ if is_inheritance_of_pydantic_base(s)])
192
-
197
+
193
198
  subset_reference = getattr(schema, const.ENSURE_SUBSET_REFERENCE, None)
194
199
  if subset_reference and is_inheritance_of_pydantic_base(subset_reference):
195
200
  bases_fields.update(get_bases_fields([subset_reference]))
@@ -197,10 +202,11 @@ class Voyager:
197
202
  if full_name not in self.node_set:
198
203
  # skip meta info for normal queries
199
204
  self.node_set[full_name] = SchemaNode(
200
- id=full_name,
205
+ id=full_name,
201
206
  module=schema.__module__,
202
207
  name=schema.__name__,
203
- fields=get_pydantic_fields(schema, bases_fields)
208
+ fields=get_pydantic_fields(schema, bases_fields),
209
+ is_entity=is_base_entity_subclass(schema, self.entity_class_names)
204
210
  )
205
211
  return full_name
206
212
 
@@ -351,14 +357,15 @@ class Voyager:
351
357
 
352
358
  renderer = Renderer(
353
359
  show_fields=self.show_fields,
354
- module_color=self.module_color,
355
- schema=self.schema,
360
+ module_color=self.module_color,
361
+ schema=self.schema,
356
362
  show_module=self.show_module,
357
- show_pydantic_resolve_meta=self.show_pydantic_resolve_meta)
363
+ show_pydantic_resolve_meta=self.show_pydantic_resolve_meta,
364
+ theme_color=self.theme_color)
358
365
 
359
366
  _tags, _routes, _links = self.handle_hide(_tags, _routes, _links)
360
367
  return renderer.render_dot(_tags, _routes, _nodes, _links)
361
-
368
+
362
369
 
363
370
  def render_tag_level_brief_dot(self, module_prefix: str | None = None):
364
371
  _tags, _routes, _nodes, _links = filter_graph(
@@ -379,7 +386,12 @@ class Voyager:
379
386
  links=_links,
380
387
  )
381
388
 
382
- renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema, show_module=self.show_module)
389
+ renderer = Renderer(
390
+ show_fields=self.show_fields,
391
+ module_color=self.module_color,
392
+ schema=self.schema,
393
+ show_module=self.show_module,
394
+ theme_color=self.theme_color)
383
395
 
384
396
  _tags, _routes, _links = self.handle_hide(_tags, _routes, _links)
385
397
  return renderer.render_dot(_tags, _routes, _nodes, _links, True)
@@ -403,7 +415,12 @@ class Voyager:
403
415
  links=_links,
404
416
  )
405
417
 
406
- renderer = Renderer(show_fields=self.show_fields, module_color=self.module_color, schema=self.schema, show_module=self.show_module)
418
+ renderer = Renderer(
419
+ show_fields=self.show_fields,
420
+ module_color=self.module_color,
421
+ schema=self.schema,
422
+ show_module=self.show_module,
423
+ theme_color=self.theme_color)
407
424
 
408
425
  _tags, _routes, _links = self.handle_hide(_tags, _routes, _links)
409
426
  return renderer.render_dot(_tags, _routes, _nodes, _links, True)
@@ -35,6 +35,9 @@
35
35
  </head>
36
36
 
37
37
  <style>
38
+ :root {
39
+ --q-primary: <!-- THEME_COLOR -->;
40
+ }
38
41
  html,
39
42
  body {
40
43
  height: 100%;
@@ -112,7 +115,7 @@
112
115
  width: 32px;
113
116
  height: 32px;
114
117
  border-radius: 50%;
115
- background: #009485;
118
+ background: var(--q-primary);
116
119
  color: white;
117
120
  cursor: pointer;
118
121
  display: flex;
@@ -122,7 +125,7 @@
122
125
  transition: all 0.2s ease;
123
126
  }
124
127
  .tag-navigator-collapse-btn-right:hover {
125
- background: #007a6d;
128
+ filter: brightness(0.9);
126
129
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
127
130
  }
128
131
 
@@ -144,7 +147,7 @@
144
147
  Helvetica,
145
148
  Arial,
146
149
  sans-serif;
147
- color: #009485;
150
+ color: var(--q-primary);
148
151
  }
149
152
  .loading-text {
150
153
  font-size: 14px;
@@ -153,7 +156,7 @@
153
156
  width: 20px;
154
157
  height: 20px;
155
158
  border: 2px solid rgba(0, 148, 133, 0.2);
156
- border-top-color: #009485;
159
+ border-top-color: var(--q-primary);
157
160
  border-radius: 50%;
158
161
  animation: frv-spin 0.8s linear infinite;
159
162
  }
@@ -171,6 +174,10 @@
171
174
  }
172
175
  </style>
173
176
  <body class="app-loading">
177
+ <script>
178
+ // Framework theme color for JS components
179
+ window.FRAMEWORK_THEME_COLOR = "<!-- THEME_COLOR -->"
180
+ </script>
174
181
  <!-- App boot loading overlay: shown until JS initializes -->
175
182
  <div id="app-loading-overlay" aria-busy="true" aria-live="polite">
176
183
  <div class="spinner" aria-hidden="true"></div>
@@ -181,7 +188,7 @@
181
188
  <q-header bordered class="bg-primary text-white">
182
189
  <q-toolbar
183
190
  class="row text-grey-9 bg-white"
184
- style="width: 100%; border-bottom: 2px solid #009485"
191
+ style="width: 100%; border-bottom: 2px solid var(--q-primary)"
185
192
  >
186
193
  <div
187
194
  class="col-auto text-primary"
@@ -313,7 +320,7 @@
313
320
  height="52"
314
321
  viewBox="0 0 250 250"
315
322
  style="
316
- fill: #009485;
323
+ fill: <!-- THEME_COLOR -->;
317
324
  color: #fff;
318
325
  position: absolute;
319
326
  top: 0;
@@ -521,6 +521,11 @@ const actions = {
521
521
  */
522
522
  resetSearchState() {
523
523
  state.search.mode = false
524
+ // Clear search schema and field selection
525
+ state.search.schemaName = null
526
+ state.search.fieldName = null
527
+ state.search.fieldOptions = []
528
+
524
529
  const hadPreviousValue = state.previousTagRoute.hasValue
525
530
 
526
531
  if (hadPreviousValue) {
@@ -5,7 +5,7 @@ import RenderGraph from "./component/render-graph.js"
5
5
  import { GraphUI } from "./graph-ui.js"
6
6
  import { store } from "./store.js"
7
7
 
8
- const { createApp, onMounted, ref, watch } = window.Vue
8
+ const { createApp, onMounted, onUnmounted, ref, watch } = window.Vue
9
9
 
10
10
  // Load toggle states from localStorage
11
11
  function loadToggleState(key, defaultValue = false) {
@@ -296,6 +296,19 @@ const app = createApp({
296
296
  document.title = `${store.state.framework_name} Voyager`
297
297
  }
298
298
  // Reveal app content only after initial JS/data is ready
299
+
300
+ // Add keyboard event listener for ESC to cancel search
301
+ const handleKeyDown = (event) => {
302
+ if (event.key === "Escape" && store.state.search.mode) {
303
+ resetSearch()
304
+ }
305
+ }
306
+ document.addEventListener("keydown", handleKeyDown)
307
+
308
+ // Cleanup on unmount
309
+ onUnmounted(() => {
310
+ document.removeEventListener("keydown", handleKeyDown)
311
+ })
299
312
  })
300
313
 
301
314
  return {
@@ -324,9 +337,10 @@ const app = createApp({
324
337
 
325
338
  app.use(window.Quasar)
326
339
 
327
- // Set Quasar primary theme color to green
340
+ // Set Quasar primary theme color from framework configuration
328
341
  if (window.Quasar && typeof window.Quasar.setCssVar === "function") {
329
- window.Quasar.setCssVar("primary", "#009485")
342
+ const themeColor = window.FRAMEWORK_THEME_COLOR || "#009485"
343
+ window.Quasar.setCssVar("primary", themeColor)
330
344
  }
331
345
 
332
346
  app.component("schema-code-display", SchemaCodeDisplay) // double click to see node details
@@ -1,2 +0,0 @@
1
- <tr><td cellpadding="6" bgcolor="{{ bg_color }}" align="center" colspan="1" {% if port %}port="{{ port }}"{% endif %}>
2
- <font color="white"> {{ text }} </font></td></tr>