diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 79fba93188..0b9163c97c 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -53,6 +53,8 @@ class GenerateJsonSchema(_GenerateJsonSchema): ) if bytes_mode == "base64": json_schema["contentEncoding"] = "base64" + else: + json_schema["format"] = "binary" # For compatibility with OAS 3.0 self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes) return json_schema diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index 479e1a7c3b..30d4e7c659 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -139,7 +139,11 @@ class UploadFile(StarletteUploadFile): def __get_pydantic_json_schema__( cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler ) -> dict[str, Any]: - return {"type": "string", "contentMediaType": "application/octet-stream"} + return { + "type": "string", + "format": "binary", # For compatibility with OAS 3.0 + "contentMediaType": "application/octet-stream", + } @classmethod def __get_pydantic_core_schema__( diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py index 5332795f4c..033871ab35 100644 --- a/tests/test_request_params/test_file/test_list.py +++ b/tests/test_request_params/test_file/test_list.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient +from inline_snapshot import Is, snapshot from .utils import get_body_model_name @@ -33,21 +34,24 @@ def test_list_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p": { - "type": "array", - "items": { - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p": { + "type": "array", + "items": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, + "title": "P", }, - "title": "P", }, - }, - "required": ["p"], - "title": body_model_name, - "type": "object", - } + "required": ["p"], + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -114,21 +118,24 @@ def test_list_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_alias": { - "type": "array", - "items": { - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_alias": { + "type": "array", + "items": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, + "title": "P Alias", }, - "title": "P Alias", }, - }, - "required": ["p_alias"], - "title": body_model_name, - "type": "object", - } + "required": ["p_alias"], + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -223,21 +230,24 @@ def test_list_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_val_alias": { - "type": "array", - "items": { - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_val_alias": { + "type": "array", + "items": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, + "title": "P Val Alias", }, - "title": "P Val Alias", }, - }, - "required": ["p_val_alias"], - "title": body_model_name, - "type": "object", - } + "required": ["p_val_alias"], + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -343,21 +353,24 @@ def test_list_alias_and_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_val_alias": { - "type": "array", - "items": { - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_val_alias": { + "type": "array", + "items": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, + "title": "P Val Alias", }, - "title": "P Val Alias", }, - }, - "required": ["p_val_alias"], - "title": body_model_name, - "type": "object", - } + "required": ["p_val_alias"], + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py index 3d1aac25e2..280c6b64fb 100644 --- a/tests/test_request_params/test_file/test_optional.py +++ b/tests/test_request_params/test_file/test_optional.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient +from inline_snapshot import Is, snapshot from .utils import get_body_model_name @@ -33,19 +34,25 @@ def test_optional_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p": { - "anyOf": [ - {"type": "string", "contentMediaType": "application/octet-stream"}, - {"type": "null"}, - ], - "title": "P", - } - }, - "title": body_model_name, - "type": "object", - } + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p": { + "anyOf": [ + { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, + {"type": "null"}, + ], + "title": "P", + } + }, + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -105,19 +112,25 @@ def test_optional_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_alias": { - "anyOf": [ - {"type": "string", "contentMediaType": "application/octet-stream"}, - {"type": "null"}, - ], - "title": "P Alias", - } - }, - "title": body_model_name, - "type": "object", - } + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_alias": { + "anyOf": [ + { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, + {"type": "null"}, + ], + "title": "P Alias", + } + }, + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -196,19 +209,25 @@ def test_optional_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_val_alias": { - "anyOf": [ - {"type": "string", "contentMediaType": "application/octet-stream"}, - {"type": "null"}, - ], - "title": "P Val Alias", - } - }, - "title": body_model_name, - "type": "object", - } + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_val_alias": { + "anyOf": [ + { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, + {"type": "null"}, + ], + "title": "P Val Alias", + } + }, + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -292,19 +311,25 @@ def test_optional_alias_and_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_val_alias": { - "anyOf": [ - {"type": "string", "contentMediaType": "application/octet-stream"}, - {"type": "null"}, - ], - "title": "P Val Alias", - } - }, - "title": body_model_name, - "type": "object", - } + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_val_alias": { + "anyOf": [ + { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, + {"type": "null"}, + ], + "title": "P Val Alias", + } + }, + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py index 3c211b1e8e..463c56f436 100644 --- a/tests/test_request_params/test_file/test_optional_list.py +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient +from inline_snapshot import Is, snapshot from .utils import get_body_model_name @@ -35,25 +36,28 @@ def test_optional_list_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, }, - }, - {"type": "null"}, - ], - "title": "P", - } - }, - "title": body_model_name, - "type": "object", - } + {"type": "null"}, + ], + "title": "P", + } + }, + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -113,25 +117,28 @@ def test_optional_list_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_alias": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_alias": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, }, - }, - {"type": "null"}, - ], - "title": "P Alias", - } - }, - "title": body_model_name, - "type": "object", - } + {"type": "null"}, + ], + "title": "P Alias", + } + }, + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -205,25 +212,28 @@ def test_optional_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_val_alias": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_val_alias": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, }, - }, - {"type": "null"}, - ], - "title": "P Val Alias", - } - }, - "title": body_model_name, - "type": "object", - } + {"type": "null"}, + ], + "title": "P Val Alias", + } + }, + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -304,25 +314,28 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_val_alias": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_val_alias": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, }, - }, - {"type": "null"}, - ], - "title": "P Val Alias", - } - }, - "title": body_model_name, - "type": "object", - } + {"type": "null"}, + ], + "title": "P Val Alias", + } + }, + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py index 22d6c0fffd..19efeb0cc4 100644 --- a/tests/test_request_params/test_file/test_required.py +++ b/tests/test_request_params/test_file/test_required.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient +from inline_snapshot import Is, snapshot from .utils import get_body_model_name @@ -33,18 +34,21 @@ def test_required_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p": { - "title": "P", - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p": { + "title": "P", + "format": "binary", + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, - }, - "required": ["p"], - "title": body_model_name, - "type": "object", - } + "required": ["p"], + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -111,18 +115,21 @@ def test_required_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_alias": { - "title": "P Alias", - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_alias": { + "title": "P Alias", + "format": "binary", + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, - }, - "required": ["p_alias"], - "title": body_model_name, - "type": "object", - } + "required": ["p_alias"], + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -219,18 +226,21 @@ def test_required_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_val_alias": { - "title": "P Val Alias", - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_val_alias": { + "title": "P Val Alias", + "format": "binary", + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, - }, - "required": ["p_val_alias"], - "title": body_model_name, - "type": "object", - } + "required": ["p_val_alias"], + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( @@ -332,18 +342,21 @@ def test_required_alias_and_validation_alias_schema(path: str): openapi = app.openapi() body_model_name = get_body_model_name(openapi, path) - assert app.openapi()["components"]["schemas"][body_model_name] == { - "properties": { - "p_val_alias": { - "title": "P Val Alias", - "type": "string", - "contentMediaType": "application/octet-stream", + assert app.openapi()["components"]["schemas"][body_model_name] == snapshot( + { + "properties": { + "p_val_alias": { + "title": "P Val Alias", + "type": "string", + "format": "binary", + "contentMediaType": "application/octet-stream", + }, }, - }, - "required": ["p_val_alias"], - "title": body_model_name, - "type": "object", - } + "required": ["p_val_alias"], + "title": Is(body_model_name), + "type": "object", + } + ) @pytest.mark.parametrize( diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index 797225bc2d..144a448101 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -162,6 +162,7 @@ def test_openapi_schema(client: TestClient): "properties": { "file": { "title": "File", + "format": "binary", "contentMediaType": "application/octet-stream", "type": "string", } @@ -174,6 +175,7 @@ def test_openapi_schema(client: TestClient): "properties": { "file": { "title": "File", + "format": "binary", "type": "string", "contentMediaType": "application/octet-stream", } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py index 4e3c33818e..0ac224f9be 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -136,6 +136,7 @@ def test_openapi_schema(client: TestClient): "anyOf": [ { "type": "string", + "format": "binary", "contentMediaType": "application/octet-stream", }, {"type": "null"}, @@ -152,6 +153,7 @@ def test_openapi_schema(client: TestClient): "anyOf": [ { "type": "string", + "format": "binary", "contentMediaType": "application/octet-stream", }, {"type": "null"}, diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index bccc617046..75489b558d 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -121,6 +121,7 @@ def test_openapi_schema(client: TestClient): "properties": { "file": { "title": "File", + "format": "binary", "type": "string", "description": "A file read as bytes", "contentMediaType": "application/octet-stream", @@ -134,6 +135,7 @@ def test_openapi_schema(client: TestClient): "properties": { "file": { "title": "File", + "format": "binary", "contentMediaType": "application/octet-stream", "type": "string", "description": "A file read as UploadFile", diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index 123468d48f..748ba50eb1 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -197,6 +197,7 @@ def test_openapi_schema(client: TestClient): "type": "array", "items": { "type": "string", + "format": "binary", "contentMediaType": "application/octet-stream", }, } @@ -212,6 +213,7 @@ def test_openapi_schema(client: TestClient): "type": "array", "items": { "type": "string", + "format": "binary", "contentMediaType": "application/octet-stream", }, } diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py index 2f554d9489..56d792d849 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003.py @@ -167,6 +167,7 @@ def test_openapi_schema(client: TestClient): "type": "array", "items": { "type": "string", + "format": "binary", "contentMediaType": "application/octet-stream", }, "description": "Multiple files as bytes", @@ -183,6 +184,7 @@ def test_openapi_schema(client: TestClient): "type": "array", "items": { "type": "string", + "format": "binary", "contentMediaType": "application/octet-stream", }, "description": "Multiple files as UploadFile", diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py index cc10d8bec5..606c97381b 100644 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py @@ -197,11 +197,13 @@ def test_openapi_schema(client: TestClient): "properties": { "file": { "title": "File", + "format": "binary", "type": "string", "contentMediaType": "application/octet-stream", }, "fileb": { "title": "Fileb", + "format": "binary", "contentMediaType": "application/octet-stream", "type": "string", },