Expand lab seed tiers, OpenAPI PARAMS, and console DATA/Params UX.

This commit is contained in:
2026-07-18 08:46:39 +03:00
parent f8d3cbdd59
commit 63cc409424
71 changed files with 38380 additions and 796 deletions
+45
View File
@@ -8,7 +8,9 @@ import uuid
from collections.abc import Awaitable, Callable
from fastapi import Request, Response
from starlette.datastructures import MutableHeaders
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import Message
logger = logging.getLogger(__name__)
@@ -40,3 +42,46 @@ class RequestContextMiddleware(BaseHTTPMiddleware):
},
)
return response
class HeadAsGetMiddleware(BaseHTTPMiddleware):
"""Serve HEAD for every GET route (deep + stub) with an empty body.
FastAPI ``add_api_route(methods=['GET'])`` and some router setups omit HEAD;
contract matrix probes expect synthetic HEAD on each GET path.
"""
async def dispatch(self, request: Request, call_next: RequestHandler) -> Response:
if request.method != "HEAD":
return await call_next(request)
# Replay as GET, then strip the body while preserving status/headers.
request.scope["method"] = "GET"
response = await call_next(request)
body = bytearray()
async for chunk in response.body_iterator:
if isinstance(chunk, str):
body.extend(chunk.encode(response.charset or "utf-8"))
else:
body.extend(chunk)
headers = MutableHeaders(scope={"type": "http", "headers": []})
for key, value in response.headers.items():
if key.lower() in {"content-length", "content-type", "transfer-encoding"}:
continue
headers.append(key, value)
headers["content-length"] = str(len(body))
if response.media_type:
headers["content-type"] = response.media_type
async def _empty_receive() -> Message:
return {"type": "http.request", "body": b"", "more_body": False}
del _empty_receive
return Response(
content=b"",
status_code=response.status_code,
headers=headers,
media_type=response.media_type,
)