From 34053064693632d01c9daa8576bfad83ff87f349 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 6 Mar 2026 18:19:31 +0100 Subject: [PATCH] Fix schema for `byte` fields inside JSON (do not add `"format": "binary"`) --- fastapi/_compat/v2.py | 27 ++++++++++++++++++--------- fastapi/params.py | 27 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 0b9163c97c..f78dbcef61 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -45,16 +45,25 @@ class GenerateJsonSchema(_GenerateJsonSchema): # TODO: remove when this is merged (or equivalent): https://github.com/pydantic/pydantic/pull/12841 # and dropping support for any version of Pydantic before that one (so, in a very long time) def bytes_schema(self, schema: CoreSchema) -> JsonSchemaValue: - json_schema = {"type": "string", "contentMediaType": "application/octet-stream"} - bytes_mode = ( - self._config.ser_json_bytes - if self.mode == "serialization" - else self._config.val_json_bytes - ) - if bytes_mode == "base64": - json_schema["contentEncoding"] = "base64" + is_file_upload = schema.get("metadata", {}).get("fastapi_file_upload", False) + if is_file_upload: + json_schema: JsonSchemaValue = { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + } else: - json_schema["format"] = "binary" # For compatibility with OAS 3.0 + json_schema = { + "type": "string", + "contentMediaType": "application/octet-stream", + } + bytes_mode = ( + self._config.ser_json_bytes + if self.mode == "serialization" + else self._config.val_json_bytes + ) + if bytes_mode == "base64": + json_schema["contentEncoding"] = "base64" self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes) return json_schema diff --git a/fastapi/params.py b/fastapi/params.py index 68f9870810..df0dfb1094 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -661,6 +661,32 @@ class Form(Body): # type: ignore[misc] ) +class _FileUploadMarker: + "Pydantic metadata marker to tag bytes CoreSchemas as file uploads." + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: Any + ) -> dict[str, Any]: + schema = handler(source) + + # Find the inner type schema (if nullable or list) + inner_type_schema = schema + if inner_type_schema.get("type") != "bytes": + if inner_type_schema.get("type") == "list": + inner_type_schema = inner_type_schema["items_schema"] + elif "schema" in inner_type_schema: + inner_type_schema = inner_type_schema["schema"] + if inner_type_schema.get("type") == "list": + inner_type_schema = inner_type_schema["items_schema"] + + # If the inner type is bytes, add the file upload marker metadata + if inner_type_schema.get("type") == "bytes": + metadata: dict[str, Any] = inner_type_schema.setdefault("metadata", {}) + metadata["fastapi_file_upload"] = True + return schema + + class File(Form): # type: ignore[misc] def __init__( self, @@ -741,6 +767,7 @@ class File(Form): # type: ignore[misc] json_schema_extra=json_schema_extra, **extra, ) + self.metadata.append(_FileUploadMarker()) @dataclass(frozen=True)