Added file upload support
This commit is contained in:
@@ -35,8 +35,12 @@ from .executor import (
|
||||
execute_function,
|
||||
)
|
||||
from .router import router, mizan_exception_handler, mizan_validation_handler
|
||||
from mizan_core.upload import File, Upload, UploadedFile
|
||||
|
||||
__all__ = [
|
||||
"Upload",
|
||||
"File",
|
||||
"UploadedFile",
|
||||
"router",
|
||||
"mizan_exception_handler",
|
||||
"mizan_validation_handler",
|
||||
|
||||
@@ -14,16 +14,20 @@ FastAPI router exposing Mizan's HTTP endpoints:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
from starlette.datastructures import UploadFile
|
||||
|
||||
from mizan_core.registry import get_context_groups, get_function
|
||||
from mizan_core.upload import UploadedFile, bind_uploads
|
||||
|
||||
from .executor import (
|
||||
BadRequest,
|
||||
ErrorCode,
|
||||
MizanError,
|
||||
NotFound,
|
||||
@@ -59,13 +63,58 @@ class CallBody(BaseModel):
|
||||
args: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
async def _parse_call(request: Request) -> tuple[str, dict[str, Any]]:
|
||||
"""Read a call request, JSON or multipart. Returns `(fn, args)`.
|
||||
|
||||
Multipart carries the non-file fields in a JSON `args` part and each file as
|
||||
its own part; the file parts bind into the Input's Upload fields with the
|
||||
declarative `File(...)` constraints enforced.
|
||||
"""
|
||||
content_type = request.headers.get("content-type", "")
|
||||
if content_type.startswith("multipart/form-data"):
|
||||
form = await request.form()
|
||||
fn = form.get("fn")
|
||||
if not isinstance(fn, str) or not fn:
|
||||
raise BadRequest("Missing 'fn' field")
|
||||
raw_args = form.get("args")
|
||||
try:
|
||||
args: dict[str, Any] = json.loads(raw_args) if raw_args else {}
|
||||
except (TypeError, ValueError):
|
||||
raise BadRequest("Invalid JSON in 'args' field")
|
||||
|
||||
fn_class = get_function(fn)
|
||||
input_cls = getattr(fn_class, "Input", None) if fn_class else None
|
||||
if input_cls is not None and hasattr(input_cls, "model_fields"):
|
||||
files: dict[str, list[UploadedFile]] = {}
|
||||
for key in set(form.keys()):
|
||||
wrapped = [
|
||||
UploadedFile(p.filename, p.content_type, await p.read())
|
||||
for p in form.getlist(key)
|
||||
if isinstance(p, UploadFile)
|
||||
]
|
||||
if wrapped:
|
||||
files[key] = wrapped
|
||||
err = bind_uploads(input_cls, args, files)
|
||||
if err is not None:
|
||||
raise BadRequest(err)
|
||||
return fn, args
|
||||
|
||||
try:
|
||||
body = CallBody(**(await request.json()))
|
||||
except (ValueError, ValidationError):
|
||||
raise BadRequest("Invalid request body")
|
||||
return body.fn, body.args
|
||||
|
||||
|
||||
@router.post("/call/")
|
||||
async def function_call(body: CallBody, request: Request) -> JSONResponse:
|
||||
"""RPC dispatch — `{"fn": "...", "args": {...}}` → `{"result": ..., "invalidate": [...], "merge"?: [...]}`."""
|
||||
fn_class = get_function(body.fn)
|
||||
result = await execute_function(request, body.fn, body.args)
|
||||
invalidate = compute_invalidation(fn_class, body.args)
|
||||
merges = compute_merges(fn_class, body.args, result)
|
||||
async def function_call(request: Request) -> JSONResponse:
|
||||
"""RPC dispatch — `{"fn": "...", "args": {...}}` (JSON) or multipart with file
|
||||
parts → `{"result": ..., "invalidate": [...], "merge"?: [...]}`."""
|
||||
fn, args = await _parse_call(request)
|
||||
fn_class = get_function(fn)
|
||||
result = await execute_function(request, fn, args)
|
||||
invalidate = compute_invalidation(fn_class, args)
|
||||
merges = compute_merges(fn_class, args, result)
|
||||
payload: dict[str, Any] = {"result": result, "invalidate": invalidate}
|
||||
if merges:
|
||||
payload["merge"] = merges
|
||||
|
||||
Reference in New Issue
Block a user