vega-framework 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -394,13 +394,13 @@ def _register_router_in_init(project_root: Path, resource_file: str, resource_na
394
394
 
395
395
 
396
396
  def _generate_router(project_root: Path, project_name: str, name: str) -> None:
397
- """Generate a FastAPI router for a resource"""
397
+ """Generate a Vega Web router for a resource"""
398
398
 
399
399
  # Check if web folder exists
400
400
  web_path = project_root / "presentation" / "web"
401
401
  if not web_path.exists():
402
402
  click.echo(click.style("ERROR: Web module not found", fg='red'))
403
- click.echo(" Router generation requires FastAPI web module")
403
+ click.echo(" Router generation requires Vega Web module")
404
404
  click.echo(" Install it with: vega add web")
405
405
  return
406
406
 
@@ -440,13 +440,13 @@ def _generate_router(project_root: Path, project_name: str, name: str) -> None:
440
440
 
441
441
 
442
442
  def _generate_web_models(project_root: Path, project_name: str, name: str, is_request: bool, is_response: bool) -> None:
443
- """Generate Pydantic request or response model for FastAPI"""
443
+ """Generate Pydantic request or response model for Vega Web"""
444
444
 
445
445
  # Check if web folder exists
446
446
  web_path = project_root / "presentation" / "web"
447
447
  if not web_path.exists():
448
448
  click.echo(click.style("ERROR: Web module not found", fg='red'))
449
- click.echo(" Model generation requires FastAPI web module")
449
+ click.echo(" Model generation requires Vega Web module")
450
450
  click.echo(" Install it with: vega add web")
451
451
  return
452
452
 
@@ -531,13 +531,13 @@ def _generate_web_models(project_root: Path, project_name: str, name: str, is_re
531
531
 
532
532
 
533
533
  def _generate_middleware(project_root: Path, project_name: str, class_name: str, file_name: str) -> None:
534
- """Generate a FastAPI middleware"""
534
+ """Generate a Vega Web middleware"""
535
535
 
536
536
  # Check if web folder exists
537
537
  web_path = project_root / "presentation" / "web"
538
538
  if not web_path.exists():
539
539
  click.echo(click.style("ERROR: Web module not found", fg='red'))
540
- click.echo(" Middleware generation requires FastAPI web module")
540
+ click.echo(" Middleware generation requires Vega Web module")
541
541
  click.echo(" Install it with: vega add web")
542
542
  return
543
543
 
@@ -554,7 +554,7 @@ def _generate_middleware(project_root: Path, project_name: str, class_name: str,
554
554
  # Check if __init__.py exists
555
555
  init_file = middleware_path / "__init__.py"
556
556
  if not init_file.exists():
557
- init_file.write_text('"""FastAPI Middlewares"""\n')
557
+ init_file.write_text('"""Vega Web Middlewares"""\n')
558
558
  click.echo(f"+ Created {click.style(str(init_file.relative_to(project_root)), fg='green')}")
559
559
 
560
560
  # Generate middleware file
@@ -589,8 +589,8 @@ def _register_middleware_in_app(project_root: Path, class_name: str, file_name:
589
589
  click.echo(click.style(f'''
590
590
  from .middleware.{file_name} import {class_name}Middleware
591
591
 
592
- def create_app() -> FastAPI:
593
- app = FastAPI(...)
592
+ def create_app() -> VegaApp:
593
+ app = VegaApp(...)
594
594
  app.add_middleware({class_name}Middleware)
595
595
  app.include_router(get_api_router())
596
596
  return app
@@ -619,9 +619,9 @@ def create_app() -> FastAPI:
619
619
  break
620
620
 
621
621
  if not import_added:
622
- # Fallback: add after FastAPI import
622
+ # Fallback: add after VegaApp import
623
623
  for i, line in enumerate(lines):
624
- if 'from fastapi import' in line:
624
+ if 'from vega.web import' in line:
625
625
  lines.insert(i + 1, middleware_import)
626
626
  lines.insert(i + 2, '')
627
627
  break
@@ -629,8 +629,8 @@ def create_app() -> FastAPI:
629
629
  # Find create_app function and add middleware registration
630
630
  middleware_added = False
631
631
  for i, line in enumerate(lines):
632
- if 'app = FastAPI(' in line:
633
- # Find the end of FastAPI initialization
632
+ if 'app = VegaApp(' in line:
633
+ # Find the end of VegaApp initialization
634
634
  j = i + 1
635
635
  while j < len(lines) and not lines[j].strip().startswith('app.include_router'):
636
636
  j += 1
@@ -649,8 +649,8 @@ def create_app() -> FastAPI:
649
649
  click.echo(click.style(f'''
650
650
  from .middleware.{file_name} import {class_name}Middleware
651
651
 
652
- def create_app() -> FastAPI:
653
- app = FastAPI(...)
652
+ def create_app() -> VegaApp:
653
+ app = VegaApp(...)
654
654
  app.add_middleware({class_name}Middleware)
655
655
  app.include_router(get_api_router())
656
656
  return app
vega/cli/commands/web.py CHANGED
@@ -1,4 +1,4 @@
1
- """Web command - Manage FastAPI web server"""
1
+ """Web command - Manage Vega Web server"""
2
2
  import sys
3
3
  from pathlib import Path
4
4
 
@@ -7,7 +7,7 @@ import click
7
7
 
8
8
  @click.group()
9
9
  def web():
10
- """Manage FastAPI web server
10
+ """Manage Vega Web server
11
11
 
12
12
  Commands to manage the web server for your Vega project.
13
13
  The web module must be added to the project first using 'vega add web'.
@@ -21,7 +21,7 @@ def web():
21
21
  @click.option('--reload', is_flag=True, help='Enable auto-reload')
22
22
  @click.option('--path', default='.', help='Path to Vega project (default: current directory)')
23
23
  def run(host: str, port: int, reload: bool, path: str):
24
- """Start the FastAPI web server
24
+ """Start the Vega Web server
25
25
 
26
26
  Examples:
27
27
  vega web run
@@ -42,7 +42,7 @@ def run(host: str, port: int, reload: bool, path: str):
42
42
  web_main = project_path / "presentation" / "web" / "main.py"
43
43
  if not web_main.exists():
44
44
  click.echo(click.style("ERROR: Web module not found", fg='red'))
45
- click.echo("\nThe FastAPI web module is not available in this project.")
45
+ click.echo("\nThe Vega Web module is not available in this project.")
46
46
  click.echo("Add it using:")
47
47
  click.echo(click.style(" vega add web", fg='cyan', bold=True))
48
48
  sys.exit(1)
@@ -56,8 +56,9 @@ def run(host: str, port: int, reload: bool, path: str):
56
56
  import uvicorn
57
57
  except ImportError:
58
58
  click.echo(click.style("ERROR: uvicorn not installed", fg='red'))
59
- click.echo("\nInstall FastAPI dependencies:")
60
- click.echo(click.style(" poetry add fastapi uvicorn[standard]", fg='cyan', bold=True))
59
+ click.echo("\nUvicorn is required but not installed.")
60
+ click.echo("It should be included with vega-framework, but you can also install it with:")
61
+ click.echo(click.style(" poetry add uvicorn[standard]", fg='cyan', bold=True))
61
62
  sys.exit(1)
62
63
 
63
64
  # Initialize DI container first
@@ -73,7 +74,7 @@ def run(host: str, port: int, reload: bool, path: str):
73
74
  try:
74
75
  from presentation.web.main import app
75
76
  except ImportError as e:
76
- click.echo(click.style("ERROR: Failed to import FastAPI app", fg='red'))
77
+ click.echo(click.style("ERROR: Failed to import Vega Web app", fg='red'))
77
78
  click.echo(f"\nDetails: {e}")
78
79
  click.echo("\nMake sure:")
79
80
  click.echo(" 1. You are in the project directory or use --path")
@@ -38,7 +38,7 @@ The innermost layer containing pure business logic, completely independent of an
38
38
  **Rules:**
39
39
 
40
40
  - ✅ **NO** dependencies on any other layer
41
- - ✅ **NO** framework-specific code (no FastAPI, SQLAlchemy, etc.)
41
+ - ✅ **NO** framework-specific code (no Vega Web, SQLAlchemy, etc.)
42
42
  - ✅ **NO** infrastructure details (no database, HTTP, file system)
43
43
  - ✅ Pure business logic only
44
44
  - ✅ Only defines **interfaces**, never concrete implementations
@@ -90,7 +90,7 @@ Handles user interaction and external communication, acting as the entry point t
90
90
 
91
91
  **Contains:**
92
92
 
93
- - **Web API** - FastAPI routes, controllers, request/response models (when web is enabled)
93
+ - **Web API** - Vega Web routes, controllers, request/response models (when web is enabled)
94
94
  - **CLI** - Command-line interface commands and argument parsing
95
95
  - **GraphQL/gRPC** - Alternative API implementations (if needed)
96
96
  - **WebSockets** - Real-time communication handlers (if needed)
@@ -108,7 +108,7 @@ Handles user interaction and external communication, acting as the entry point t
108
108
  **Examples:**
109
109
 
110
110
  - **CLI**: Uses Click/Typer to define commands that invoke interactors or mediators
111
- - **Web API**: FastAPI endpoints that receive HTTP requests and call domain use cases
111
+ - **Web API**: Vega Web endpoints that receive HTTP requests and call domain use cases
112
112
  - **Both**: Can coexist in the same application, sharing the same business logic
113
113
 
114
114
  ## Core Patterns
@@ -344,7 +344,7 @@ The generator will interactively prompt for:
344
344
 
345
345
  Commands are **automatically discovered** from `presentation/cli/commands/` - no manual registration required.
346
346
 
347
- **FastAPI Routers** - HTTP API endpoints (requires web support):
347
+ **Vega Web Routers** - HTTP API endpoints (requires web support):
348
348
  ```bash
349
349
  vega generate router User
350
350
  vega generate router Product
@@ -353,7 +353,7 @@ vega generate router Order
353
353
 
354
354
  Routers are **automatically discovered** from `presentation/web/routes/` - no manual registration required.
355
355
 
356
- **FastAPI Middleware** - Request/response processing (requires web support):
356
+ **Vega Web Middleware** - Request/response processing (requires web support):
357
357
  ```bash
358
358
  vega generate middleware Logging
359
359
  vega generate middleware Authentication
@@ -362,12 +362,12 @@ vega generate middleware RateLimiting
362
362
 
363
363
  ### Adding Features to Existing Projects
364
364
 
365
- **Add FastAPI Web Support**:
365
+ **Add Vega Web Support**:
366
366
  ```bash
367
367
  vega add web
368
368
  ```
369
369
 
370
- Creates complete FastAPI scaffold:
370
+ Creates complete Vega Web scaffold:
371
371
  - `presentation/web/` directory structure
372
372
  - Routes and middleware setup
373
373
  - Health check endpoints
@@ -482,9 +482,9 @@ vega doctor
482
482
  | `vega generate interactor <Name>` | Generate use case |
483
483
  | `vega generate mediator <Name>` | Generate workflow |
484
484
  | `vega generate command <Name>` | Generate CLI command |
485
- | `vega generate router <Name>` | Generate FastAPI router |
485
+ | `vega generate router <Name>` | Generate Vega Web router |
486
486
  | `vega generate model <Name>` | Generate SQLAlchemy model |
487
- | `vega add web` | Add FastAPI support |
487
+ | `vega add web` | Add Vega Web support |
488
488
  | `vega add sqlalchemy` | Add database support |
489
489
  | `vega migrate <command>` | Manage database migrations |
490
490
  | `vega doctor` | Validate project architecture |
@@ -519,9 +519,9 @@ vega generate router Product
519
519
 
520
520
  This creates `presentation/web/routes/product.py`:
521
521
  ```python
522
- from fastapi import APIRouter
522
+ from vega.web import Router
523
523
 
524
- router = APIRouter() # MUST be named 'router'
524
+ router = Router() # MUST be named 'router'
525
525
 
526
526
  @router.get("/")
527
527
  async def list_products():
@@ -682,7 +682,7 @@ Vega projects follow a standard 4-layer structure:
682
682
  │ └── sendgrid_email_service.py # Sendgrid implementation
683
683
 
684
684
  ├── presentation/ # 🟠 PRESENTATION LAYER (Delivery Mechanisms)
685
- │ ├── web/ # FastAPI web interface (if enabled)
685
+ │ ├── web/ # Vega Web interface (if enabled)
686
686
  │ │ ├── __init__.py
687
687
  │ │ ├── routes/
688
688
  │ │ │ └── user_routes.py
@@ -833,7 +833,7 @@ async def create_user(name: str, email: str):
833
833
 
834
834
  **Benefits:**
835
835
  - Execute async interactors directly in CLI commands
836
- - Same business logic works in both CLI and Web (FastAPI) contexts
836
+ - Same business logic works in both CLI and Web (Vega Web) contexts
837
837
  - Clean async/await syntax
838
838
  - Automatic asyncio event loop management
839
839
 
@@ -20,9 +20,9 @@ Vega Framework application with Clean Architecture.
20
20
  │ └── services/ # Service implementations
21
21
 
22
22
  ├── presentation/ # Delivery mechanisms
23
- │ ├── web/ # FastAPI web interface (if added)
23
+ │ ├── web/ # Vega Web interface (if added)
24
24
  │ │ ├── routes/ # HTTP endpoints
25
- │ │ ├── app.py # FastAPI app factory
25
+ │ │ ├── app.py # Vega app factory
26
26
  │ │ └── main.py # ASGI entrypoint
27
27
  │ └── cli/ # CLI commands
28
28
 
@@ -45,7 +45,7 @@ cp .env.example .env
45
45
  python main.py hello
46
46
  python main.py greet --name John
47
47
 
48
- # If using FastAPI template, run the web server
48
+ # If using web template, run the web server
49
49
  vega web run
50
50
  vega web run --reload # With auto-reload
51
51
  # Visit http://localhost:8000/api/health/status
@@ -81,7 +81,7 @@ vega generate command ListUsers --impl sync
81
81
 
82
82
  ### Add Features
83
83
 
84
- Add FastAPI web support to your project:
84
+ Add Vega Web support to your project:
85
85
 
86
86
  ```bash
87
87
  vega add web
@@ -140,7 +140,7 @@ async def create_user(name: str):
140
140
  click.echo(f"Created: {user.name}")
141
141
  ```
142
142
 
143
- This allows the same async business logic to work in both CLI and web contexts (FastAPI).
143
+ This allows the same async business logic to work in both CLI and web contexts (Vega Web).
144
144
 
145
145
  ## Project Commands Quick Reference
146
146
 
@@ -1,5 +1,4 @@
1
- """FastAPI ASGI entrypoint for {{ project_name }}"""
2
- from fastapi import FastAPI
1
+ """Vega Web ASGI entrypoint for {{ project_name }}"""
3
2
  import config # noqa: F401 - Import to initialize DI container
4
3
 
5
4
  # Auto-discover event subscribers so @subscribe handlers are ready
@@ -12,7 +11,7 @@ else:
12
11
 
13
12
  from .app import create_app
14
13
 
15
- app: FastAPI = create_app()
14
+ app = create_app()
16
15
 
17
16
 
18
17
  if __name__ == "__main__":
@@ -1,5 +1,5 @@
1
- """{{ class_name }} middleware for FastAPI application"""
2
- from fastapi import Request, Response
1
+ """{{ class_name }} middleware for Vega Web application"""
2
+ from vega.web import Request, Response
3
3
  from starlette.middleware.base import BaseHTTPMiddleware
4
4
  from starlette.types import ASGIApp
5
5
  import time
@@ -14,7 +14,7 @@ class {{ class_name }}Middleware(BaseHTTPMiddleware):
14
14
  and responses before they're returned to clients.
15
15
 
16
16
  Usage:
17
- Add to your FastAPI app in app.py:
17
+ Add to your Vega Web app in app.py:
18
18
  from .middleware.{{ file_name }} import {{ class_name }}Middleware
19
19
  app.add_middleware({{ class_name }}Middleware)
20
20
  """
@@ -1,5 +1,5 @@
1
1
  """API routers aggregation"""
2
- from fastapi import APIRouter
2
+ from vega.web import Router
3
3
 
4
4
  from . import health, users
5
5
 
@@ -7,9 +7,9 @@ from . import health, users
7
7
  API_PREFIX = "/api"
8
8
 
9
9
 
10
- def get_api_router() -> APIRouter:
10
+ def get_api_router() -> Router:
11
11
  """Return application API router"""
12
- router = APIRouter(prefix=API_PREFIX)
12
+ router = Router(prefix=API_PREFIX)
13
13
  router.include_router(health.router, tags=["health"], prefix="/health")
14
14
  router.include_router(users.router, tags=["users"], prefix="/users")
15
15
  return router
vega/web/__init__.py CHANGED
@@ -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
  ]
vega/web/application.py CHANGED
@@ -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,
vega/web/docs.py ADDED
@@ -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"]
vega/web/openapi.py ADDED
@@ -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"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vega-framework
3
- Version: 0.2.0
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
@@ -2,11 +2,11 @@ vega/__init__.py,sha256=A05RwOYXooAZUz3GnbJ--ofLXgtRZK9gaSmsLVRdGPY,1811
2
2
  vega/cli/__init__.py,sha256=NCzOOyhKHqLeN1r80ekhMfkQwBdAQXKcKiKoNwYPNiY,304
3
3
  vega/cli/commands/__init__.py,sha256=UH7MdYduBG_YoulgdiWkUCtcgGLzuYRGFzxaqoa0pyg,19
4
4
  vega/cli/commands/add.py,sha256=Q9T6MHJcWCnRY68OrS3y4sO0kI6ElGlUHNHB2_jNJOU,6418
5
- vega/cli/commands/generate.py,sha256=wCvcj1bSIK9MJam8FcT9G2vrQTYv92JFRzPYOipJywA,38172
5
+ vega/cli/commands/generate.py,sha256=KQdnAtk_R9LlLBOTmfHfd2BEEXBVk5GUU5DEcJrfhYY,38168
6
6
  vega/cli/commands/init.py,sha256=oOlc_CjZDLOswLJd8POUfPA6yFep3Th-f2sr4fqHI7M,5936
7
7
  vega/cli/commands/migrate.py,sha256=00swKeVhmKr1_1VJhg3GpIMcJ6Jfgz5FUpmJoLf5qSQ,3805
8
8
  vega/cli/commands/update.py,sha256=0zRWkHvQwKGlJL9XF3bi--dThkFapyNOugL6AgGr6Ic,5897
9
- vega/cli/commands/web.py,sha256=tkaMzrENiV044JdPtCSRmzX_vVpnumlDeTG6YH3DWo4,3450
9
+ vega/cli/commands/web.py,sha256=eNWxh3pHtymYYgInlFy4xzGQMz01PwrQGu1HGvlTHw8,3539
10
10
  vega/cli/main.py,sha256=MlRUaJriz68rFW7fwG1Ug3k9fI8PF4NbENcTWTZcdh8,5470
11
11
  vega/cli/scaffolds/__init__.py,sha256=wqddIhjBNDI-ztYj_E6B3tTrKfIoF4iPeynt454eHH0,378
12
12
  vega/cli/scaffolds/fastapi.py,sha256=9VqZD5TIT7iX6za_5h0L1aWqJ0V58pV8CyEiIxw7_w0,3013
@@ -30,8 +30,8 @@ vega/cli/templates/infrastructure/service_impl.py.j2,sha256=JYCfPDOcdVd-x1Y0nSh7
30
30
  vega/cli/templates/loader.py,sha256=cyt9ZOVLwBgv4ijPxoqR4JgFSbdkXCMzviHcq3fdv8Q,2712
31
31
  vega/cli/templates/project/.env.example,sha256=BqfRxYeOasgZMz4_B2kybu6areIy9dHCt5BJ6fa56ik,221
32
32
  vega/cli/templates/project/.gitignore,sha256=7JSDihCtBznd-QxQ4wUtHM9fnbYnbw6PK4Auh56ofAY,229
33
- vega/cli/templates/project/ARCHITECTURE.md.j2,sha256=HunrJ_9LlPxd5-GONaJxjoLlw-XfjYaLpsVHFa72Yf4,25868
34
- vega/cli/templates/project/README.md.j2,sha256=tZtMKhyKjfCq5JTECHihISu0VjYd254t-7y2kJ0nbKY,4589
33
+ vega/cli/templates/project/ARCHITECTURE.md.j2,sha256=tw1grFM9hWnPcmTJ174NW-lzGSLnkETYJK7IkGqtK_c,25866
34
+ vega/cli/templates/project/README.md.j2,sha256=kXuPhWbB7LhYhk68Ib0AmVf3AQcA7iYPlXk8FzEd_1g,4577
35
35
  vega/cli/templates/project/config.py.j2,sha256=1Iva9JEz5ej_WmTbRVBvOfSBhYUKIzN88p6GYKR0m4s,866
36
36
  vega/cli/templates/project/events_init.py.j2,sha256=dp9yc8_Du6lv62vum_Br0p030I4TKZAkZG29mj4AZaM,1057
37
37
  vega/cli/templates/project/main.py.j2,sha256=33mohAsEP4f-aGPyWcGdKTP8OxDZDI3JVQKhWCeGe_U,753
@@ -46,13 +46,13 @@ vega/cli/templates/sqlalchemy/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6H
46
46
  vega/cli/templates/web/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  vega/cli/templates/web/app.py.j2,sha256=hIUiH1vtqAtX8u8aiSOaYz9xh9jX1Y9Isl8WeBf-21k,419
48
48
  vega/cli/templates/web/health_route.py.j2,sha256=ZsPhr3mUmFlatoqVPS91YC1kLDOUWQViot28ijQ88FU,248
49
- vega/cli/templates/web/main.py.j2,sha256=9zJzBMOLZl4J3WqLULARosyILhdHiXYMMdxeNAHlX3w,557
50
- vega/cli/templates/web/middleware.py.j2,sha256=ild8nAdq45HGCiA-XVcWgmMBCfwIgORcZ7qsykmOclw,2165
49
+ vega/cli/templates/web/main.py.j2,sha256=JXC-QNdeVY3xj0de8aXlDmcs5PpEojULUAOOby3pBR8,521
50
+ vega/cli/templates/web/middleware.py.j2,sha256=v5-foaT0R3NZV4919Ff8E7kXB9CiOkimfC-awPvPidc,2168
51
51
  vega/cli/templates/web/models_init.py.j2,sha256=QdNOaZ6iMh3Tz7uut1VA3XUIivYD1SkHFmDHCfstqho,159
52
52
  vega/cli/templates/web/request_model.py.j2,sha256=PPoLGMd7VZ6QXj75UxCcryZZ9SNPhaDfMH2ykVtjIXQ,534
53
53
  vega/cli/templates/web/response_model.py.j2,sha256=wCSOhEUaDD1CYcim8qf6PEkhECTR7P0NsTtAme71qic,505
54
54
  vega/cli/templates/web/router.py.j2,sha256=4L71VCJ_On7uS76aoTqPsD0249WQJBQMAg6KCL7n6bE,4290
55
- vega/cli/templates/web/routes_init.py.j2,sha256=uyeuLGRZ-bDY-xefJ1eI9wU1kt9z7lwoVjw_nvY7Vk4,397
55
+ vega/cli/templates/web/routes_init.py.j2,sha256=PakFhAJLe4yogrTBi_Z8HFiwuucXzeK4Litn5R4xNLw,389
56
56
  vega/cli/templates/web/routes_init_autodiscovery.py.j2,sha256=XXCDNw_yBBgMLM0K8lTUEx7-_lxBhSM6dI7K-9Qpylg,308
57
57
  vega/cli/templates/web/user_models.py.j2,sha256=_lbG-oL8-C7Lk3-48YuKR65USD5TV4D9JNoFT9kktIM,1250
58
58
  vega/cli/templates/web/users_route.py.j2,sha256=pSpnTZLE1kNtigkDqa-tx7-lsHgKdkDipgO7iNvz1IQ,1922
@@ -84,18 +84,20 @@ vega/patterns/repository.py,sha256=uYUyLs-O8OqW1Wb9ZqIo8UUcCjZ5UFuHors_F2iDg9A,1
84
84
  vega/patterns/service.py,sha256=buFRgJoeQtZQK22Upb4vh84c1elWKFXWBaB0X4RaruE,1374
85
85
  vega/settings/__init__.py,sha256=Eb8PMUyXAlCAQIcL2W8QhTTUHUbVlkAfXdpTUlADo1I,786
86
86
  vega/settings/base.py,sha256=bL45hyoa3t-hQOvur860eSo7O833sQMsXJJPwbTVbwE,1321
87
- vega/web/__init__.py,sha256=grAuMNHY1Z0uPXJVDdyyBt_X6JbKzdubG-7FAizW7Ws,2032
88
- vega/web/application.py,sha256=OIupZT_Q3Lad74Zr-fzl1oxgbBc0cl1NEDR3Nz0TxTY,7004
87
+ vega/web/__init__.py,sha256=ZwWpk5eXXOsV6wWT03JqtadzNrcfD9h4eFELmvZOOuY,2249
88
+ vega/web/application.py,sha256=tlq-StqFdBZlV3AIo4ETvvEGSskg3BJXTVMaWuNjXNo,9227
89
89
  vega/web/builtin_middlewares.py,sha256=01iSy-pZL-Y1R-f-GHzT2GVtdRuYK_-wZFv0VP_S9BU,9356
90
+ vega/web/docs.py,sha256=yTIgJLcqNz4Zca5AbtKYJsqCz-5UJevjJ7_g04XrwVo,2979
90
91
  vega/web/exceptions.py,sha256=QxfhHntAIMwfqCvaCRro8MqKvxZxARjSoL9eTNWa5Qc,4897
91
92
  vega/web/middleware.py,sha256=DZLlkcuKOV1uPGYnaT4MsbvFWnzFVAiyGcSvi8D2Vnw,5204
93
+ vega/web/openapi.py,sha256=OAdsIWa8jRwgEJruhPaOlHt0lLqW-SIr930NvtIu6ac,9636
92
94
  vega/web/request.py,sha256=-xz6cbRAxtY0hZv62UFgg0ces0dtDcCCE_EHcw6cEms,3181
93
95
  vega/web/response.py,sha256=h5E7crRepaFRyUecasIbP3ErkRLpFQXRZhYl9OrFLWY,5641
94
96
  vega/web/route_middleware.py,sha256=zZzkXsdu7bCRcw-CXvFmTjNuyJ1v4NNM1DbEQo7l0qg,8208
95
97
  vega/web/router.py,sha256=6K6TPMZhi92jd030guYh_mdfN3Z9MTSfjy1P3xnFfdg,10575
96
98
  vega/web/routing.py,sha256=5uxiroobZb194Vg_MNzJyd-B7BOiitdwQuH2oq9Z5B8,10506
97
- vega_framework-0.2.0.dist-info/METADATA,sha256=R2K4dHAqHR36fDva1SB4iHMdMdRu_mcqg0LpBWG_beQ,11226
98
- vega_framework-0.2.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
99
- vega_framework-0.2.0.dist-info/entry_points.txt,sha256=p3gyTmPYjNRLbuiKS-hG3ytWd-ssBweFy6VZ-F9FTNk,42
100
- vega_framework-0.2.0.dist-info/licenses/LICENSE,sha256=wlHh1MBTcs2kSQr99P30mZ61s7uh7Cp9Rk0YiJxots0,1084
101
- vega_framework-0.2.0.dist-info/RECORD,,
99
+ vega_framework-0.2.2.dist-info/METADATA,sha256=QrzifI2YAZv8y_zVLSB7fM3FDeOINyRDctOnZIW3KSM,11226
100
+ vega_framework-0.2.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
101
+ vega_framework-0.2.2.dist-info/entry_points.txt,sha256=p3gyTmPYjNRLbuiKS-hG3ytWd-ssBweFy6VZ-F9FTNk,42
102
+ vega_framework-0.2.2.dist-info/licenses/LICENSE,sha256=wlHh1MBTcs2kSQr99P30mZ61s7uh7Cp9Rk0YiJxots0,1084
103
+ vega_framework-0.2.2.dist-info/RECORD,,