vega-framework 0.2.1__py3-none-any.whl → 0.2.3__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/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"]
vega/web/routing.py CHANGED
@@ -6,6 +6,7 @@ from functools import wraps
6
6
 
7
7
  from starlette.routing import Route as StarletteRoute, Mount
8
8
  from starlette.requests import Request as StarletteRequest
9
+ from pydantic import BaseModel, ValidationError
9
10
 
10
11
  from .exceptions import HTTPException
11
12
  from .request import Request
@@ -13,6 +14,14 @@ from .response import JSONResponse, Response, create_response
13
14
  from .route_middleware import MiddlewareChain
14
15
 
15
16
 
17
+ def _is_pydantic_model(type_hint: Any) -> bool:
18
+ """Check if a type hint is a Pydantic BaseModel"""
19
+ try:
20
+ return inspect.isclass(type_hint) and issubclass(type_hint, BaseModel)
21
+ except (TypeError, AttributeError):
22
+ return False
23
+
24
+
16
25
  class Route:
17
26
  """
18
27
  Represents a single route in the application.
@@ -73,6 +82,12 @@ class Route:
73
82
  sig = inspect.signature(self.endpoint)
74
83
  params = sig.parameters
75
84
 
85
+ # Get type hints for the function
86
+ try:
87
+ type_hints = get_type_hints(self.endpoint)
88
+ except Exception:
89
+ type_hints = {}
90
+
76
91
  # Prepare kwargs for function call
77
92
  kwargs = {}
78
93
 
@@ -93,6 +108,37 @@ class Route:
93
108
  if param_name in params:
94
109
  kwargs[param_name] = param_value
95
110
 
111
+ # Check for Pydantic model parameters (body parsing)
112
+ # Only parse the first Pydantic model found
113
+ body_parsed = False
114
+ for param_name, param in params.items():
115
+ # Skip if already processed (request or path param)
116
+ if param_name in kwargs or param_name == "request":
117
+ continue
118
+
119
+ # Get type hint for this parameter
120
+ param_type = type_hints.get(param_name, param.annotation)
121
+
122
+ # If it's a Pydantic model, parse the request body (only once)
123
+ if _is_pydantic_model(param_type) and not body_parsed:
124
+ try:
125
+ body_data = await request.json()
126
+ # Validate and parse using Pydantic
127
+ kwargs[param_name] = param_type(**body_data)
128
+ body_parsed = True
129
+ except ValidationError as e:
130
+ # Return validation errors in a user-friendly format
131
+ return JSONResponse(
132
+ content={"detail": e.errors()},
133
+ status_code=422,
134
+ )
135
+ except Exception as e:
136
+ # Handle JSON parsing errors
137
+ return JSONResponse(
138
+ content={"detail": f"Invalid JSON body: {str(e)}"},
139
+ status_code=400,
140
+ )
141
+
96
142
  # Execute middleware chain if present
97
143
  if self.middlewares:
98
144
  middleware_chain = MiddlewareChain(self.middlewares)
@@ -113,6 +159,9 @@ class Route:
113
159
  # Handle different return types
114
160
  if isinstance(result, (Response, JSONResponse)):
115
161
  return result
162
+ elif isinstance(result, BaseModel):
163
+ # Serialize Pydantic models using model_dump()
164
+ return JSONResponse(content=result.model_dump(), status_code=self.status_code)
116
165
  elif isinstance(result, dict):
117
166
  return JSONResponse(content=result, status_code=self.status_code)
118
167
  elif isinstance(result, (list, tuple)):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vega-framework
3
- Version: 0.2.1
3
+ Version: 0.2.3
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
@@ -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
- vega/web/routing.py,sha256=5uxiroobZb194Vg_MNzJyd-B7BOiitdwQuH2oq9Z5B8,10506
97
- vega_framework-0.2.1.dist-info/METADATA,sha256=6VpDFAD9nzakWdVxQ1YtHWX2qaisS89opDKe2dcfU8s,11226
98
- vega_framework-0.2.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
99
- vega_framework-0.2.1.dist-info/entry_points.txt,sha256=p3gyTmPYjNRLbuiKS-hG3ytWd-ssBweFy6VZ-F9FTNk,42
100
- vega_framework-0.2.1.dist-info/licenses/LICENSE,sha256=wlHh1MBTcs2kSQr99P30mZ61s7uh7Cp9Rk0YiJxots0,1084
101
- vega_framework-0.2.1.dist-info/RECORD,,
98
+ vega/web/routing.py,sha256=PLytM0eBBoxIkewj6BFJ6f7py3Uqery1H94ANOqQkjs,12840
99
+ vega_framework-0.2.3.dist-info/METADATA,sha256=HtFX2scSAqbbUV113sln9FjQs0DqPdY3nglj4nWAYT4,11226
100
+ vega_framework-0.2.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
101
+ vega_framework-0.2.3.dist-info/entry_points.txt,sha256=p3gyTmPYjNRLbuiKS-hG3ytWd-ssBweFy6VZ-F9FTNk,42
102
+ vega_framework-0.2.3.dist-info/licenses/LICENSE,sha256=wlHh1MBTcs2kSQr99P30mZ61s7uh7Cp9Rk0YiJxots0,1084
103
+ vega_framework-0.2.3.dist-info/RECORD,,