vega-framework 0.1.35__py3-none-any.whl → 0.2.1__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.
- vega/cli/commands/add.py +9 -10
- vega/cli/commands/generate.py +15 -15
- vega/cli/commands/init.py +9 -8
- vega/cli/commands/web.py +8 -7
- vega/cli/main.py +4 -4
- vega/cli/scaffolds/__init__.py +6 -2
- vega/cli/scaffolds/vega_web.py +109 -0
- vega/cli/templates/__init__.py +34 -8
- vega/cli/templates/components.py +29 -13
- vega/cli/templates/project/ARCHITECTURE.md.j2 +13 -13
- vega/cli/templates/project/README.md.j2 +5 -5
- vega/cli/templates/web/app.py.j2 +5 -5
- vega/cli/templates/web/health_route.py.j2 +2 -2
- vega/cli/templates/web/main.py.j2 +2 -3
- vega/cli/templates/web/middleware.py.j2 +3 -3
- vega/cli/templates/web/router.py.j2 +2 -2
- vega/cli/templates/web/routes_init.py.j2 +3 -3
- vega/cli/templates/web/routes_init_autodiscovery.py.j2 +2 -2
- vega/cli/templates/web/users_route.py.j2 +2 -2
- vega/discovery/routes.py +13 -13
- vega/web/__init__.py +100 -0
- vega/web/application.py +234 -0
- vega/web/builtin_middlewares.py +288 -0
- vega/web/exceptions.py +151 -0
- vega/web/middleware.py +185 -0
- vega/web/request.py +120 -0
- vega/web/response.py +220 -0
- vega/web/route_middleware.py +266 -0
- vega/web/router.py +350 -0
- vega/web/routing.py +347 -0
- {vega_framework-0.1.35.dist-info → vega_framework-0.2.1.dist-info}/METADATA +10 -9
- {vega_framework-0.1.35.dist-info → vega_framework-0.2.1.dist-info}/RECORD +35 -24
- {vega_framework-0.1.35.dist-info → vega_framework-0.2.1.dist-info}/WHEEL +0 -0
- {vega_framework-0.1.35.dist-info → vega_framework-0.2.1.dist-info}/entry_points.txt +0 -0
- {vega_framework-0.1.35.dist-info → vega_framework-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -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
|
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** -
|
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**:
|
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
|
-
**
|
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
|
-
**
|
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
|
365
|
+
**Add Vega Web Support**:
|
366
366
|
```bash
|
367
367
|
vega add web
|
368
368
|
```
|
369
369
|
|
370
|
-
Creates complete
|
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
|
485
|
+
| `vega generate router <Name>` | Generate Vega Web router |
|
486
486
|
| `vega generate model <Name>` | Generate SQLAlchemy model |
|
487
|
-
| `vega add web` | Add
|
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
|
522
|
+
from vega.web import Router
|
523
523
|
|
524
|
-
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/ #
|
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 (
|
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/ #
|
23
|
+
│ ├── web/ # Vega Web interface (if added)
|
24
24
|
│ │ ├── routes/ # HTTP endpoints
|
25
|
-
│ │ ├── app.py #
|
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
|
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
|
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 (
|
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
|
|
vega/cli/templates/web/app.py.j2
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
"""
|
2
|
-
from
|
1
|
+
"""Vega Web application factory for {{ project_name }}"""
|
2
|
+
from vega.web import VegaApp
|
3
3
|
|
4
4
|
from .routes import get_api_router
|
5
5
|
|
@@ -8,8 +8,8 @@ APP_TITLE = "{{ project_name.replace('-', ' ').replace('_', ' ').title() }}"
|
|
8
8
|
APP_VERSION = "0.1.0"
|
9
9
|
|
10
10
|
|
11
|
-
def create_app() ->
|
12
|
-
"""Create and configure
|
13
|
-
app =
|
11
|
+
def create_app() -> VegaApp:
|
12
|
+
"""Create and configure Vega Web application"""
|
13
|
+
app = VegaApp(title=APP_TITLE, version=APP_VERSION)
|
14
14
|
app.include_router(get_api_router())
|
15
15
|
return app
|
@@ -1,5 +1,4 @@
|
|
1
|
-
"""
|
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
|
14
|
+
app = create_app()
|
16
15
|
|
17
16
|
|
18
17
|
if __name__ == "__main__":
|
@@ -1,5 +1,5 @@
|
|
1
|
-
"""{{ class_name }} middleware for
|
2
|
-
from
|
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
|
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
|
"""{{ resource_name }} management endpoints"""
|
2
|
-
from
|
2
|
+
from vega.web import Router, HTTPException, status
|
3
3
|
from typing import List
|
4
4
|
|
5
5
|
# TODO: Import your Pydantic models here
|
@@ -8,7 +8,7 @@ from typing import List
|
|
8
8
|
# TODO: Import your interactors/use cases here
|
9
9
|
# from {{ project_name }}.domain.interactors.{{ resource_file }} import Create{{ resource_name }}, Get{{ resource_name }}, List{{ resource_name }}
|
10
10
|
|
11
|
-
router =
|
11
|
+
router = Router()
|
12
12
|
|
13
13
|
# TODO: Remove this in-memory storage and use proper repositories/interactors
|
14
14
|
{{ resource_file }}_db: dict[str, dict] = {}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
"""API routers aggregation"""
|
2
|
-
from
|
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() ->
|
10
|
+
def get_api_router() -> Router:
|
11
11
|
"""Return application API router"""
|
12
|
-
router =
|
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
|
@@ -4,9 +4,9 @@ from vega.discovery import discover_routers
|
|
4
4
|
|
5
5
|
def get_api_router():
|
6
6
|
"""
|
7
|
-
Auto-discover and register
|
7
|
+
Auto-discover and register Vega Web routers in this directory.
|
8
8
|
|
9
9
|
Returns:
|
10
|
-
|
10
|
+
Router: Main API router with all discovered routers included
|
11
11
|
"""
|
12
12
|
return discover_routers(__package__)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
"""User management endpoints"""
|
2
|
-
from
|
2
|
+
from vega.web import Router, HTTPException, status
|
3
3
|
from typing import List
|
4
4
|
|
5
5
|
from ..models.user_models import CreateUserRequest, UserResponse
|
6
6
|
|
7
|
-
router =
|
7
|
+
router = Router()
|
8
8
|
|
9
9
|
# In-memory storage for demonstration (replace with repository in production)
|
10
10
|
users_db: dict[str, UserResponse] = {}
|
vega/discovery/routes.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""
|
1
|
+
"""Vega Web router auto-discovery utilities"""
|
2
2
|
import importlib
|
3
3
|
import inspect
|
4
4
|
import logging
|
@@ -6,9 +6,9 @@ from pathlib import Path
|
|
6
6
|
from typing import Optional
|
7
7
|
|
8
8
|
try:
|
9
|
-
from
|
9
|
+
from vega.web import Router
|
10
10
|
except ImportError:
|
11
|
-
|
11
|
+
Router = None
|
12
12
|
|
13
13
|
logger = logging.getLogger(__name__)
|
14
14
|
|
@@ -19,12 +19,12 @@ def discover_routers(
|
|
19
19
|
api_prefix: str = "/api",
|
20
20
|
auto_tags: bool = True,
|
21
21
|
auto_prefix: bool = True
|
22
|
-
) -> "
|
22
|
+
) -> "Router":
|
23
23
|
"""
|
24
|
-
Auto-discover and register
|
24
|
+
Auto-discover and register Vega Web routers from a package.
|
25
25
|
|
26
26
|
This function scans a package directory for Python modules containing
|
27
|
-
|
27
|
+
Router instances named 'router' and automatically registers them
|
28
28
|
with the main router.
|
29
29
|
|
30
30
|
Args:
|
@@ -35,7 +35,7 @@ def discover_routers(
|
|
35
35
|
auto_prefix: Automatically generate prefix from module name (default: True)
|
36
36
|
|
37
37
|
Returns:
|
38
|
-
|
38
|
+
Router: Main router with all discovered routers included
|
39
39
|
|
40
40
|
Example:
|
41
41
|
# In your project's presentation/web/routes/__init__.py
|
@@ -51,15 +51,15 @@ def discover_routers(
|
|
51
51
|
)
|
52
52
|
|
53
53
|
Note:
|
54
|
-
Each route module should export
|
54
|
+
Each route module should export a Router instance named 'router'.
|
55
55
|
The module filename will be used for tags and prefix generation if enabled.
|
56
56
|
"""
|
57
|
-
if
|
57
|
+
if Router is None:
|
58
58
|
raise ImportError(
|
59
|
-
"
|
59
|
+
"Vega Web is not installed. This should not happen if you're using vega-framework."
|
60
60
|
)
|
61
61
|
|
62
|
-
main_router =
|
62
|
+
main_router = Router(prefix=api_prefix)
|
63
63
|
|
64
64
|
# Resolve the routes package path
|
65
65
|
try:
|
@@ -90,10 +90,10 @@ def discover_routers(
|
|
90
90
|
try:
|
91
91
|
module = importlib.import_module(module_name)
|
92
92
|
|
93
|
-
# Find
|
93
|
+
# Find Router instance named 'router'
|
94
94
|
router = getattr(module, 'router', None)
|
95
95
|
|
96
|
-
if isinstance(router,
|
96
|
+
if isinstance(router, Router):
|
97
97
|
# Generate tags and prefix from module name
|
98
98
|
if auto_tags:
|
99
99
|
tag = file.stem.replace("_", " ").title()
|
vega/web/__init__.py
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
"""
|
2
|
+
Vega Web Framework
|
3
|
+
|
4
|
+
A lightweight web framework built on Starlette, providing a FastAPI-like
|
5
|
+
developer experience while being deeply integrated with Vega's architecture.
|
6
|
+
|
7
|
+
Example:
|
8
|
+
from vega.web import Router, VegaApp, HTTPException, status
|
9
|
+
|
10
|
+
router = Router()
|
11
|
+
|
12
|
+
@router.get("/users/{user_id}")
|
13
|
+
async def get_user(user_id: str):
|
14
|
+
if user_id == "invalid":
|
15
|
+
raise HTTPException(status_code=404, detail="User not found")
|
16
|
+
return {"id": user_id, "name": "John"}
|
17
|
+
|
18
|
+
app = VegaApp(title="My API", version="1.0.0")
|
19
|
+
app.include_router(router, prefix="/api")
|
20
|
+
|
21
|
+
# Run with: uvicorn main:app --reload
|
22
|
+
"""
|
23
|
+
|
24
|
+
__version__ = "0.1.0"
|
25
|
+
|
26
|
+
# Core application
|
27
|
+
from .application import VegaApp
|
28
|
+
|
29
|
+
# Routing
|
30
|
+
from .router import Router
|
31
|
+
|
32
|
+
# Request/Response
|
33
|
+
from .request import Request
|
34
|
+
from .response import (
|
35
|
+
Response,
|
36
|
+
JSONResponse,
|
37
|
+
HTMLResponse,
|
38
|
+
PlainTextResponse,
|
39
|
+
RedirectResponse,
|
40
|
+
StreamingResponse,
|
41
|
+
FileResponse,
|
42
|
+
)
|
43
|
+
|
44
|
+
# Exceptions
|
45
|
+
from .exceptions import (
|
46
|
+
HTTPException,
|
47
|
+
ValidationError,
|
48
|
+
NotFoundError,
|
49
|
+
UnauthorizedError,
|
50
|
+
ForbiddenError,
|
51
|
+
BadRequestError,
|
52
|
+
status,
|
53
|
+
)
|
54
|
+
|
55
|
+
# Middleware (optional, can be imported explicitly)
|
56
|
+
from .middleware import (
|
57
|
+
VegaMiddleware,
|
58
|
+
CORSMiddleware,
|
59
|
+
RequestLoggingMiddleware,
|
60
|
+
)
|
61
|
+
|
62
|
+
# Route middleware
|
63
|
+
from .route_middleware import (
|
64
|
+
RouteMiddleware,
|
65
|
+
MiddlewarePhase,
|
66
|
+
middleware,
|
67
|
+
)
|
68
|
+
|
69
|
+
__all__ = [
|
70
|
+
# Version
|
71
|
+
"__version__",
|
72
|
+
# Core
|
73
|
+
"VegaApp",
|
74
|
+
"Router",
|
75
|
+
# Request/Response
|
76
|
+
"Request",
|
77
|
+
"Response",
|
78
|
+
"JSONResponse",
|
79
|
+
"HTMLResponse",
|
80
|
+
"PlainTextResponse",
|
81
|
+
"RedirectResponse",
|
82
|
+
"StreamingResponse",
|
83
|
+
"FileResponse",
|
84
|
+
# Exceptions
|
85
|
+
"HTTPException",
|
86
|
+
"ValidationError",
|
87
|
+
"NotFoundError",
|
88
|
+
"UnauthorizedError",
|
89
|
+
"ForbiddenError",
|
90
|
+
"BadRequestError",
|
91
|
+
"status",
|
92
|
+
# Middleware
|
93
|
+
"VegaMiddleware",
|
94
|
+
"CORSMiddleware",
|
95
|
+
"RequestLoggingMiddleware",
|
96
|
+
# Route Middleware
|
97
|
+
"RouteMiddleware",
|
98
|
+
"MiddlewarePhase",
|
99
|
+
"middleware",
|
100
|
+
]
|
vega/web/application.py
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
"""Main application class for Vega Web Framework"""
|
2
|
+
|
3
|
+
from typing import Any, Callable, Dict, List, Optional, Sequence
|
4
|
+
|
5
|
+
from starlette.applications import Starlette
|
6
|
+
from starlette.middleware import Middleware
|
7
|
+
from starlette.middleware.cors import CORSMiddleware
|
8
|
+
from starlette.routing import Mount, Route as StarletteRoute
|
9
|
+
from starlette.types import ASGIApp
|
10
|
+
|
11
|
+
from .router import Router
|
12
|
+
from .exceptions import HTTPException
|
13
|
+
from .response import JSONResponse
|
14
|
+
|
15
|
+
|
16
|
+
class VegaApp:
|
17
|
+
"""
|
18
|
+
Main application class for Vega Web Framework.
|
19
|
+
|
20
|
+
This is the core ASGI application that handles all HTTP requests.
|
21
|
+
It's built on Starlette but provides a FastAPI-like API for familiarity.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
title: Application title
|
25
|
+
description: Application description
|
26
|
+
version: Application version
|
27
|
+
debug: Enable debug mode
|
28
|
+
middleware: List of middleware to apply
|
29
|
+
on_startup: Functions to run on startup
|
30
|
+
on_shutdown: Functions to run on shutdown
|
31
|
+
|
32
|
+
Example:
|
33
|
+
app = VegaApp(
|
34
|
+
title="My API",
|
35
|
+
version="1.0.0",
|
36
|
+
debug=True
|
37
|
+
)
|
38
|
+
|
39
|
+
@app.get("/")
|
40
|
+
async def root():
|
41
|
+
return {"message": "Hello World"}
|
42
|
+
|
43
|
+
# Or with routers
|
44
|
+
router = Router(prefix="/api")
|
45
|
+
@router.get("/users")
|
46
|
+
async def get_users():
|
47
|
+
return {"users": []}
|
48
|
+
|
49
|
+
app.include_router(router)
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(
|
53
|
+
self,
|
54
|
+
*,
|
55
|
+
title: str = "Vega API",
|
56
|
+
description: str = "",
|
57
|
+
version: str = "0.1.0",
|
58
|
+
debug: bool = False,
|
59
|
+
middleware: Optional[Sequence[Middleware]] = None,
|
60
|
+
on_startup: Optional[Sequence[Callable]] = None,
|
61
|
+
on_shutdown: Optional[Sequence[Callable]] = None,
|
62
|
+
):
|
63
|
+
self.title = title
|
64
|
+
self.description = description
|
65
|
+
self.version = version
|
66
|
+
self.debug = debug
|
67
|
+
|
68
|
+
# Internal router for top-level routes
|
69
|
+
self._router = Router()
|
70
|
+
|
71
|
+
# Middleware stack
|
72
|
+
self._middleware = list(middleware) if middleware else []
|
73
|
+
|
74
|
+
# Lifecycle handlers
|
75
|
+
self._on_startup = list(on_startup) if on_startup else []
|
76
|
+
self._on_shutdown = list(on_shutdown) if on_shutdown else []
|
77
|
+
|
78
|
+
# Starlette app (created lazily)
|
79
|
+
self._starlette_app: Optional[Starlette] = None
|
80
|
+
|
81
|
+
def add_middleware(
|
82
|
+
self,
|
83
|
+
middleware_class: type,
|
84
|
+
**options: Any,
|
85
|
+
) -> None:
|
86
|
+
"""
|
87
|
+
Add middleware to the application.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
middleware_class: Middleware class
|
91
|
+
**options: Middleware configuration options
|
92
|
+
|
93
|
+
Example:
|
94
|
+
from starlette.middleware.cors import CORSMiddleware
|
95
|
+
|
96
|
+
app.add_middleware(
|
97
|
+
CORSMiddleware,
|
98
|
+
allow_origins=["*"],
|
99
|
+
allow_methods=["*"],
|
100
|
+
)
|
101
|
+
"""
|
102
|
+
self._middleware.append(Middleware(middleware_class, **options))
|
103
|
+
# Invalidate cached Starlette app
|
104
|
+
self._starlette_app = None
|
105
|
+
|
106
|
+
def on_event(self, event_type: str) -> Callable:
|
107
|
+
"""
|
108
|
+
Register lifecycle event handler.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
event_type: Either "startup" or "shutdown"
|
112
|
+
|
113
|
+
Example:
|
114
|
+
@app.on_event("startup")
|
115
|
+
async def startup():
|
116
|
+
print("Starting up!")
|
117
|
+
|
118
|
+
@app.on_event("shutdown")
|
119
|
+
async def shutdown():
|
120
|
+
print("Shutting down!")
|
121
|
+
"""
|
122
|
+
|
123
|
+
def decorator(func: Callable) -> Callable:
|
124
|
+
if event_type == "startup":
|
125
|
+
self._on_startup.append(func)
|
126
|
+
elif event_type == "shutdown":
|
127
|
+
self._on_shutdown.append(func)
|
128
|
+
else:
|
129
|
+
raise ValueError(f"Invalid event type: {event_type}")
|
130
|
+
# Invalidate cached Starlette app
|
131
|
+
self._starlette_app = None
|
132
|
+
return func
|
133
|
+
|
134
|
+
return decorator
|
135
|
+
|
136
|
+
def include_router(
|
137
|
+
self,
|
138
|
+
router: Router,
|
139
|
+
prefix: str = "",
|
140
|
+
tags: Optional[List[str]] = None,
|
141
|
+
) -> None:
|
142
|
+
"""
|
143
|
+
Include a router in the application.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
router: Router to include
|
147
|
+
prefix: URL prefix for all routes
|
148
|
+
tags: Tags to add to all routes
|
149
|
+
|
150
|
+
Example:
|
151
|
+
users_router = Router()
|
152
|
+
@users_router.get("/{user_id}")
|
153
|
+
async def get_user(user_id: str):
|
154
|
+
return {"id": user_id}
|
155
|
+
|
156
|
+
app.include_router(users_router, prefix="/users", tags=["users"])
|
157
|
+
"""
|
158
|
+
self._router.include_router(router, prefix=prefix, tags=tags)
|
159
|
+
# Invalidate cached Starlette app
|
160
|
+
self._starlette_app = None
|
161
|
+
|
162
|
+
def get(self, path: str, **kwargs: Any) -> Callable:
|
163
|
+
"""
|
164
|
+
Decorator for GET requests.
|
165
|
+
|
166
|
+
Example:
|
167
|
+
@app.get("/items/{item_id}")
|
168
|
+
async def get_item(item_id: str):
|
169
|
+
return {"id": item_id}
|
170
|
+
"""
|
171
|
+
return self._router.get(path, **kwargs)
|
172
|
+
|
173
|
+
def post(self, path: str, **kwargs: Any) -> Callable:
|
174
|
+
"""Decorator for POST requests."""
|
175
|
+
return self._router.post(path, **kwargs)
|
176
|
+
|
177
|
+
def put(self, path: str, **kwargs: Any) -> Callable:
|
178
|
+
"""Decorator for PUT requests."""
|
179
|
+
return self._router.put(path, **kwargs)
|
180
|
+
|
181
|
+
def patch(self, path: str, **kwargs: Any) -> Callable:
|
182
|
+
"""Decorator for PATCH requests."""
|
183
|
+
return self._router.patch(path, **kwargs)
|
184
|
+
|
185
|
+
def delete(self, path: str, **kwargs: Any) -> Callable:
|
186
|
+
"""Decorator for DELETE requests."""
|
187
|
+
return self._router.delete(path, **kwargs)
|
188
|
+
|
189
|
+
def route(self, path: str, methods: List[str], **kwargs: Any) -> Callable:
|
190
|
+
"""
|
191
|
+
Decorator for custom methods.
|
192
|
+
|
193
|
+
Example:
|
194
|
+
@app.route("/items", methods=["GET", "POST"])
|
195
|
+
async def items():
|
196
|
+
return {"items": []}
|
197
|
+
"""
|
198
|
+
return self._router.route(path, methods, **kwargs)
|
199
|
+
|
200
|
+
def _build_starlette_app(self) -> Starlette:
|
201
|
+
"""Build the Starlette application from routes and middleware."""
|
202
|
+
# Convert Vega routes to Starlette routes
|
203
|
+
starlette_routes = [
|
204
|
+
route.to_starlette_route() for route in self._router.get_routes()
|
205
|
+
]
|
206
|
+
|
207
|
+
# Create Starlette app
|
208
|
+
app = Starlette(
|
209
|
+
debug=self.debug,
|
210
|
+
routes=starlette_routes,
|
211
|
+
middleware=self._middleware,
|
212
|
+
on_startup=self._on_startup,
|
213
|
+
on_shutdown=self._on_shutdown,
|
214
|
+
)
|
215
|
+
|
216
|
+
return app
|
217
|
+
|
218
|
+
def _get_app(self) -> Starlette:
|
219
|
+
"""Get or create the Starlette app."""
|
220
|
+
if self._starlette_app is None:
|
221
|
+
self._starlette_app = self._build_starlette_app()
|
222
|
+
return self._starlette_app
|
223
|
+
|
224
|
+
async def __call__(self, scope: dict, receive: Callable, send: Callable) -> None:
|
225
|
+
"""
|
226
|
+
ASGI application callable.
|
227
|
+
|
228
|
+
This makes VegaApp a valid ASGI application.
|
229
|
+
"""
|
230
|
+
app = self._get_app()
|
231
|
+
await app(scope, receive, send)
|
232
|
+
|
233
|
+
|
234
|
+
__all__ = ["VegaApp"]
|