mirror of https://github.com/tiangolo/fastapi.git
Remove tests for `File` fields declared with models
This commit is contained in:
parent
5a3cd6da2d
commit
d01931c02b
|
|
@ -1,11 +1,10 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from dirty_equals import IsDict, IsOneOf, IsPartialDict
|
from dirty_equals import IsDict
|
||||||
from fastapi import FastAPI, File, Form, UploadFile
|
from fastapi import FastAPI, File, UploadFile
|
||||||
from fastapi._compat import PYDANTIC_V2
|
from fastapi._compat import PYDANTIC_V2
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
from tests.utils import needs_pydanticv2
|
from tests.utils import needs_pydanticv2
|
||||||
|
|
@ -28,45 +27,11 @@ async def read_list_uploadfile(p: Annotated[List[UploadFile], File()]):
|
||||||
return {"file_size": [file.size for file in p]}
|
return {"file_size": [file.size for file in p]}
|
||||||
|
|
||||||
|
|
||||||
class FormModelListBytes(BaseModel):
|
|
||||||
p: List[bytes] = File()
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-list-bytes", operation_id="model_list_bytes")
|
|
||||||
async def read_model_list_bytes(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelListBytes,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [len(file) for file in p.p]}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelListUploadFile(BaseModel):
|
|
||||||
p: List[UploadFile] = File()
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-list-uploadfile", operation_id="model_list_uploadfile")
|
|
||||||
async def read_model_list_uploadfile(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelListUploadFile,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [file.size for file in p.p]}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes",
|
"/list-bytes",
|
||||||
"/model-list-bytes",
|
|
||||||
"/list-uploadfile",
|
"/list-uploadfile",
|
||||||
"/model-list-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_schema(path: str):
|
def test_list_schema(path: str):
|
||||||
|
|
@ -107,9 +72,7 @@ def test_list_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes",
|
"/list-bytes",
|
||||||
"/model-list-bytes",
|
|
||||||
"/list-uploadfile",
|
"/list-uploadfile",
|
||||||
"/model-list-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_missing(path: str):
|
def test_list_missing(path: str):
|
||||||
|
|
@ -123,7 +86,7 @@ def test_list_missing(path: str):
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p"],
|
"loc": ["body", "p"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -145,9 +108,7 @@ def test_list_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes",
|
"/list-bytes",
|
||||||
"/model-list-bytes",
|
|
||||||
"/list-uploadfile",
|
"/list-uploadfile",
|
||||||
"/model-list-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list(path: str):
|
def test_list(path: str):
|
||||||
|
|
@ -173,38 +134,6 @@ async def read_list_uploadfile_alias(
|
||||||
return {"file_size": [file.size for file in p]}
|
return {"file_size": [file.size for file in p]}
|
||||||
|
|
||||||
|
|
||||||
class FormModelListBytesAlias(BaseModel):
|
|
||||||
p: List[bytes] = File(alias="p_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-list-bytes-alias", operation_id="model_list_bytes_alias")
|
|
||||||
async def read_model_list_bytes_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelListBytesAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [len(file) for file in p.p]}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelListUploadFileAlias(BaseModel):
|
|
||||||
p: List[UploadFile] = File(alias="p_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-list-uploadfile-alias", operation_id="model_list_uploadfile_alias")
|
|
||||||
async def read_model_list_uploadfile_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelListUploadFileAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [file.size for file in p.p]}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
@pytest.mark.xfail(
|
||||||
raises=AssertionError,
|
raises=AssertionError,
|
||||||
condition=PYDANTIC_V2,
|
condition=PYDANTIC_V2,
|
||||||
|
|
@ -215,9 +144,7 @@ async def read_model_list_uploadfile_alias(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-alias",
|
"/list-bytes-alias",
|
||||||
"/model-list-bytes-alias",
|
|
||||||
"/list-uploadfile-alias",
|
"/list-uploadfile-alias",
|
||||||
"/model-list-uploadfile-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_schema(path: str):
|
def test_list_alias_schema(path: str):
|
||||||
|
|
@ -258,25 +185,7 @@ def test_list_alias_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-alias",
|
"/list-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-list-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/list-uploadfile-alias",
|
"/list-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-list-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_missing(path: str):
|
def test_list_alias_missing(path: str):
|
||||||
|
|
@ -288,9 +197,9 @@ def test_list_alias_missing(path: str):
|
||||||
"detail": [
|
"detail": [
|
||||||
{
|
{
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p_alias"], # model-list-*-alias fail here
|
"loc": ["body", "p_alias"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -312,31 +221,13 @@ def test_list_alias_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-alias",
|
"/list-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-list-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/list-uploadfile-alias",
|
"/list-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-list-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_by_name(path: str):
|
def test_list_alias_by_name(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
|
response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
|
||||||
assert response.status_code == 422 # model-list-uploadfile-alias fail here
|
assert response.status_code == 422
|
||||||
assert response.json() == IsDict(
|
assert response.json() == IsDict(
|
||||||
{
|
{
|
||||||
"detail": [
|
"detail": [
|
||||||
|
|
@ -344,10 +235,7 @@ def test_list_alias_by_name(path: str):
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p_alias"],
|
"loc": ["body", "p_alias"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf( # model-list-bytes-alias fail here
|
"input": None,
|
||||||
None,
|
|
||||||
{"p": [IsPartialDict({"size": 5}), IsPartialDict({"size": 5})]},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -369,33 +257,13 @@ def test_list_alias_by_name(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-alias",
|
"/list-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-list-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/list-uploadfile-alias",
|
"/list-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-list-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_by_alias(path: str):
|
def test_list_alias_by_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")])
|
response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")])
|
||||||
assert response.status_code == 200, ( # model-list-*-alias fail here
|
assert response.status_code == 200, response.text
|
||||||
response.text
|
|
||||||
)
|
|
||||||
assert response.json() == {"file_size": [5, 5]}
|
assert response.json() == {"file_size": [5, 5]}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -420,52 +288,12 @@ def read_list_uploadfile_validation_alias(
|
||||||
return {"file_size": [file.size for file in p]}
|
return {"file_size": [file.size for file in p]}
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredBytesValidationAlias(BaseModel):
|
|
||||||
p: List[bytes] = File(validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-list-bytes-validation-alias",
|
|
||||||
operation_id="model_list_bytes_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_list_bytes_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredBytesValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [len(file) for file in p.p]} # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredUploadFileValidationAlias(BaseModel):
|
|
||||||
p: List[UploadFile] = File(validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-list-uploadfile-validation-alias",
|
|
||||||
operation_id="model_list_uploadfile_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_list_uploadfile_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredUploadFileValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [file.size for file in p.p]} # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-validation-alias",
|
"/list-bytes-validation-alias",
|
||||||
"/model-list-uploadfile-validation-alias",
|
|
||||||
"/list-uploadfile-validation-alias",
|
"/list-uploadfile-validation-alias",
|
||||||
"/model-list-bytes-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_validation_alias_schema(path: str):
|
def test_list_validation_alias_schema(path: str):
|
||||||
|
|
@ -510,12 +338,10 @@ def test_list_validation_alias_schema(path: str):
|
||||||
"/list-bytes-validation-alias",
|
"/list-bytes-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-list-bytes-validation-alias",
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/list-uploadfile-validation-alias",
|
"/list-uploadfile-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-list-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_validation_alias_missing(path: str):
|
def test_list_validation_alias_missing(path: str):
|
||||||
|
|
@ -531,7 +357,7 @@ def test_list_validation_alias_missing(path: str):
|
||||||
"p_val_alias",
|
"p_val_alias",
|
||||||
],
|
],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -545,15 +371,10 @@ def test_list_validation_alias_missing(path: str):
|
||||||
"/list-bytes-validation-alias",
|
"/list-bytes-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-list-bytes-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/list-uploadfile-validation-alias",
|
"/list-uploadfile-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-list-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_validation_alias_by_name(path: str):
|
def test_list_validation_alias_by_name(path: str):
|
||||||
|
|
@ -563,16 +384,13 @@ def test_list_validation_alias_by_name(path: str):
|
||||||
response.text
|
response.text
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.json() == {
|
assert response.json() == { # pragma: no cover
|
||||||
"detail": [
|
"detail": [
|
||||||
{
|
{
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p_val_alias"],
|
"loc": ["body", "p_val_alias"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf( # /model-list-bytes-validation-alias fails here
|
"input": None,
|
||||||
None,
|
|
||||||
{"p": [IsPartialDict({"size": 5}), IsPartialDict({"size": 5})]},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -584,9 +402,7 @@ def test_list_validation_alias_by_name(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-validation-alias",
|
"/list-bytes-validation-alias",
|
||||||
"/model-list-bytes-validation-alias",
|
|
||||||
"/list-uploadfile-validation-alias",
|
"/list-uploadfile-validation-alias",
|
||||||
"/model-list-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_validation_alias_by_validation_alias(path: str):
|
def test_list_validation_alias_by_validation_alias(path: str):
|
||||||
|
|
@ -594,7 +410,7 @@ def test_list_validation_alias_by_validation_alias(path: str):
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
|
path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, response.text # all 4 fail here
|
assert response.status_code == 200, response.text # all 2 fail here
|
||||||
assert response.json() == {"file_size": [5, 5]} # pragma: no cover
|
assert response.json() == {"file_size": [5, 5]} # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -624,52 +440,12 @@ def read_list_uploadfile_alias_and_validation_alias(
|
||||||
return {"file_size": [file.size for file in p]}
|
return {"file_size": [file.size for file in p]}
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredBytesAliasAndValidationAlias(BaseModel):
|
|
||||||
p: List[bytes] = File(alias="p_alias", validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-list-bytes-alias-and-validation-alias",
|
|
||||||
operation_id="model_list_bytes_alias_and_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_list_bytes_alias_and_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredBytesAliasAndValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [len(file) for file in p.p]} # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredUploadFileAliasAndValidationAlias(BaseModel):
|
|
||||||
p: List[UploadFile] = File(alias="p_alias", validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-list-uploadfile-alias-and-validation-alias",
|
|
||||||
operation_id="model_list_uploadfile_alias_and_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_list_uploadfile_alias_and_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredUploadFileAliasAndValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [file.size for file in p.p]} # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-alias-and-validation-alias",
|
"/list-bytes-alias-and-validation-alias",
|
||||||
"/model-list-bytes-alias-and-validation-alias",
|
|
||||||
"/list-uploadfile-alias-and-validation-alias",
|
"/list-uploadfile-alias-and-validation-alias",
|
||||||
"/model-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_and_validation_alias_schema(path: str):
|
def test_list_alias_and_validation_alias_schema(path: str):
|
||||||
|
|
@ -714,12 +490,10 @@ def test_list_alias_and_validation_alias_schema(path: str):
|
||||||
"/list-bytes-alias-and-validation-alias",
|
"/list-bytes-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-list-bytes-alias-and-validation-alias",
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/list-uploadfile-alias-and-validation-alias",
|
"/list-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_and_validation_alias_missing(path: str):
|
def test_list_alias_and_validation_alias_missing(path: str):
|
||||||
|
|
@ -735,7 +509,7 @@ def test_list_alias_and_validation_alias_missing(path: str):
|
||||||
"p_val_alias", # /list-*-alias-and-validation-alias fail here
|
"p_val_alias", # /list-*-alias-and-validation-alias fail here
|
||||||
],
|
],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -747,9 +521,7 @@ def test_list_alias_and_validation_alias_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-alias-and-validation-alias",
|
"/list-bytes-alias-and-validation-alias",
|
||||||
"/model-list-bytes-alias-and-validation-alias",
|
|
||||||
"/list-uploadfile-alias-and-validation-alias",
|
"/list-uploadfile-alias-and-validation-alias",
|
||||||
"/model-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_and_validation_alias_by_name(path: str):
|
def test_list_alias_and_validation_alias_by_name(path: str):
|
||||||
|
|
@ -766,11 +538,7 @@ def test_list_alias_and_validation_alias_by_name(path: str):
|
||||||
"p_val_alias", # /list-*-alias-and-validation-alias fail here
|
"p_val_alias", # /list-*-alias-and-validation-alias fail here
|
||||||
],
|
],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(
|
"input": None,
|
||||||
None,
|
|
||||||
# /model-list-*-alias-and-validation-alias fail here
|
|
||||||
{"p": [IsPartialDict({"size": 5}), IsPartialDict({"size": 5})]},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -784,15 +552,10 @@ def test_list_alias_and_validation_alias_by_name(path: str):
|
||||||
"/list-bytes-alias-and-validation-alias",
|
"/list-bytes-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-list-bytes-alias-and-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/list-uploadfile-alias-and-validation-alias",
|
"/list-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_and_validation_alias_by_alias(path: str):
|
def test_list_alias_and_validation_alias_by_alias(path: str):
|
||||||
|
|
@ -802,22 +565,13 @@ def test_list_alias_and_validation_alias_by_alias(path: str):
|
||||||
response.text # /list-*-alias-and-validation-alias fails here
|
response.text # /list-*-alias-and-validation-alias fails here
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.json() == {
|
assert response.json() == { # pragma: no cover
|
||||||
"detail": [
|
"detail": [
|
||||||
{
|
{
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p_val_alias"],
|
"loc": ["body", "p_val_alias"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(
|
"input": None,
|
||||||
None,
|
|
||||||
# /model-list-bytes-alias-and-validation-alias fails here
|
|
||||||
{
|
|
||||||
"p_alias": [
|
|
||||||
IsPartialDict({"size": 5}),
|
|
||||||
IsPartialDict({"size": 5}),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -829,9 +583,7 @@ def test_list_alias_and_validation_alias_by_alias(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/list-bytes-alias-and-validation-alias",
|
"/list-bytes-alias-and-validation-alias",
|
||||||
"/model-list-bytes-alias-and-validation-alias",
|
|
||||||
"/list-uploadfile-alias-and-validation-alias",
|
"/list-uploadfile-alias-and-validation-alias",
|
||||||
"/model-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_list_alias_and_validation_alias_by_validation_alias(path: str):
|
def test_list_alias_and_validation_alias_by_validation_alias(path: str):
|
||||||
|
|
@ -839,7 +591,7 @@ def test_list_alias_and_validation_alias_by_validation_alias(path: str):
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
|
path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, ( # all 4 fail here
|
assert response.status_code == 200, ( # all 2 fail here
|
||||||
response.text
|
response.text
|
||||||
)
|
)
|
||||||
assert response.json() == {"file_size": [5, 5]} # pragma: no cover
|
assert response.json() == {"file_size": [5, 5]} # pragma: no cover
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from dirty_equals import IsDict
|
from dirty_equals import IsDict
|
||||||
from fastapi import FastAPI, File, Form, UploadFile
|
from fastapi import FastAPI, File, UploadFile
|
||||||
from fastapi._compat import PYDANTIC_V2
|
from fastapi._compat import PYDANTIC_V2
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
from tests.utils import needs_pydanticv2
|
from tests.utils import needs_pydanticv2
|
||||||
|
|
@ -28,45 +27,11 @@ async def read_optional_uploadfile(p: Annotated[Optional[UploadFile], File()] =
|
||||||
return {"file_size": p.size if p else None}
|
return {"file_size": p.size if p else None}
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalBytes(BaseModel):
|
|
||||||
p: Optional[bytes] = File(default=None)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-bytes", operation_id="model_optional_bytes")
|
|
||||||
async def read_model_optional_bytes(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalBytes,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": len(p.p) if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalUploadFile(BaseModel):
|
|
||||||
p: Optional[UploadFile] = File(default=None)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-uploadfile", operation_id="model_optional_uploadfile")
|
|
||||||
async def read_model_optional_uploadfile(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalUploadFile,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": p.p.size if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes",
|
"/optional-bytes",
|
||||||
"/model-optional-bytes",
|
|
||||||
"/optional-uploadfile",
|
"/optional-uploadfile",
|
||||||
"/model-optional-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_schema(path: str):
|
def test_optional_schema(path: str):
|
||||||
|
|
@ -100,9 +65,7 @@ def test_optional_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes",
|
"/optional-bytes",
|
||||||
"/model-optional-bytes",
|
|
||||||
"/optional-uploadfile",
|
"/optional-uploadfile",
|
||||||
"/model-optional-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_missing(path: str):
|
def test_optional_missing(path: str):
|
||||||
|
|
@ -116,9 +79,7 @@ def test_optional_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes",
|
"/optional-bytes",
|
||||||
"/model-optional-bytes",
|
|
||||||
"/optional-uploadfile",
|
"/optional-uploadfile",
|
||||||
"/model-optional-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional(path: str):
|
def test_optional(path: str):
|
||||||
|
|
@ -146,40 +107,6 @@ async def read_optional_uploadfile_alias(
|
||||||
return {"file_size": p.size if p else None}
|
return {"file_size": p.size if p else None}
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalBytesAlias(BaseModel):
|
|
||||||
p: Optional[bytes] = File(default=None, alias="p_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-bytes-alias", operation_id="model_optional_bytes_alias")
|
|
||||||
async def read_model_optional_bytes_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalBytesAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": len(p.p) if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalUploadFileAlias(BaseModel):
|
|
||||||
p: Optional[UploadFile] = File(default=None, alias="p_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-optional-uploadfile-alias", operation_id="model_optional_uploadfile_alias"
|
|
||||||
)
|
|
||||||
async def read_model_optional_uploadfile_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalUploadFileAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": p.p.size if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
@pytest.mark.xfail(
|
||||||
raises=AssertionError,
|
raises=AssertionError,
|
||||||
condition=PYDANTIC_V2,
|
condition=PYDANTIC_V2,
|
||||||
|
|
@ -190,9 +117,7 @@ async def read_model_optional_uploadfile_alias(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-alias",
|
"/optional-bytes-alias",
|
||||||
"/model-optional-bytes-alias",
|
|
||||||
"/optional-uploadfile-alias",
|
"/optional-uploadfile-alias",
|
||||||
"/model-optional-uploadfile-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_schema(path: str):
|
def test_optional_alias_schema(path: str):
|
||||||
|
|
@ -226,9 +151,7 @@ def test_optional_alias_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-alias",
|
"/optional-bytes-alias",
|
||||||
"/model-optional-bytes-alias",
|
|
||||||
"/optional-uploadfile-alias",
|
"/optional-uploadfile-alias",
|
||||||
"/model-optional-uploadfile-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_missing(path: str):
|
def test_optional_alias_missing(path: str):
|
||||||
|
|
@ -242,64 +165,28 @@ def test_optional_alias_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-alias",
|
"/optional-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-optional-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/optional-uploadfile-alias",
|
"/optional-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-optional-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_by_name(path: str):
|
def test_optional_alias_by_name(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p", b"hello")])
|
response = client.post(path, files=[("p", b"hello")])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"file_size": None} # model-optional-*-alias fail here
|
assert response.json() == {"file_size": None}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-alias",
|
"/optional-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-optional-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/optional-uploadfile-alias",
|
"/optional-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-optional-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_by_alias(path: str):
|
def test_optional_alias_by_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_alias", b"hello")])
|
response = client.post(path, files=[("p_alias", b"hello")])
|
||||||
assert response.status_code == 200, response.text
|
assert response.status_code == 200, response.text
|
||||||
assert response.json() == {"file_size": 5} # model-optional-*-alias fail here
|
assert response.json() == {"file_size": 5}
|
||||||
|
|
||||||
|
|
||||||
# =====================================================================================
|
# =====================================================================================
|
||||||
|
|
@ -325,52 +212,12 @@ def read_optional_uploadfile_validation_alias(
|
||||||
return {"file_size": p.size if p else None}
|
return {"file_size": p.size if p else None}
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalBytesValidationAlias(BaseModel):
|
|
||||||
p: Optional[bytes] = File(default=None, validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-optional-bytes-validation-alias",
|
|
||||||
operation_id="model_optional_bytes_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_optional_bytes_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalBytesValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": len(p.p) if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalUploadFileValidationAlias(BaseModel):
|
|
||||||
p: Optional[UploadFile] = File(default=None, validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-optional-uploadfile-validation-alias",
|
|
||||||
operation_id="model_optional_uploadfile_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_optional_uploadfile_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalUploadFileValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": p.p.size if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-validation-alias",
|
"/optional-bytes-validation-alias",
|
||||||
"/model-optional-uploadfile-validation-alias",
|
|
||||||
"/optional-uploadfile-validation-alias",
|
"/optional-uploadfile-validation-alias",
|
||||||
"/model-optional-bytes-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_validation_alias_schema(path: str):
|
def test_optional_validation_alias_schema(path: str):
|
||||||
|
|
@ -405,9 +252,7 @@ def test_optional_validation_alias_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-validation-alias",
|
"/optional-bytes-validation-alias",
|
||||||
"/model-optional-bytes-validation-alias",
|
|
||||||
"/optional-uploadfile-validation-alias",
|
"/optional-uploadfile-validation-alias",
|
||||||
"/model-optional-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_validation_alias_missing(path: str):
|
def test_optional_validation_alias_missing(path: str):
|
||||||
|
|
@ -425,12 +270,10 @@ def test_optional_validation_alias_missing(path: str):
|
||||||
"/optional-bytes-validation-alias",
|
"/optional-bytes-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-optional-bytes-validation-alias",
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/optional-uploadfile-validation-alias",
|
"/optional-uploadfile-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-optional-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_validation_alias_by_name(path: str):
|
def test_optional_validation_alias_by_name(path: str):
|
||||||
|
|
@ -450,23 +293,16 @@ def test_optional_validation_alias_by_name(path: str):
|
||||||
"/optional-bytes-validation-alias",
|
"/optional-bytes-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-optional-bytes-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/optional-uploadfile-validation-alias",
|
"/optional-uploadfile-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-optional-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_validation_alias_by_validation_alias(path: str):
|
def test_optional_validation_alias_by_validation_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_val_alias", b"hello")])
|
response = client.post(path, files=[("p_val_alias", b"hello")])
|
||||||
assert response.status_code == 200, (
|
assert response.status_code == 200, response.text
|
||||||
response.text # /model-optional-bytes-validation-alias fail here
|
|
||||||
)
|
|
||||||
assert response.json() == {"file_size": 5} # /optional-*-validation-alias fail here
|
assert response.json() == {"file_size": 5} # /optional-*-validation-alias fail here
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -498,56 +334,12 @@ def read_optional_uploadfile_alias_and_validation_alias(
|
||||||
return {"file_size": p.size if p else None}
|
return {"file_size": p.size if p else None}
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalBytesAliasAndValidationAlias(BaseModel):
|
|
||||||
p: Optional[bytes] = File(
|
|
||||||
default=None, alias="p_alias", validation_alias="p_val_alias"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-optional-bytes-alias-and-validation-alias",
|
|
||||||
operation_id="model_optional_bytes_alias_and_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_optional_bytes_alias_and_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalBytesAliasAndValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": len(p.p) if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalUploadFileAliasAndValidationAlias(BaseModel):
|
|
||||||
p: Optional[UploadFile] = File(
|
|
||||||
default=None, alias="p_alias", validation_alias="p_val_alias"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-optional-uploadfile-alias-and-validation-alias",
|
|
||||||
operation_id="model_optional_uploadfile_alias_and_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_optional_uploadfile_alias_and_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalUploadFileAliasAndValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": p.p.size if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-alias-and-validation-alias",
|
"/optional-bytes-alias-and-validation-alias",
|
||||||
"/model-optional-bytes-alias-and-validation-alias",
|
|
||||||
"/optional-uploadfile-alias-and-validation-alias",
|
"/optional-uploadfile-alias-and-validation-alias",
|
||||||
"/model-optional-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_and_validation_alias_schema(path: str):
|
def test_optional_alias_and_validation_alias_schema(path: str):
|
||||||
|
|
@ -582,9 +374,7 @@ def test_optional_alias_and_validation_alias_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-alias-and-validation-alias",
|
"/optional-bytes-alias-and-validation-alias",
|
||||||
"/model-optional-bytes-alias-and-validation-alias",
|
|
||||||
"/optional-uploadfile-alias-and-validation-alias",
|
"/optional-uploadfile-alias-and-validation-alias",
|
||||||
"/model-optional-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_and_validation_alias_missing(path: str):
|
def test_optional_alias_and_validation_alias_missing(path: str):
|
||||||
|
|
@ -599,9 +389,7 @@ def test_optional_alias_and_validation_alias_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-bytes-alias-and-validation-alias",
|
"/optional-bytes-alias-and-validation-alias",
|
||||||
"/model-optional-bytes-alias-and-validation-alias",
|
|
||||||
"/optional-uploadfile-alias-and-validation-alias",
|
"/optional-uploadfile-alias-and-validation-alias",
|
||||||
"/model-optional-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_and_validation_alias_by_name(path: str):
|
def test_optional_alias_and_validation_alias_by_name(path: str):
|
||||||
|
|
@ -619,21 +407,17 @@ def test_optional_alias_and_validation_alias_by_name(path: str):
|
||||||
"/optional-bytes-alias-and-validation-alias",
|
"/optional-bytes-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-optional-bytes-alias-and-validation-alias",
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/optional-uploadfile-alias-and-validation-alias",
|
"/optional-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-optional-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_and_validation_alias_by_alias(path: str):
|
def test_optional_alias_and_validation_alias_by_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_alias", b"hello")])
|
response = client.post(path, files=[("p_alias", b"hello")])
|
||||||
assert response.status_code == 200, response.text
|
assert response.status_code == 200, response.text
|
||||||
assert response.json() == {
|
assert response.json() == {"file_size": None}
|
||||||
"file_size": None # model-optional-*-alias-and-validation-alias fail here
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
|
|
@ -644,23 +428,16 @@ def test_optional_alias_and_validation_alias_by_alias(path: str):
|
||||||
"/optional-bytes-alias-and-validation-alias",
|
"/optional-bytes-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-optional-bytes-alias-and-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/optional-uploadfile-alias-and-validation-alias",
|
"/optional-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-optional-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_alias_and_validation_alias_by_validation_alias(path: str):
|
def test_optional_alias_and_validation_alias_by_validation_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_val_alias", b"hello")])
|
response = client.post(path, files=[("p_val_alias", b"hello")])
|
||||||
assert response.status_code == 200, (
|
assert response.status_code == 200, response.text
|
||||||
response.text # model-optional-bytes-alias-and-validation-alias fails here
|
|
||||||
)
|
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"file_size": 5
|
"file_size": 5
|
||||||
} # /optional-*-alias-and-validation-alias fail here
|
} # /optional-*-alias-and-validation-alias fail here
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ from typing import List, Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from dirty_equals import IsDict
|
from dirty_equals import IsDict
|
||||||
from fastapi import FastAPI, File, Form, UploadFile
|
from fastapi import FastAPI, File, UploadFile
|
||||||
from fastapi._compat import PYDANTIC_V2
|
from fastapi._compat import PYDANTIC_V2
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
from tests.utils import needs_pydanticv2
|
from tests.utils import needs_pydanticv2
|
||||||
|
|
@ -30,45 +29,11 @@ async def read_optional_list_uploadfile(
|
||||||
return {"file_size": [file.size for file in p] if p else None}
|
return {"file_size": [file.size for file in p] if p else None}
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalListBytes(BaseModel):
|
|
||||||
p: Optional[List[bytes]] = File(default=None)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-list-bytes")
|
|
||||||
async def read_model_optional_list_bytes(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalListBytes,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [len(file) for file in p.p] if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalListUploadFile(BaseModel):
|
|
||||||
p: Optional[List[UploadFile]] = File(default=None)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-list-uploadfile")
|
|
||||||
async def read_model_optional_list_uploadfile(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalListUploadFile,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [file.size for file in p.p] if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes",
|
"/optional-list-bytes",
|
||||||
"/model-optional-list-bytes",
|
|
||||||
"/optional-list-uploadfile",
|
"/optional-list-uploadfile",
|
||||||
"/model-optional-list-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_schema(path: str):
|
def test_optional_list_schema(path: str):
|
||||||
|
|
@ -109,9 +74,7 @@ def test_optional_list_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes",
|
"/optional-list-bytes",
|
||||||
"/model-optional-list-bytes",
|
|
||||||
"/optional-list-uploadfile",
|
"/optional-list-uploadfile",
|
||||||
"/model-optional-list-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_missing(path: str):
|
def test_optional_list_missing(path: str):
|
||||||
|
|
@ -133,17 +96,7 @@ def test_optional_list_missing(path: str):
|
||||||
strict=False,
|
strict=False,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-optional-list-bytes",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=(TypeError, AssertionError),
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 due to #14297",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/optional-list-uploadfile",
|
"/optional-list-uploadfile",
|
||||||
"/model-optional-list-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list(path: str):
|
def test_optional_list(path: str):
|
||||||
|
|
@ -171,38 +124,6 @@ async def read_optional_list_uploadfile_alias(
|
||||||
return {"file_size": [file.size for file in p] if p else None}
|
return {"file_size": [file.size for file in p] if p else None}
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalListBytesAlias(BaseModel):
|
|
||||||
p: Optional[List[bytes]] = File(default=None, alias="p_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-list-bytes-alias")
|
|
||||||
async def read_model_optional_list_bytes_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalListBytesAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [len(file) for file in p.p] if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalListUploadFileAlias(BaseModel):
|
|
||||||
p: Optional[List[UploadFile]] = File(default=None, alias="p_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-list-uploadfile-alias")
|
|
||||||
async def read_model_optional_list_uploadfile_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalListUploadFileAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [file.size for file in p.p] if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
@pytest.mark.xfail(
|
||||||
raises=AssertionError,
|
raises=AssertionError,
|
||||||
condition=PYDANTIC_V2,
|
condition=PYDANTIC_V2,
|
||||||
|
|
@ -213,9 +134,7 @@ async def read_model_optional_list_uploadfile_alias(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-alias",
|
"/optional-list-bytes-alias",
|
||||||
"/model-optional-list-bytes-alias",
|
|
||||||
"/optional-list-uploadfile-alias",
|
"/optional-list-uploadfile-alias",
|
||||||
"/model-optional-list-uploadfile-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_schema(path: str):
|
def test_optional_list_alias_schema(path: str):
|
||||||
|
|
@ -256,9 +175,7 @@ def test_optional_list_alias_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-alias",
|
"/optional-list-bytes-alias",
|
||||||
"/model-optional-list-bytes-alias",
|
|
||||||
"/optional-list-uploadfile-alias",
|
"/optional-list-uploadfile-alias",
|
||||||
"/model-optional-list-uploadfile-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_missing(path: str):
|
def test_optional_list_alias_missing(path: str):
|
||||||
|
|
@ -272,33 +189,13 @@ def test_optional_list_alias_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-alias",
|
"/optional-list-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-optional-list-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/optional-list-uploadfile-alias",
|
"/optional-list-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-optional-list-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_by_name(path: str):
|
def test_optional_list_alias_by_name(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
|
response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
|
||||||
assert response.status_code == 200, (
|
assert response.status_code == 200, response.text
|
||||||
response.text # model-optional-list-*-alias fail here
|
|
||||||
)
|
|
||||||
assert response.json() == {"file_size": None}
|
assert response.json() == {"file_size": None}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -314,34 +211,14 @@ def test_optional_list_alias_by_name(path: str):
|
||||||
reason="Fails only with PDv2 model due to #14297",
|
reason="Fails only with PDv2 model due to #14297",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-optional-list-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=(TypeError, AssertionError),
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model due to #14297",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/optional-list-uploadfile-alias",
|
"/optional-list-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-optional-list-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_by_alias(path: str):
|
def test_optional_list_alias_by_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")])
|
response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")])
|
||||||
assert response.status_code == 200, response.text
|
assert response.status_code == 200, response.text
|
||||||
assert response.json() == {
|
assert response.json() == {"file_size": [5, 5]}
|
||||||
"file_size": [5, 5] # /model-optional-list-uploadfile-alias fails here
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# =====================================================================================
|
# =====================================================================================
|
||||||
|
|
@ -364,46 +241,12 @@ def read_optional_list_uploadfile_validation_alias(
|
||||||
return {"file_size": [file.size for file in p] if p else None}
|
return {"file_size": [file.size for file in p] if p else None}
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalListBytesValidationAlias(BaseModel):
|
|
||||||
p: Optional[List[bytes]] = File(default=None, validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-list-bytes-validation-alias")
|
|
||||||
def read_model_optional_list_bytes_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalListBytesValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [len(file) for file in p.p] if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalListUploadFileValidationAlias(BaseModel):
|
|
||||||
p: Optional[List[UploadFile]] = File(default=None, validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-list-uploadfile-validation-alias")
|
|
||||||
def read_model_optional_list_uploadfile_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalListUploadFileValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [file.size for file in p.p] if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-validation-alias",
|
"/optional-list-bytes-validation-alias",
|
||||||
"/model-optional-list-uploadfile-validation-alias",
|
|
||||||
"/optional-list-uploadfile-validation-alias",
|
"/optional-list-uploadfile-validation-alias",
|
||||||
"/model-optional-list-bytes-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_validation_alias_schema(path: str):
|
def test_optional_validation_alias_schema(path: str):
|
||||||
|
|
@ -445,9 +288,7 @@ def test_optional_validation_alias_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-validation-alias",
|
"/optional-list-bytes-validation-alias",
|
||||||
"/model-optional-list-bytes-validation-alias",
|
|
||||||
"/optional-list-uploadfile-validation-alias",
|
"/optional-list-uploadfile-validation-alias",
|
||||||
"/model-optional-list-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_validation_alias_missing(path: str):
|
def test_optional_validation_alias_missing(path: str):
|
||||||
|
|
@ -469,19 +310,10 @@ def test_optional_validation_alias_missing(path: str):
|
||||||
reason="Fails due to #14297",
|
reason="Fails due to #14297",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-optional-list-bytes-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=(TypeError, AssertionError),
|
|
||||||
strict=False,
|
|
||||||
reason="Fails due to #14297",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/optional-list-uploadfile-validation-alias",
|
"/optional-list-uploadfile-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-optional-list-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_validation_alias_by_name(path: str):
|
def test_optional_validation_alias_by_name(path: str):
|
||||||
|
|
@ -499,9 +331,7 @@ def test_optional_validation_alias_by_name(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-validation-alias",
|
"/optional-list-bytes-validation-alias",
|
||||||
"/model-optional-list-bytes-validation-alias",
|
|
||||||
"/optional-list-uploadfile-validation-alias",
|
"/optional-list-uploadfile-validation-alias",
|
||||||
"/model-optional-list-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_validation_alias_by_validation_alias(path: str):
|
def test_optional_validation_alias_by_validation_alias(path: str):
|
||||||
|
|
@ -509,9 +339,7 @@ def test_optional_validation_alias_by_validation_alias(path: str):
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
|
path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, (
|
assert response.status_code == 200, response.text
|
||||||
response.text # /model-optional-list-*-validation-alias fail here
|
|
||||||
)
|
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"file_size": [5, 5] # /optional-list-*-validation-alias fail here
|
"file_size": [5, 5] # /optional-list-*-validation-alias fail here
|
||||||
}
|
}
|
||||||
|
|
@ -540,50 +368,12 @@ def read_optional_list_uploadfile_alias_and_validation_alias(
|
||||||
return {"file_size": [file.size for file in p] if p else None}
|
return {"file_size": [file.size for file in p] if p else None}
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalListBytesAliasAndValidationAlias(BaseModel):
|
|
||||||
p: Optional[List[bytes]] = File(
|
|
||||||
default=None, alias="p_alias", validation_alias="p_val_alias"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-list-bytes-alias-and-validation-alias")
|
|
||||||
def read_model_optional_list_bytes_alias_and_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalListBytesAliasAndValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [len(file) for file in p.p] if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelOptionalListUploadFileAliasAndValidationAlias(BaseModel):
|
|
||||||
p: Optional[List[UploadFile]] = File(
|
|
||||||
default=None, alias="p_alias", validation_alias="p_val_alias"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-optional-list-uploadfile-alias-and-validation-alias")
|
|
||||||
def read_model_optional_list_uploadfile_alias_and_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelOptionalListUploadFileAliasAndValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": [file.size for file in p.p] if p.p else None}
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-alias-and-validation-alias",
|
"/optional-list-bytes-alias-and-validation-alias",
|
||||||
"/model-optional-list-bytes-alias-and-validation-alias",
|
|
||||||
"/optional-list-uploadfile-alias-and-validation-alias",
|
"/optional-list-uploadfile-alias-and-validation-alias",
|
||||||
"/model-optional-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_and_validation_alias_schema(path: str):
|
def test_optional_list_alias_and_validation_alias_schema(path: str):
|
||||||
|
|
@ -625,9 +415,7 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-alias-and-validation-alias",
|
"/optional-list-bytes-alias-and-validation-alias",
|
||||||
"/model-optional-list-bytes-alias-and-validation-alias",
|
|
||||||
"/optional-list-uploadfile-alias-and-validation-alias",
|
"/optional-list-uploadfile-alias-and-validation-alias",
|
||||||
"/model-optional-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_and_validation_alias_missing(path: str):
|
def test_optional_list_alias_and_validation_alias_missing(path: str):
|
||||||
|
|
@ -642,9 +430,7 @@ def test_optional_list_alias_and_validation_alias_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-alias-and-validation-alias",
|
"/optional-list-bytes-alias-and-validation-alias",
|
||||||
"/model-optional-list-bytes-alias-and-validation-alias",
|
|
||||||
"/optional-list-uploadfile-alias-and-validation-alias",
|
"/optional-list-uploadfile-alias-and-validation-alias",
|
||||||
"/model-optional-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_and_validation_alias_by_name(path: str):
|
def test_optional_list_alias_and_validation_alias_by_name(path: str):
|
||||||
|
|
@ -666,19 +452,10 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str):
|
||||||
reason="Fails due to #14297",
|
reason="Fails due to #14297",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-optional-list-bytes-alias-and-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=(TypeError, AssertionError),
|
|
||||||
strict=False,
|
|
||||||
reason="Fails due to #14297",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/optional-list-uploadfile-alias-and-validation-alias",
|
"/optional-list-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-optional-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_and_validation_alias_by_alias(path: str):
|
def test_optional_list_alias_and_validation_alias_by_alias(path: str):
|
||||||
|
|
@ -696,9 +473,7 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/optional-list-bytes-alias-and-validation-alias",
|
"/optional-list-bytes-alias-and-validation-alias",
|
||||||
"/model-optional-list-bytes-alias-and-validation-alias",
|
|
||||||
"/optional-list-uploadfile-alias-and-validation-alias",
|
"/optional-list-uploadfile-alias-and-validation-alias",
|
||||||
"/model-optional-list-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str):
|
def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str):
|
||||||
|
|
@ -706,9 +481,7 @@ def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str)
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
|
path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, (
|
assert response.status_code == 200, response.text
|
||||||
response.text # /model-optional-list-*-alias-and-validation-alias fails here
|
|
||||||
)
|
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"file_size": [5, 5] # /optional-list-*-alias-and-validation-alias fail here
|
"file_size": [5, 5] # /optional-list-*-alias-and-validation-alias fail here
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import pytest
|
import pytest
|
||||||
from dirty_equals import IsDict, IsOneOf, IsPartialDict
|
from dirty_equals import IsDict
|
||||||
from fastapi import FastAPI, File, Form, UploadFile
|
from fastapi import FastAPI, File, UploadFile
|
||||||
from fastapi._compat import PYDANTIC_V2
|
from fastapi._compat import PYDANTIC_V2
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
from tests.utils import needs_pydanticv2
|
from tests.utils import needs_pydanticv2
|
||||||
|
|
@ -26,45 +25,11 @@ async def read_required_uploadfile(p: Annotated[UploadFile, File()]):
|
||||||
return {"file_size": p.size}
|
return {"file_size": p.size}
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredBytes(BaseModel):
|
|
||||||
p: bytes = File()
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-required-bytes", operation_id="model_required_bytes")
|
|
||||||
async def read_model_required_bytes(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredBytes,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": len(p.p)}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredUploadFile(BaseModel):
|
|
||||||
p: UploadFile = File()
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-required-uploadfile", operation_id="model_required_uploadfile")
|
|
||||||
async def read_model_required_uploadfile(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredUploadFile,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": p.p.size}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes",
|
"/required-bytes",
|
||||||
"/model-required-bytes",
|
|
||||||
"/required-uploadfile",
|
"/required-uploadfile",
|
||||||
"/model-required-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_schema(path: str):
|
def test_required_schema(path: str):
|
||||||
|
|
@ -85,9 +50,7 @@ def test_required_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes",
|
"/required-bytes",
|
||||||
"/model-required-bytes",
|
|
||||||
"/required-uploadfile",
|
"/required-uploadfile",
|
||||||
"/model-required-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_missing(path: str):
|
def test_required_missing(path: str):
|
||||||
|
|
@ -101,7 +64,7 @@ def test_required_missing(path: str):
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p"],
|
"loc": ["body", "p"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -123,9 +86,7 @@ def test_required_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes",
|
"/required-bytes",
|
||||||
"/model-required-bytes",
|
|
||||||
"/required-uploadfile",
|
"/required-uploadfile",
|
||||||
"/model-required-uploadfile",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required(path: str):
|
def test_required(path: str):
|
||||||
|
|
@ -151,40 +112,6 @@ async def read_required_uploadfile_alias(
|
||||||
return {"file_size": p.size}
|
return {"file_size": p.size}
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredBytesAlias(BaseModel):
|
|
||||||
p: bytes = File(alias="p_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/model-required-bytes-alias", operation_id="model_required_bytes_alias")
|
|
||||||
async def read_model_required_bytes_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredBytesAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": len(p.p)}
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredUploadFileAlias(BaseModel):
|
|
||||||
p: UploadFile = File(alias="p_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-required-uploadfile-alias", operation_id="model_required_uploadfile_alias"
|
|
||||||
)
|
|
||||||
async def read_model_required_uploadfile_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredUploadFileAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": p.p.size}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
@pytest.mark.xfail(
|
||||||
raises=AssertionError,
|
raises=AssertionError,
|
||||||
condition=PYDANTIC_V2,
|
condition=PYDANTIC_V2,
|
||||||
|
|
@ -195,9 +122,7 @@ async def read_model_required_uploadfile_alias(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes-alias",
|
"/required-bytes-alias",
|
||||||
"/model-required-bytes-alias",
|
|
||||||
"/required-uploadfile-alias",
|
"/required-uploadfile-alias",
|
||||||
"/model-required-uploadfile-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_schema(path: str):
|
def test_required_alias_schema(path: str):
|
||||||
|
|
@ -218,25 +143,7 @@ def test_required_alias_schema(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes-alias",
|
"/required-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-required-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/required-uploadfile-alias",
|
"/required-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-required-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_missing(path: str):
|
def test_required_alias_missing(path: str):
|
||||||
|
|
@ -248,9 +155,9 @@ def test_required_alias_missing(path: str):
|
||||||
"detail": [
|
"detail": [
|
||||||
{
|
{
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p_alias"], # model-required-*-alias fail here
|
"loc": ["body", "p_alias"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -272,31 +179,13 @@ def test_required_alias_missing(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes-alias",
|
"/required-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-required-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/required-uploadfile-alias",
|
"/required-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-required-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
strict=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_by_name(path: str):
|
def test_required_alias_by_name(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p", b"hello")])
|
response = client.post(path, files=[("p", b"hello")])
|
||||||
assert response.status_code == 422 # model-required-upload-alias fail here
|
assert response.status_code == 422
|
||||||
assert response.json() == IsDict(
|
assert response.json() == IsDict(
|
||||||
{
|
{
|
||||||
"detail": [
|
"detail": [
|
||||||
|
|
@ -304,11 +193,7 @@ def test_required_alias_by_name(path: str):
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p_alias"],
|
"loc": ["body", "p_alias"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf( # model-required-bytes-alias fail here
|
"input": None,
|
||||||
None,
|
|
||||||
{"p": IsPartialDict({"size": 5})},
|
|
||||||
{"p": b"hello"}, # ToDo: check this
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -330,33 +215,13 @@ def test_required_alias_by_name(path: str):
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes-alias",
|
"/required-bytes-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-required-bytes-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"/required-uploadfile-alias",
|
"/required-uploadfile-alias",
|
||||||
pytest.param(
|
|
||||||
"/model-required-uploadfile-alias",
|
|
||||||
marks=pytest.mark.xfail(
|
|
||||||
raises=AssertionError,
|
|
||||||
strict=False,
|
|
||||||
condition=PYDANTIC_V2,
|
|
||||||
reason="Fails only with PDv2 model",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_by_alias(path: str):
|
def test_required_alias_by_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_alias", b"hello")])
|
response = client.post(path, files=[("p_alias", b"hello")])
|
||||||
assert response.status_code == 200, ( # model-required-*-alias fail here
|
assert response.status_code == 200, response.text
|
||||||
response.text
|
|
||||||
)
|
|
||||||
assert response.json() == {"file_size": 5}
|
assert response.json() == {"file_size": 5}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -383,52 +248,12 @@ def read_required_uploadfile_validation_alias(
|
||||||
return {"file_size": p.size}
|
return {"file_size": p.size}
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredBytesValidationAlias(BaseModel):
|
|
||||||
p: bytes = File(validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-required-bytes-validation-alias",
|
|
||||||
operation_id="model_required_bytes_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_required_bytes_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredBytesValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": len(p.p)} # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredUploadFileValidationAlias(BaseModel):
|
|
||||||
p: UploadFile = File(validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-required-uploadfile-validation-alias",
|
|
||||||
operation_id="model_required_uploadfile_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_required_uploadfile_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredUploadFileValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": p.p.size}
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes-validation-alias",
|
"/required-bytes-validation-alias",
|
||||||
"/model-required-uploadfile-validation-alias",
|
|
||||||
"/required-uploadfile-validation-alias",
|
"/required-uploadfile-validation-alias",
|
||||||
"/model-required-bytes-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_validation_alias_schema(path: str):
|
def test_required_validation_alias_schema(path: str):
|
||||||
|
|
@ -457,12 +282,10 @@ def test_required_validation_alias_schema(path: str):
|
||||||
"/required-bytes-validation-alias",
|
"/required-bytes-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-bytes-validation-alias",
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/required-uploadfile-validation-alias",
|
"/required-uploadfile-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_validation_alias_missing(path: str):
|
def test_required_validation_alias_missing(path: str):
|
||||||
|
|
@ -478,7 +301,7 @@ def test_required_validation_alias_missing(path: str):
|
||||||
"p_val_alias",
|
"p_val_alias",
|
||||||
],
|
],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -492,15 +315,10 @@ def test_required_validation_alias_missing(path: str):
|
||||||
"/required-bytes-validation-alias",
|
"/required-bytes-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-required-bytes-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/required-uploadfile-validation-alias",
|
"/required-uploadfile-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_validation_alias_by_name(path: str):
|
def test_required_validation_alias_by_name(path: str):
|
||||||
|
|
@ -510,15 +328,13 @@ def test_required_validation_alias_by_name(path: str):
|
||||||
response.text
|
response.text
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.json() == {
|
assert response.json() == { # pragma: no cover
|
||||||
"detail": [
|
"detail": [
|
||||||
{
|
{
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p_val_alias"],
|
"loc": ["body", "p_val_alias"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf( # /model-required-bytes-validation-alias fails here
|
"input": None,
|
||||||
None, {"p": IsPartialDict({"size": 5})}
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -532,24 +348,19 @@ def test_required_validation_alias_by_name(path: str):
|
||||||
"/required-bytes-validation-alias",
|
"/required-bytes-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-required-bytes-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/required-uploadfile-validation-alias",
|
"/required-uploadfile-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-uploadfile-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_validation_alias_by_validation_alias(path: str):
|
def test_required_validation_alias_by_validation_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_val_alias", b"hello")])
|
response = client.post(path, files=[("p_val_alias", b"hello")])
|
||||||
assert response.status_code == 200, ( # all 3 fail here
|
assert response.status_code == 200, ( # all 2 fail here
|
||||||
response.text
|
response.text
|
||||||
)
|
)
|
||||||
assert response.json() == {"file_size": 5}
|
assert response.json() == {"file_size": 5} # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
# =====================================================================================
|
# =====================================================================================
|
||||||
|
|
@ -576,52 +387,12 @@ def read_required_uploadfile_alias_and_validation_alias(
|
||||||
return {"file_size": p.size}
|
return {"file_size": p.size}
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredBytesAliasAndValidationAlias(BaseModel):
|
|
||||||
p: bytes = File(alias="p_alias", validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-required-bytes-alias-and-validation-alias",
|
|
||||||
operation_id="model_required_bytes_alias_and_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_required_bytes_alias_and_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredBytesAliasAndValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": len(p.p)} # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
class FormModelRequiredUploadFileAliasAndValidationAlias(BaseModel):
|
|
||||||
p: UploadFile = File(alias="p_alias", validation_alias="p_val_alias")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/model-required-uploadfile-alias-and-validation-alias",
|
|
||||||
operation_id="model_required_uploadfile_alias_and_validation_alias",
|
|
||||||
)
|
|
||||||
def read_model_required_uploadfile_alias_and_validation_alias(
|
|
||||||
p: Annotated[
|
|
||||||
FormModelRequiredUploadFileAliasAndValidationAlias,
|
|
||||||
Form(
|
|
||||||
media_type="multipart/form-data" # Remove media_type when https://github.com/fastapi/fastapi/pull/14343 is fixed
|
|
||||||
),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
return {"file_size": p.p.size}
|
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/required-bytes-alias-and-validation-alias",
|
"/required-bytes-alias-and-validation-alias",
|
||||||
"/model-required-bytes-alias-and-validation-alias",
|
|
||||||
"/required-uploadfile-alias-and-validation-alias",
|
"/required-uploadfile-alias-and-validation-alias",
|
||||||
"/model-required-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_and_validation_alias_schema(path: str):
|
def test_required_alias_and_validation_alias_schema(path: str):
|
||||||
|
|
@ -650,12 +421,10 @@ def test_required_alias_and_validation_alias_schema(path: str):
|
||||||
"/required-bytes-alias-and-validation-alias",
|
"/required-bytes-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-bytes-alias-and-validation-alias",
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/required-uploadfile-alias-and-validation-alias",
|
"/required-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_and_validation_alias_missing(path: str):
|
def test_required_alias_and_validation_alias_missing(path: str):
|
||||||
|
|
@ -671,7 +440,7 @@ def test_required_alias_and_validation_alias_missing(path: str):
|
||||||
"p_val_alias", # /required-*-alias-and-validation-alias fail here
|
"p_val_alias", # /required-*-alias-and-validation-alias fail here
|
||||||
],
|
],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -685,12 +454,10 @@ def test_required_alias_and_validation_alias_missing(path: str):
|
||||||
"/required-bytes-alias-and-validation-alias",
|
"/required-bytes-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-bytes-alias-and-validation-alias",
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/required-uploadfile-alias-and-validation-alias",
|
"/required-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_and_validation_alias_by_name(path: str):
|
def test_required_alias_and_validation_alias_by_name(path: str):
|
||||||
|
|
@ -707,7 +474,7 @@ def test_required_alias_and_validation_alias_by_name(path: str):
|
||||||
"p_val_alias", # /required-*-alias-and-validation-alias fail here
|
"p_val_alias", # /required-*-alias-and-validation-alias fail here
|
||||||
],
|
],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(None, {"p": IsPartialDict({"size": 5})}),
|
"input": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -721,15 +488,10 @@ def test_required_alias_and_validation_alias_by_name(path: str):
|
||||||
"/required-bytes-alias-and-validation-alias",
|
"/required-bytes-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-required-bytes-alias-and-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/required-uploadfile-alias-and-validation-alias",
|
"/required-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_and_validation_alias_by_alias(path: str):
|
def test_required_alias_and_validation_alias_by_alias(path: str):
|
||||||
|
|
@ -739,17 +501,13 @@ def test_required_alias_and_validation_alias_by_alias(path: str):
|
||||||
response.text # /required-*-alias-and-validation-alias fails here
|
response.text # /required-*-alias-and-validation-alias fails here
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.json() == {
|
assert response.json() == { # pragma: no cover
|
||||||
"detail": [
|
"detail": [
|
||||||
{
|
{
|
||||||
"type": "missing",
|
"type": "missing",
|
||||||
"loc": ["body", "p_val_alias"],
|
"loc": ["body", "p_val_alias"],
|
||||||
"msg": "Field required",
|
"msg": "Field required",
|
||||||
"input": IsOneOf(
|
"input": None,
|
||||||
None,
|
|
||||||
# /model-required-uploadfile-alias-and-validation-alias fails here
|
|
||||||
{"p_alias": IsPartialDict({"size": 5})},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -763,21 +521,16 @@ def test_required_alias_and_validation_alias_by_alias(path: str):
|
||||||
"/required-bytes-alias-and-validation-alias",
|
"/required-bytes-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
pytest.param(
|
|
||||||
"/model-required-bytes-alias-and-validation-alias",
|
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
|
||||||
),
|
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"/required-uploadfile-alias-and-validation-alias",
|
"/required-uploadfile-alias-and-validation-alias",
|
||||||
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
marks=pytest.mark.xfail(raises=AssertionError, strict=False),
|
||||||
),
|
),
|
||||||
"/model-required-uploadfile-alias-and-validation-alias",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_required_alias_and_validation_alias_by_validation_alias(path: str):
|
def test_required_alias_and_validation_alias_by_validation_alias(path: str):
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = client.post(path, files=[("p_val_alias", b"hello")])
|
response = client.post(path, files=[("p_val_alias", b"hello")])
|
||||||
assert response.status_code == 200, ( # all 3 fail here
|
assert response.status_code == 200, ( # all 2 fail here
|
||||||
response.text
|
response.text
|
||||||
)
|
)
|
||||||
assert response.json() == {"file_size": 5}
|
assert response.json() == {"file_size": 5} # pragma: no cover
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue