Skip to content

pixano_inference.api.v1.helpers

Shared helpers for the /v1 inference routes: handle validation, dispatch, multipart.

build_binary_request(request_type, metadata, **extra_fields)

Parse JSON metadata + binary media fields into a typed request model.

Source code in pixano_inference/api/v1/helpers.py
def build_binary_request(request_type: type[_RequestT], metadata: str, **extra_fields: Any) -> _RequestT:
    """Parse JSON metadata + binary media fields into a typed request model."""
    try:
        payload = json.loads(metadata)
    except JSONDecodeError as exc:
        raise HTTPException(status_code=400, detail=f"Invalid binary inference metadata: {exc.msg}") from exc
    payload.update(extra_fields)
    try:
        return request_type.model_validate(payload)
    except ValidationError as exc:
        raise HTTPException(status_code=422, detail=exc.errors()) from exc

build_binary_request_from_request(request, request_type, *, file_field, payload_key, max_part_size=BINARY_METADATA_MAX_PART_SIZE, **extra_fields) async

Parse a multipart request (metadata JSON part + one or more binary media parts).

Source code in pixano_inference/api/v1/helpers.py
async def build_binary_request_from_request(
    request: Request,
    request_type: type[_RequestT],
    *,
    file_field: str,
    payload_key: str,
    max_part_size: int = BINARY_METADATA_MAX_PART_SIZE,
    **extra_fields: Any,
) -> _RequestT:
    """Parse a multipart request (metadata JSON part + one or more binary media parts)."""
    form = await request.form(max_part_size=max_part_size)
    metadata_upload = form.get("metadata")
    payload_field = form.get(file_field)

    metadata_text: str | None
    if isinstance(metadata_upload, (UploadFile, StarletteUploadFile)):
        metadata_text = await _read_metadata_upload(metadata_upload)
    elif isinstance(metadata_upload, str):
        metadata_text = metadata_upload
    else:
        metadata_text = None
    if metadata_text is None:
        raise HTTPException(status_code=400, detail="Missing binary inference metadata.")

    if file_field == "image":
        if not isinstance(payload_field, (UploadFile, StarletteUploadFile)):
            raise HTTPException(status_code=400, detail="Missing binary image payload.")
        extra_fields[payload_key] = await payload_field.read()
    else:
        files = form.getlist(file_field)
        if not files or not all(isinstance(file, (UploadFile, StarletteUploadFile)) for file in files):
            raise HTTPException(status_code=400, detail="Missing binary frame payload.")
        uploads: list[Any] = [f for f in files if isinstance(f, (UploadFile, StarletteUploadFile))]
        extra_fields[payload_key] = [await f.read() for f in uploads]

    return build_binary_request(request_type, metadata_text, **extra_fields)

get_validated_handle(deployment_manager, model_name, expected_capability)

Resolve a deployment handle and check its capability, or raise 404/400.

Source code in pixano_inference/api/v1/helpers.py
def get_validated_handle(deployment_manager: DeploymentManager, model_name: str, expected_capability: str) -> Any:
    """Resolve a deployment handle and check its capability, or raise 404/400."""
    handle = deployment_manager.get_handle(model_name)
    if handle is None:
        raise HTTPException(status_code=404, detail=f"Model '{model_name}' not found")
    actual_capability = deployment_manager.get_model_capability(model_name)
    if actual_capability != expected_capability:
        raise HTTPException(
            status_code=400,
            detail=(
                f"Model '{model_name}' does not support '{expected_capability}' inference. "
                f"It is deployed as '{actual_capability}'."
            ),
        )
    return handle

run_inference(deployment_manager, model_name, input_data, expected_capability) async

Dispatch inference through the Serve handle with a per-capability timeout.

Source code in pixano_inference/api/v1/helpers.py
async def run_inference(
    deployment_manager: DeploymentManager,
    model_name: str,
    input_data: BaseModel,
    expected_capability: str,
) -> dict[str, Any]:
    """Dispatch inference through the Serve handle with a per-capability timeout."""
    handle = get_validated_handle(deployment_manager, model_name, expected_capability)
    timeout_s = deployment_manager.get_timeout(model_name, expected_capability)
    start_time = time.time()

    response = handle.predict.remote(input_data)
    try:
        result = await asyncio.wait_for(_await_response(response), timeout=timeout_s)
    except asyncio.TimeoutError:
        try:
            response.cancel()
        except Exception:
            pass
        raise HTTPException(status_code=504, detail=f"Inference timed out after {timeout_s:g}s.")
    except HTTPException:
        raise
    except Exception as exc:
        logger.exception("Inference error for model '%s' on '%s': %s", model_name, expected_capability, exc)
        raise HTTPException(status_code=500, detail="Inference error.")

    return {
        "id": f"ray-{model_name}-{uuid4().hex[:12]}",
        "status": "SUCCESS",
        "timestamp": datetime.now(timezone.utc),
        "processing_time": time.time() - start_time,
        "metadata": deployment_manager.get_model_metadata(model_name),
        "data": result.model_dump(by_alias=True),
    }