Browse Source

Update schema for UploadFile (add `format: binary`)

pull/15069/head
Yurii Motov 4 months ago
parent
commit
506d278db7
  1. 2
      fastapi/_compat/v2.py
  2. 6
      fastapi/datastructures.py
  3. 117
      tests/test_request_params/test_file/test_list.py
  4. 129
      tests/test_request_params/test_file/test_optional.py
  5. 157
      tests/test_request_params/test_file/test_optional_list.py
  6. 101
      tests/test_request_params/test_file/test_required.py
  7. 2
      tests/test_tutorial/test_request_files/test_tutorial001.py
  8. 2
      tests/test_tutorial/test_request_files/test_tutorial001_02.py
  9. 2
      tests/test_tutorial/test_request_files/test_tutorial001_03.py
  10. 2
      tests/test_tutorial/test_request_files/test_tutorial002.py
  11. 2
      tests/test_tutorial/test_request_files/test_tutorial003.py
  12. 2
      tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py

2
fastapi/_compat/v2.py

@ -53,6 +53,8 @@ class GenerateJsonSchema(_GenerateJsonSchema):
) )
if bytes_mode == "base64": if bytes_mode == "base64":
json_schema["contentEncoding"] = "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) self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes)
return json_schema return json_schema

6
fastapi/datastructures.py

@ -139,7 +139,11 @@ class UploadFile(StarletteUploadFile):
def __get_pydantic_json_schema__( def __get_pydantic_json_schema__(
cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler
) -> dict[str, Any]: ) -> 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 @classmethod
def __get_pydantic_core_schema__( def __get_pydantic_core_schema__(

117
tests/test_request_params/test_file/test_list.py

@ -3,6 +3,7 @@ from typing import Annotated
import pytest import pytest
from fastapi import FastAPI, File, UploadFile from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import Is, snapshot
from .utils import get_body_model_name from .utils import get_body_model_name
@ -33,21 +34,24 @@ def test_list_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p": { "properties": {
"type": "array", "p": {
"items": { "type": "array",
"type": "string", "items": {
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
"title": "P",
}, },
"title": "P",
}, },
}, "required": ["p"],
"required": ["p"], "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -114,21 +118,24 @@ def test_list_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_alias": { "properties": {
"type": "array", "p_alias": {
"items": { "type": "array",
"type": "string", "items": {
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
"title": "P Alias",
}, },
"title": "P Alias",
}, },
}, "required": ["p_alias"],
"required": ["p_alias"], "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -223,21 +230,24 @@ def test_list_validation_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_val_alias": { "properties": {
"type": "array", "p_val_alias": {
"items": { "type": "array",
"type": "string", "items": {
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
"title": "P Val Alias",
}, },
"title": "P Val Alias",
}, },
}, "required": ["p_val_alias"],
"required": ["p_val_alias"], "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -343,21 +353,24 @@ def test_list_alias_and_validation_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_val_alias": { "properties": {
"type": "array", "p_val_alias": {
"items": { "type": "array",
"type": "string", "items": {
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
"title": "P Val Alias",
}, },
"title": "P Val Alias",
}, },
}, "required": ["p_val_alias"],
"required": ["p_val_alias"], "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(

129
tests/test_request_params/test_file/test_optional.py

@ -3,6 +3,7 @@ from typing import Annotated
import pytest import pytest
from fastapi import FastAPI, File, UploadFile from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import Is, snapshot
from .utils import get_body_model_name from .utils import get_body_model_name
@ -33,19 +34,25 @@ def test_optional_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p": { "properties": {
"anyOf": [ "p": {
{"type": "string", "contentMediaType": "application/octet-stream"}, "anyOf": [
{"type": "null"}, {
], "type": "string",
"title": "P", "format": "binary",
} "contentMediaType": "application/octet-stream",
}, },
"title": body_model_name, {"type": "null"},
"type": "object", ],
} "title": "P",
}
},
"title": Is(body_model_name),
"type": "object",
}
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -105,19 +112,25 @@ def test_optional_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_alias": { "properties": {
"anyOf": [ "p_alias": {
{"type": "string", "contentMediaType": "application/octet-stream"}, "anyOf": [
{"type": "null"}, {
], "type": "string",
"title": "P Alias", "format": "binary",
} "contentMediaType": "application/octet-stream",
}, },
"title": body_model_name, {"type": "null"},
"type": "object", ],
} "title": "P Alias",
}
},
"title": Is(body_model_name),
"type": "object",
}
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -196,19 +209,25 @@ def test_optional_validation_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_val_alias": { "properties": {
"anyOf": [ "p_val_alias": {
{"type": "string", "contentMediaType": "application/octet-stream"}, "anyOf": [
{"type": "null"}, {
], "type": "string",
"title": "P Val Alias", "format": "binary",
} "contentMediaType": "application/octet-stream",
}, },
"title": body_model_name, {"type": "null"},
"type": "object", ],
} "title": "P Val Alias",
}
},
"title": Is(body_model_name),
"type": "object",
}
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -292,19 +311,25 @@ def test_optional_alias_and_validation_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_val_alias": { "properties": {
"anyOf": [ "p_val_alias": {
{"type": "string", "contentMediaType": "application/octet-stream"}, "anyOf": [
{"type": "null"}, {
], "type": "string",
"title": "P Val Alias", "format": "binary",
} "contentMediaType": "application/octet-stream",
}, },
"title": body_model_name, {"type": "null"},
"type": "object", ],
} "title": "P Val Alias",
}
},
"title": Is(body_model_name),
"type": "object",
}
)
@pytest.mark.parametrize( @pytest.mark.parametrize(

157
tests/test_request_params/test_file/test_optional_list.py

@ -3,6 +3,7 @@ from typing import Annotated
import pytest import pytest
from fastapi import FastAPI, File, UploadFile from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import Is, snapshot
from .utils import get_body_model_name from .utils import get_body_model_name
@ -35,25 +36,28 @@ def test_optional_list_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p": { "properties": {
"anyOf": [ "p": {
{ "anyOf": [
"type": "array", {
"items": { "type": "array",
"type": "string", "items": {
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
}, },
}, {"type": "null"},
{"type": "null"}, ],
], "title": "P",
"title": "P", }
} },
}, "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -113,25 +117,28 @@ def test_optional_list_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_alias": { "properties": {
"anyOf": [ "p_alias": {
{ "anyOf": [
"type": "array", {
"items": { "type": "array",
"type": "string", "items": {
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
}, },
}, {"type": "null"},
{"type": "null"}, ],
], "title": "P Alias",
"title": "P Alias", }
} },
}, "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -205,25 +212,28 @@ def test_optional_validation_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_val_alias": { "properties": {
"anyOf": [ "p_val_alias": {
{ "anyOf": [
"type": "array", {
"items": { "type": "array",
"type": "string", "items": {
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
}, },
}, {"type": "null"},
{"type": "null"}, ],
], "title": "P Val Alias",
"title": "P Val Alias", }
} },
}, "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -304,25 +314,28 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_val_alias": { "properties": {
"anyOf": [ "p_val_alias": {
{ "anyOf": [
"type": "array", {
"items": { "type": "array",
"type": "string", "items": {
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
}, },
}, {"type": "null"},
{"type": "null"}, ],
], "title": "P Val Alias",
"title": "P Val Alias", }
} },
}, "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(

101
tests/test_request_params/test_file/test_required.py

@ -3,6 +3,7 @@ from typing import Annotated
import pytest import pytest
from fastapi import FastAPI, File, UploadFile from fastapi import FastAPI, File, UploadFile
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import Is, snapshot
from .utils import get_body_model_name from .utils import get_body_model_name
@ -33,18 +34,21 @@ def test_required_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p": { "properties": {
"title": "P", "p": {
"type": "string", "title": "P",
"contentMediaType": "application/octet-stream", "format": "binary",
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
}, "required": ["p"],
"required": ["p"], "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -111,18 +115,21 @@ def test_required_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_alias": { "properties": {
"title": "P Alias", "p_alias": {
"type": "string", "title": "P Alias",
"contentMediaType": "application/octet-stream", "format": "binary",
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
}, "required": ["p_alias"],
"required": ["p_alias"], "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -219,18 +226,21 @@ def test_required_validation_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_val_alias": { "properties": {
"title": "P Val Alias", "p_val_alias": {
"type": "string", "title": "P Val Alias",
"contentMediaType": "application/octet-stream", "format": "binary",
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
}, "required": ["p_val_alias"],
"required": ["p_val_alias"], "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -332,18 +342,21 @@ def test_required_alias_and_validation_alias_schema(path: str):
openapi = app.openapi() openapi = app.openapi()
body_model_name = get_body_model_name(openapi, path) body_model_name = get_body_model_name(openapi, path)
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == snapshot(
"properties": { {
"p_val_alias": { "properties": {
"title": "P Val Alias", "p_val_alias": {
"type": "string", "title": "P Val Alias",
"contentMediaType": "application/octet-stream", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream",
},
}, },
}, "required": ["p_val_alias"],
"required": ["p_val_alias"], "title": Is(body_model_name),
"title": body_model_name, "type": "object",
"type": "object", }
} )
@pytest.mark.parametrize( @pytest.mark.parametrize(

2
tests/test_tutorial/test_request_files/test_tutorial001.py

@ -162,6 +162,7 @@ def test_openapi_schema(client: TestClient):
"properties": { "properties": {
"file": { "file": {
"title": "File", "title": "File",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
"type": "string", "type": "string",
} }
@ -174,6 +175,7 @@ def test_openapi_schema(client: TestClient):
"properties": { "properties": {
"file": { "file": {
"title": "File", "title": "File",
"format": "binary",
"type": "string", "type": "string",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
} }

2
tests/test_tutorial/test_request_files/test_tutorial001_02.py

@ -136,6 +136,7 @@ def test_openapi_schema(client: TestClient):
"anyOf": [ "anyOf": [
{ {
"type": "string", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
}, },
{"type": "null"}, {"type": "null"},
@ -152,6 +153,7 @@ def test_openapi_schema(client: TestClient):
"anyOf": [ "anyOf": [
{ {
"type": "string", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
}, },
{"type": "null"}, {"type": "null"},

2
tests/test_tutorial/test_request_files/test_tutorial001_03.py

@ -121,6 +121,7 @@ def test_openapi_schema(client: TestClient):
"properties": { "properties": {
"file": { "file": {
"title": "File", "title": "File",
"format": "binary",
"type": "string", "type": "string",
"description": "A file read as bytes", "description": "A file read as bytes",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
@ -134,6 +135,7 @@ def test_openapi_schema(client: TestClient):
"properties": { "properties": {
"file": { "file": {
"title": "File", "title": "File",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
"type": "string", "type": "string",
"description": "A file read as UploadFile", "description": "A file read as UploadFile",

2
tests/test_tutorial/test_request_files/test_tutorial002.py

@ -197,6 +197,7 @@ def test_openapi_schema(client: TestClient):
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
}, },
} }
@ -212,6 +213,7 @@ def test_openapi_schema(client: TestClient):
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
}, },
} }

2
tests/test_tutorial/test_request_files/test_tutorial003.py

@ -167,6 +167,7 @@ def test_openapi_schema(client: TestClient):
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
}, },
"description": "Multiple files as bytes", "description": "Multiple files as bytes",
@ -183,6 +184,7 @@ def test_openapi_schema(client: TestClient):
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
}, },
"description": "Multiple files as UploadFile", "description": "Multiple files as UploadFile",

2
tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py

@ -197,11 +197,13 @@ def test_openapi_schema(client: TestClient):
"properties": { "properties": {
"file": { "file": {
"title": "File", "title": "File",
"format": "binary",
"type": "string", "type": "string",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
}, },
"fileb": { "fileb": {
"title": "Fileb", "title": "Fileb",
"format": "binary",
"contentMediaType": "application/octet-stream", "contentMediaType": "application/octet-stream",
"type": "string", "type": "string",
}, },

Loading…
Cancel
Save