vega-framework 0.2.1__tar.gz → 0.2.2__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 (102) hide show
  1. {vega_framework-0.2.1 → vega_framework-0.2.2}/PKG-INFO +1 -1
  2. {vega_framework-0.2.1 → vega_framework-0.2.2}/pyproject.toml +1 -1
  3. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/__init__.py +8 -0
  4. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/application.py +63 -0
  5. vega_framework-0.2.2/vega/web/docs.py +104 -0
  6. vega_framework-0.2.2/vega/web/openapi.py +292 -0
  7. {vega_framework-0.2.1 → vega_framework-0.2.2}/LICENSE +0 -0
  8. {vega_framework-0.2.1 → vega_framework-0.2.2}/README.md +0 -0
  9. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/__init__.py +0 -0
  10. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/__init__.py +0 -0
  11. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/commands/__init__.py +0 -0
  12. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/commands/add.py +0 -0
  13. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/commands/generate.py +0 -0
  14. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/commands/init.py +0 -0
  15. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/commands/migrate.py +0 -0
  16. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/commands/update.py +0 -0
  17. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/commands/web.py +0 -0
  18. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/main.py +0 -0
  19. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/scaffolds/__init__.py +0 -0
  20. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/scaffolds/fastapi.py +0 -0
  21. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/scaffolds/sqlalchemy.py +0 -0
  22. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/scaffolds/vega_web.py +0 -0
  23. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/__init__.py +0 -0
  24. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/cli/command.py.j2 +0 -0
  25. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/cli/command_simple.py.j2 +0 -0
  26. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/cli/commands_init.py.j2 +0 -0
  27. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/components.py +0 -0
  28. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/domain/entity.py.j2 +0 -0
  29. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/domain/event.py.j2 +0 -0
  30. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/domain/event_handler.py.j2 +0 -0
  31. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/domain/interactor.py.j2 +0 -0
  32. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/domain/mediator.py.j2 +0 -0
  33. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/domain/repository_interface.py.j2 +0 -0
  34. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/domain/service_interface.py.j2 +0 -0
  35. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/infrastructure/model.py.j2 +0 -0
  36. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/infrastructure/repository_impl.py.j2 +0 -0
  37. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/infrastructure/service_impl.py.j2 +0 -0
  38. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/loader.py +0 -0
  39. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/.env.example +0 -0
  40. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/.gitignore +0 -0
  41. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/ARCHITECTURE.md.j2 +0 -0
  42. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/README.md.j2 +0 -0
  43. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/config.py.j2 +0 -0
  44. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/events_init.py.j2 +0 -0
  45. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/main.py.j2 +0 -0
  46. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/main_fastapi.py.j2 +0 -0
  47. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/main_standard.py.j2 +0 -0
  48. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/pyproject.toml.j2 +0 -0
  49. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/project/settings.py.j2 +0 -0
  50. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/sqlalchemy/alembic.ini.j2 +0 -0
  51. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/sqlalchemy/database_manager.py.j2 +0 -0
  52. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/sqlalchemy/env.py.j2 +0 -0
  53. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/sqlalchemy/script.py.mako +0 -0
  54. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/__init__.py.j2 +0 -0
  55. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/app.py.j2 +0 -0
  56. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/health_route.py.j2 +0 -0
  57. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/main.py.j2 +0 -0
  58. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/middleware.py.j2 +0 -0
  59. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/models_init.py.j2 +0 -0
  60. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/request_model.py.j2 +0 -0
  61. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/response_model.py.j2 +0 -0
  62. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/router.py.j2 +0 -0
  63. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/routes_init.py.j2 +0 -0
  64. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/routes_init_autodiscovery.py.j2 +0 -0
  65. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/user_models.py.j2 +0 -0
  66. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/templates/web/users_route.py.j2 +0 -0
  67. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/utils/__init__.py +0 -0
  68. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/utils/async_support.py +0 -0
  69. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/utils/messages.py +0 -0
  70. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/utils/naming.py +0 -0
  71. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/cli/utils/validators.py +0 -0
  72. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/di/__init__.py +0 -0
  73. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/di/container.py +0 -0
  74. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/di/decorators.py +0 -0
  75. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/di/errors.py +0 -0
  76. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/di/scope.py +0 -0
  77. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/discovery/__init__.py +0 -0
  78. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/discovery/commands.py +0 -0
  79. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/discovery/events.py +0 -0
  80. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/discovery/routes.py +0 -0
  81. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/events/README.md +0 -0
  82. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/events/SYNTAX_GUIDE.md +0 -0
  83. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/events/__init__.py +0 -0
  84. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/events/bus.py +0 -0
  85. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/events/decorators.py +0 -0
  86. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/events/event.py +0 -0
  87. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/events/middleware.py +0 -0
  88. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/patterns/__init__.py +0 -0
  89. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/patterns/interactor.py +0 -0
  90. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/patterns/mediator.py +0 -0
  91. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/patterns/repository.py +0 -0
  92. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/patterns/service.py +0 -0
  93. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/settings/__init__.py +0 -0
  94. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/settings/base.py +0 -0
  95. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/builtin_middlewares.py +0 -0
  96. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/exceptions.py +0 -0
  97. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/middleware.py +0 -0
  98. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/request.py +0 -0
  99. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/response.py +0 -0
  100. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/route_middleware.py +0 -0
  101. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/router.py +0 -0
  102. {vega_framework-0.2.1 → vega_framework-0.2.2}/vega/web/routing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vega-framework
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Enterprise-ready Python framework that enforces Clean Architecture for building maintainable and scalable applications.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "vega-framework"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Enterprise-ready Python framework that enforces Clean Architecture for building maintainable and scalable applications."
5
5
  authors = ["Roberto Ferro"]
6
6
  license = "MIT"
@@ -66,6 +66,10 @@ from .route_middleware import (
66
66
  middleware,
67
67
  )
68
68
 
69
+ # OpenAPI / Documentation
70
+ from .openapi import get_openapi_schema
71
+ from .docs import get_swagger_ui_html, get_redoc_html
72
+
69
73
  __all__ = [
70
74
  # Version
71
75
  "__version__",
@@ -97,4 +101,8 @@ __all__ = [
97
101
  "RouteMiddleware",
98
102
  "MiddlewarePhase",
99
103
  "middleware",
104
+ # OpenAPI / Docs
105
+ "get_openapi_schema",
106
+ "get_swagger_ui_html",
107
+ "get_redoc_html",
100
108
  ]
@@ -7,10 +7,13 @@ from starlette.middleware import Middleware
7
7
  from starlette.middleware.cors import CORSMiddleware
8
8
  from starlette.routing import Mount, Route as StarletteRoute
9
9
  from starlette.types import ASGIApp
10
+ from starlette.responses import JSONResponse as StarletteJSONResponse
10
11
 
11
12
  from .router import Router
12
13
  from .exceptions import HTTPException
13
14
  from .response import JSONResponse
15
+ from .openapi import get_openapi_schema
16
+ from .docs import get_swagger_ui_html, get_redoc_html
14
17
 
15
18
 
16
19
  class VegaApp:
@@ -59,12 +62,20 @@ class VegaApp:
59
62
  middleware: Optional[Sequence[Middleware]] = None,
60
63
  on_startup: Optional[Sequence[Callable]] = None,
61
64
  on_shutdown: Optional[Sequence[Callable]] = None,
65
+ docs_url: Optional[str] = "/docs",
66
+ redoc_url: Optional[str] = "/redoc",
67
+ openapi_url: Optional[str] = "/openapi.json",
62
68
  ):
63
69
  self.title = title
64
70
  self.description = description
65
71
  self.version = version
66
72
  self.debug = debug
67
73
 
74
+ # Documentation URLs (None to disable)
75
+ self.docs_url = docs_url
76
+ self.redoc_url = redoc_url
77
+ self.openapi_url = openapi_url
78
+
68
79
  # Internal router for top-level routes
69
80
  self._router = Router()
70
81
 
@@ -78,6 +89,9 @@ class VegaApp:
78
89
  # Starlette app (created lazily)
79
90
  self._starlette_app: Optional[Starlette] = None
80
91
 
92
+ # OpenAPI schema (cached)
93
+ self._openapi_schema: Optional[Dict[str, Any]] = None
94
+
81
95
  def add_middleware(
82
96
  self,
83
97
  middleware_class: type,
@@ -197,6 +211,22 @@ class VegaApp:
197
211
  """
198
212
  return self._router.route(path, methods, **kwargs)
199
213
 
214
+ def openapi(self) -> Dict[str, Any]:
215
+ """
216
+ Generate and return the OpenAPI schema.
217
+
218
+ Returns:
219
+ OpenAPI schema dictionary
220
+ """
221
+ if self._openapi_schema is None:
222
+ self._openapi_schema = get_openapi_schema(
223
+ title=self.title,
224
+ version=self.version,
225
+ description=self.description,
226
+ routes=self._router.get_routes(),
227
+ )
228
+ return self._openapi_schema
229
+
200
230
  def _build_starlette_app(self) -> Starlette:
201
231
  """Build the Starlette application from routes and middleware."""
202
232
  # Convert Vega routes to Starlette routes
@@ -204,6 +234,39 @@ class VegaApp:
204
234
  route.to_starlette_route() for route in self._router.get_routes()
205
235
  ]
206
236
 
237
+ # Add OpenAPI endpoint
238
+ if self.openapi_url:
239
+ async def openapi_endpoint(request):
240
+ return StarletteJSONResponse(self.openapi())
241
+
242
+ starlette_routes.append(
243
+ StarletteRoute(self.openapi_url, endpoint=openapi_endpoint, methods=["GET"])
244
+ )
245
+
246
+ # Add Swagger UI endpoint
247
+ if self.docs_url:
248
+ async def swagger_ui_endpoint(request):
249
+ return get_swagger_ui_html(
250
+ openapi_url=self.openapi_url or "/openapi.json",
251
+ title=f"{self.title} - Swagger UI"
252
+ )
253
+
254
+ starlette_routes.append(
255
+ StarletteRoute(self.docs_url, endpoint=swagger_ui_endpoint, methods=["GET"])
256
+ )
257
+
258
+ # Add ReDoc endpoint
259
+ if self.redoc_url:
260
+ async def redoc_endpoint(request):
261
+ return get_redoc_html(
262
+ openapi_url=self.openapi_url or "/openapi.json",
263
+ title=f"{self.title} - ReDoc"
264
+ )
265
+
266
+ starlette_routes.append(
267
+ StarletteRoute(self.redoc_url, endpoint=redoc_endpoint, methods=["GET"])
268
+ )
269
+
207
270
  # Create Starlette app
208
271
  app = Starlette(
209
272
  debug=self.debug,
@@ -0,0 +1,104 @@
1
+ """Documentation endpoints for Vega Web Framework"""
2
+
3
+ from typing import Callable
4
+ from starlette.responses import HTMLResponse, JSONResponse
5
+
6
+
7
+ def get_swagger_ui_html(
8
+ *,
9
+ openapi_url: str,
10
+ title: str,
11
+ swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
12
+ swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
13
+ swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
14
+ ) -> HTMLResponse:
15
+ """
16
+ Generate Swagger UI HTML page.
17
+
18
+ Args:
19
+ openapi_url: URL to OpenAPI schema JSON
20
+ title: Page title
21
+ swagger_js_url: URL to Swagger UI JavaScript
22
+ swagger_css_url: URL to Swagger UI CSS
23
+ swagger_favicon_url: URL to favicon
24
+
25
+ Returns:
26
+ HTML response with Swagger UI
27
+ """
28
+ html = f"""
29
+ <!DOCTYPE html>
30
+ <html>
31
+ <head>
32
+ <title>{title}</title>
33
+ <meta charset="utf-8"/>
34
+ <meta name="viewport" content="width=device-width, initial-scale=1">
35
+ <link rel="shortcut icon" href="{swagger_favicon_url}">
36
+ <link rel="stylesheet" type="text/css" href="{swagger_css_url}" />
37
+ </head>
38
+ <body>
39
+ <div id="swagger-ui"></div>
40
+ <script src="{swagger_js_url}"></script>
41
+ <script>
42
+ const ui = SwaggerUIBundle({{
43
+ url: '{openapi_url}',
44
+ dom_id: '#swagger-ui',
45
+ presets: [
46
+ SwaggerUIBundle.presets.apis,
47
+ SwaggerUIBundle.SwaggerUIStandalonePreset
48
+ ],
49
+ layout: "BaseLayout",
50
+ deepLinking: true,
51
+ showExtensions: true,
52
+ showCommonExtensions: true
53
+ }})
54
+ </script>
55
+ </body>
56
+ </html>
57
+ """
58
+ return HTMLResponse(content=html)
59
+
60
+
61
+ def get_redoc_html(
62
+ *,
63
+ openapi_url: str,
64
+ title: str,
65
+ redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
66
+ redoc_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
67
+ ) -> HTMLResponse:
68
+ """
69
+ Generate ReDoc HTML page.
70
+
71
+ Args:
72
+ openapi_url: URL to OpenAPI schema JSON
73
+ title: Page title
74
+ redoc_js_url: URL to ReDoc JavaScript
75
+ redoc_favicon_url: URL to favicon
76
+
77
+ Returns:
78
+ HTML response with ReDoc
79
+ """
80
+ html = f"""
81
+ <!DOCTYPE html>
82
+ <html>
83
+ <head>
84
+ <title>{title}</title>
85
+ <meta charset="utf-8"/>
86
+ <meta name="viewport" content="width=device-width, initial-scale=1">
87
+ <link rel="shortcut icon" href="{redoc_favicon_url}">
88
+ <style>
89
+ body {{
90
+ margin: 0;
91
+ padding: 0;
92
+ }}
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <redoc spec-url="{openapi_url}"></redoc>
97
+ <script src="{redoc_js_url}"></script>
98
+ </body>
99
+ </html>
100
+ """
101
+ return HTMLResponse(content=html)
102
+
103
+
104
+ __all__ = ["get_swagger_ui_html", "get_redoc_html"]
@@ -0,0 +1,292 @@
1
+ """OpenAPI schema generation for Vega Web Framework"""
2
+
3
+ from typing import Any, Dict, List, Optional, Type, get_type_hints
4
+ from inspect import signature, Parameter
5
+ import json
6
+
7
+ try:
8
+ from pydantic import BaseModel
9
+ PYDANTIC_AVAILABLE = True
10
+ except ImportError:
11
+ PYDANTIC_AVAILABLE = False
12
+ BaseModel = None # type: ignore
13
+
14
+
15
+ def get_openapi_schema(
16
+ *,
17
+ title: str,
18
+ version: str,
19
+ description: str = "",
20
+ routes: List[Any],
21
+ openapi_version: str = "3.0.2",
22
+ ) -> Dict[str, Any]:
23
+ """
24
+ Generate OpenAPI 3.0 schema from routes.
25
+
26
+ Args:
27
+ title: API title
28
+ version: API version
29
+ description: API description
30
+ routes: List of Route objects
31
+ openapi_version: OpenAPI specification version
32
+
33
+ Returns:
34
+ OpenAPI schema dictionary
35
+ """
36
+ schema: Dict[str, Any] = {
37
+ "openapi": openapi_version,
38
+ "info": {
39
+ "title": title,
40
+ "version": version,
41
+ },
42
+ "paths": {},
43
+ }
44
+
45
+ if description:
46
+ schema["info"]["description"] = description
47
+
48
+ # Components for reusable schemas
49
+ components: Dict[str, Any] = {
50
+ "schemas": {}
51
+ }
52
+
53
+ # Process each route
54
+ for route in routes:
55
+ path = route.path
56
+
57
+ # Convert path parameters from {param} to OpenAPI format
58
+ openapi_path = path
59
+
60
+ if openapi_path not in schema["paths"]:
61
+ schema["paths"][openapi_path] = {}
62
+
63
+ for method in route.methods:
64
+ method_lower = method.lower()
65
+
66
+ operation: Dict[str, Any] = {
67
+ "responses": {
68
+ "200": {
69
+ "description": "Successful Response"
70
+ }
71
+ }
72
+ }
73
+
74
+ # Add summary and description
75
+ if route.summary:
76
+ operation["summary"] = route.summary
77
+
78
+ if route.description:
79
+ operation["description"] = route.description
80
+ elif route.endpoint.__doc__:
81
+ operation["description"] = route.endpoint.__doc__.strip()
82
+
83
+ # Add tags
84
+ if route.tags:
85
+ operation["tags"] = route.tags
86
+
87
+ # Add operation ID
88
+ operation["operationId"] = f"{method_lower}_{route.name or route.endpoint.__name__}"
89
+
90
+ # Analyze endpoint parameters
91
+ params = _get_parameters(route)
92
+ if params:
93
+ operation["parameters"] = params
94
+
95
+ # Analyze request body
96
+ request_body = _get_request_body(route)
97
+ if request_body:
98
+ operation["requestBody"] = request_body
99
+ # Add request body schemas to components
100
+ if PYDANTIC_AVAILABLE and "content" in request_body:
101
+ for content_type, content_schema in request_body["content"].items():
102
+ if "schema" in content_schema and "$ref" in content_schema["schema"]:
103
+ model_name = content_schema["schema"]["$ref"].split("/")[-1]
104
+ # Model will be added when processing response_model
105
+
106
+ # Analyze response model
107
+ if route.response_model:
108
+ response_schema = _get_response_schema(route.response_model, components)
109
+ if response_schema:
110
+ operation["responses"]["200"] = {
111
+ "description": "Successful Response",
112
+ "content": {
113
+ "application/json": {
114
+ "schema": response_schema
115
+ }
116
+ }
117
+ }
118
+
119
+ # Add status code if specified
120
+ if route.status_code and route.status_code != 200:
121
+ operation["responses"][str(route.status_code)] = operation["responses"].pop("200")
122
+
123
+ # Add error responses
124
+ operation["responses"]["422"] = {
125
+ "description": "Validation Error"
126
+ }
127
+
128
+ schema["paths"][openapi_path][method_lower] = operation
129
+
130
+ # Add components if we have any schemas
131
+ if components["schemas"]:
132
+ schema["components"] = components
133
+
134
+ return schema
135
+
136
+
137
+ def _get_parameters(route: Any) -> List[Dict[str, Any]]:
138
+ """Extract path and query parameters from route."""
139
+ parameters = []
140
+
141
+ # Get function signature
142
+ sig = signature(route.endpoint)
143
+ type_hints = get_type_hints(route.endpoint)
144
+
145
+ # Find which parameters are Pydantic models (request body)
146
+ request_body_params = set()
147
+ if PYDANTIC_AVAILABLE:
148
+ for param_name, param in sig.parameters.items():
149
+ param_type = type_hints.get(param_name)
150
+ if param_type and isinstance(param_type, type) and issubclass(param_type, BaseModel):
151
+ request_body_params.add(param_name)
152
+
153
+ for param_name, param in sig.parameters.items():
154
+ # Skip special parameters
155
+ if param_name in ("request", "self", "cls"):
156
+ continue
157
+
158
+ # Skip parameters that are Pydantic models (they're in request body)
159
+ if param_name in request_body_params:
160
+ continue
161
+
162
+ # Check if it's a path parameter
163
+ if f"{{{param_name}}}" in route.path:
164
+ param_schema = {
165
+ "name": param_name,
166
+ "in": "path",
167
+ "required": True,
168
+ "schema": _get_type_schema(type_hints.get(param_name, str))
169
+ }
170
+ parameters.append(param_schema)
171
+ elif param.default == Parameter.empty:
172
+ # Required query parameter
173
+ param_schema = {
174
+ "name": param_name,
175
+ "in": "query",
176
+ "required": True,
177
+ "schema": _get_type_schema(type_hints.get(param_name, str))
178
+ }
179
+ parameters.append(param_schema)
180
+ else:
181
+ # Optional query parameter
182
+ param_schema = {
183
+ "name": param_name,
184
+ "in": "query",
185
+ "required": False,
186
+ "schema": _get_type_schema(type_hints.get(param_name, str))
187
+ }
188
+ if param.default is not None and param.default != Parameter.empty:
189
+ param_schema["schema"]["default"] = param.default
190
+ parameters.append(param_schema)
191
+
192
+ return parameters
193
+
194
+
195
+ def _get_request_body(route: Any) -> Optional[Dict[str, Any]]:
196
+ """Extract request body schema from route."""
197
+ if not PYDANTIC_AVAILABLE:
198
+ return None
199
+
200
+ # Get function signature
201
+ sig = signature(route.endpoint)
202
+ type_hints = get_type_hints(route.endpoint)
203
+
204
+ for param_name, param in sig.parameters.items():
205
+ # Skip special parameters and path parameters
206
+ if param_name in ("request", "self", "cls"):
207
+ continue
208
+ if f"{{{param_name}}}" in route.path:
209
+ continue
210
+
211
+ param_type = type_hints.get(param_name)
212
+
213
+ # Check if it's a Pydantic model
214
+ if param_type and isinstance(param_type, type) and issubclass(param_type, BaseModel):
215
+ model_schema = param_type.model_json_schema()
216
+ model_name = param_type.__name__
217
+
218
+ return {
219
+ "required": True,
220
+ "content": {
221
+ "application/json": {
222
+ "schema": {
223
+ "$ref": f"#/components/schemas/{model_name}"
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ return None
230
+
231
+
232
+ def _get_response_schema(response_model: Type, components: Dict[str, Any]) -> Dict[str, Any]:
233
+ """Get response schema from response model."""
234
+ if not PYDANTIC_AVAILABLE:
235
+ return {}
236
+
237
+ # Handle Pydantic models
238
+ if isinstance(response_model, type) and issubclass(response_model, BaseModel):
239
+ model_name = response_model.__name__
240
+
241
+ # Add model schema to components
242
+ if model_name not in components["schemas"]:
243
+ components["schemas"][model_name] = response_model.model_json_schema()
244
+
245
+ return {
246
+ "$ref": f"#/components/schemas/{model_name}"
247
+ }
248
+
249
+ # Handle List types
250
+ if hasattr(response_model, "__origin__"):
251
+ if response_model.__origin__ is list:
252
+ item_type = response_model.__args__[0]
253
+ if isinstance(item_type, type) and issubclass(item_type, BaseModel):
254
+ model_name = item_type.__name__
255
+
256
+ # Add model schema to components
257
+ if model_name not in components["schemas"]:
258
+ components["schemas"][model_name] = item_type.model_json_schema()
259
+
260
+ return {
261
+ "type": "array",
262
+ "items": {
263
+ "$ref": f"#/components/schemas/{model_name}"
264
+ }
265
+ }
266
+
267
+ # Handle dict
268
+ if response_model is dict:
269
+ return {"type": "object"}
270
+
271
+ return {}
272
+
273
+
274
+ def _get_type_schema(param_type: Any) -> Dict[str, Any]:
275
+ """Convert Python type to OpenAPI schema."""
276
+ if param_type is str or param_type == "str":
277
+ return {"type": "string"}
278
+ elif param_type is int or param_type == "int":
279
+ return {"type": "integer"}
280
+ elif param_type is float or param_type == "float":
281
+ return {"type": "number"}
282
+ elif param_type is bool or param_type == "bool":
283
+ return {"type": "boolean"}
284
+ elif param_type is list or (hasattr(param_type, "__origin__") and param_type.__origin__ is list):
285
+ return {"type": "array", "items": {}}
286
+ elif param_type is dict:
287
+ return {"type": "object"}
288
+ else:
289
+ return {"type": "string"}
290
+
291
+
292
+ __all__ = ["get_openapi_schema"]
File without changes
File without changes