diff --git a/tests/test_aliases/test_file_model_aliases.py b/tests/test_aliases/test_file_model_aliases.py index f002cb4a4..ba13ac8a4 100644 --- a/tests/test_aliases/test_file_model_aliases.py +++ b/tests/test_aliases/test_file_model_aliases.py @@ -1,9 +1,9 @@ from typing import List, Optional import pytest -from fastapi import FastAPI, File +from fastapi import FastAPI, File, Form from fastapi.testclient import TestClient -from pydantic import BaseModel, Field +from pydantic import BaseModel from ..utils import needs_pydanticv2 @@ -13,30 +13,33 @@ app = FastAPI() # ===================================================================================== # Field(alias=...) -# Current situation: doesn't work due to validation error +# Current situation: doesn't work (neither validation nor schema generation) # ------------------------------ # required field class RequiredFieldAliasModel(BaseModel): - file: bytes = Field(alias="file_alias") + file: bytes = File(alias="file_alias") @app.post("/required-field-alias-model") -async def required_field_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: RequiredFieldAliasModel = File(...), -): +async def required_field_alias_model(data: RequiredFieldAliasModel = Form()): return {"file_size": len(data.file)} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_model_by_name(): client = TestClient(app) resp = client.post("/required-field-alias-model", files={"file": b"content"}) assert resp.status_code == 422 detail = resp.json()["detail"] assert detail[0]["msg"] == "Field required" - assert "file_alias" in detail[0]["loc"] + # Currently fails due to issue with aliases: + # AssertionError: assert 'Input should be a valid bytes' == 'Field required' + + # Uncomment when the assertion above passes: + # assert "file_alias" in detail[0]["loc"] @pytest.mark.xfail(raises=AssertionError, strict=False) @@ -44,20 +47,25 @@ def test_required_field_alias_model_by_alias(): client = TestClient(app) resp = client.post("/required-field-alias-model", files={"file_alias": b"content"}) assert resp.status_code == 200, resp.text - # Fails with: + # Currently fails due to issue with aliases: # AssertionError: assert 422 == 200 - # {"detail":[{"type":"bytes_type","loc":["body","file_alias"],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":7,"headers":{"content-disposition":"form-data; name=\"file_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} + # {"detail":[{"type":"missing","loc":["body","file"],"msg":"Field required","input":{"file_alias":"content"}}]} # Uncomment when the assertion above passes: # assert resp.json() == {"file_size": 7} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_model_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["RequiredFieldAliasModel"] assert body_schema["properties"] == { "file_alias": {"title": "File Alias", "type": "string", "format": "binary"} } + # Currently fails due to issue with aliases: + # AssertionError: assert + # {'file': {'type': 'string', 'format': 'binary', 'title': 'File'}} == + # {'file_alias': {'title': 'File Alias', 'type': 'string', 'format': 'binary'}} # ------------------------------ @@ -65,23 +73,27 @@ def test_required_field_alias_model_schema(): class OptionalFieldAliasModel(BaseModel): - file: Optional[bytes] = Field(None, alias="file_alias") + file: Optional[bytes] = File(None, alias="file_alias") @app.post("/optional-field-alias-model") -async def optional_field_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: OptionalFieldAliasModel = File(...), -): +async def optional_field_alias_model(data: OptionalFieldAliasModel = Form()): if data.file is None: return {"file_size": None} return {"file_size": len(data.file)} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_alias_model_by_name(): client = TestClient(app) resp = client.post("/optional-field-alias-model", files={"file": b"content"}) - assert resp.status_code == 200 - assert resp.json() == {"file_size": None} + assert resp.status_code == 200, resp.text + # Currently fails due to issue with aliases: + # AssertionError: assert 422 == 200 + # {"detail":[{"type":"bytes_type","loc":["body","file"],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":7,"headers":{"content-disposition":"form-data; name=\"file\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} + + # Uncomment when the assertion above passes: + # assert resp.json() == {"file_size": None} @pytest.mark.xfail(raises=AssertionError, strict=False) @@ -89,14 +101,12 @@ def test_optional_field_alias_model_by_alias(): client = TestClient(app) resp = client.post("/optional-field-alias-model", files={"file_alias": b"content"}) assert resp.status_code == 200, resp.text - # Fails with: - # AssertionError: assert 422 == 200 - # {"detail":[{"type":"bytes_type","loc":["body","file_alias"],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":7,"headers":{"content-disposition":"form-data; name=\"file_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} - - # Uncomment when the assertion above passes: - # assert resp.json() == {"file_size": 7} + assert resp.json() == {"file_size": 7} + # Currently fails due to issue with aliases: + # AssertionError: assert {'file_size': None} == {'file_size': 7} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_alias_model_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["OptionalFieldAliasModel"] @@ -106,6 +116,10 @@ def test_optional_field_alias_model_schema(): "title": "File Alias", }, } + # Currently fails due to issue with aliases: + # AssertionError: assert + # {'file': {'anyOf': [{'type': 'string', 'format': 'binary'}, {'type': 'null'}], 'title': 'File'}} == + # {'file_alias': {'anyOf': [{'type': 'string', 'format': 'binary'}, {'type': 'null'}], 'title': 'File Alias'}} # ------------------------------ @@ -113,16 +127,15 @@ def test_optional_field_alias_model_schema(): class ListFieldAliasModel(BaseModel): - files: List[bytes] = Field(alias="files_alias") + files: List[bytes] = File(alias="files_alias") @app.post("/list-field-alias-model") -async def list_field_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: ListFieldAliasModel = File(...), -): +async def list_field_alias_model(data: ListFieldAliasModel = Form()): return {"file_sizes": [len(file) for file in data.files]} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_model_by_name(): client = TestClient(app) resp = client.post( @@ -132,7 +145,11 @@ def test_list_field_alias_model_by_name(): assert resp.status_code == 422 detail = resp.json()["detail"] assert detail[0]["msg"] == "Field required" - assert "files_alias" in detail[0]["loc"] + # Currently fails due to issue with aliases: + # AssertionError: assert 'Input should be a valid bytes' == 'Field required' + + # Uncomment when the assertion above passes: + # assert "files_alias" in detail[0]["loc"] @pytest.mark.xfail(raises=AssertionError, strict=False) @@ -143,14 +160,15 @@ def test_list_field_alias_model_by_alias(): files=[("files_alias", b"content1"), ("files_alias", b"content2")], ) assert resp.status_code == 200, resp.text - # Fails with: + # Currently fails due to issue with aliases: # AssertionError: assert 422 == 200 - # {"detail":[{"type":"bytes_type","loc":["body","files_alias",0],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}},{"type":"bytes_type","loc":["body","files_alias",1],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} + # {"detail":[{"type":"missing","loc":["body","files"],"msg":"Field required","input":{"files_alias":["content1","content2"]}}]} # Uncomment when the assertion above passes: # assert resp.json() == {"file_sizes": [8, 8]} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_model_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["ListFieldAliasModel"] @@ -161,6 +179,10 @@ def test_list_field_alias_model_schema(): "type": "array", }, } + # Currently fails due to issue with aliases: + # AssertionError: assert + # {'files': {'items': {'type': 'string', 'format': 'binary'}, 'type': 'array', 'title': 'Files'}} == + # {'files_alias': {'items': {'type': 'string', 'format': 'binary'}, 'title': 'Files Alias', 'type': 'array'}} # ------------------------------ @@ -168,26 +190,28 @@ def test_list_field_alias_model_schema(): class OptionalListFieldAliasModel(BaseModel): - files: Optional[List[bytes]] = Field(None, alias="files_alias") + files: Optional[List[bytes]] = File(None, alias="files_alias") @app.post("/optional-list-field-alias-model") -async def optional_list_field_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: OptionalListFieldAliasModel = File(), -): +async def optional_list_field_alias_model(data: OptionalListFieldAliasModel = Form()): if data.files is None: return {"file_sizes": None} return {"file_sizes": [len(file) for file in data.files]} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_alias_model_by_name(): client = TestClient(app) resp = client.post( "/optional-list-field-alias-model", files=[("files", b"content1"), ("files", b"content2")], ) - assert resp.status_code == 200 - assert resp.json() == {"file_sizes": None} + assert resp.status_code == 200, resp.text + # Currently fails due to issue with aliases + + # Uncomment when the assertion above passes: + # assert resp.json() == {"file_sizes": None} @pytest.mark.xfail(raises=AssertionError, strict=False) @@ -198,14 +222,12 @@ def test_optional_list_field_alias_model_by_alias(): files=[("files_alias", b"content1"), ("files_alias", b"content2")], ) assert resp.status_code == 200, resp.text - # Fails with: - # AssertionError: assert 422 == 200 - # {"detail":[{"type":"bytes_type","loc":["body","files_alias",0],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}},{"type":"bytes_type","loc":["body","files_alias",1],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} - - # Uncomment when the assertion above passes: - # assert resp.json() == {"file_sizes": [8, 8]} + assert resp.json() == {"file_sizes": [8, 8]} + # Currently fails due to issue with aliases + # AssertionError: assert {'file_sizes': None} == {'file_sizes': [8, 8]} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_alias_model_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["OptionalListFieldAliasModel"] @@ -218,11 +240,15 @@ def test_optional_list_field_alias_model_schema(): "title": "Files Alias", } } + # Currently fails due to issue with aliases: + # AssertionError: assert + # {'files': {'anyOf': [{'items': {'type': 'string', 'format': 'binary'}, 'type': 'array'}, {'type': 'null'}], 'title': 'Files'}} == + # {'files_alias': {'anyOf': [{'items': {'type': 'string', 'format': 'binary'}, 'type': 'array'}, {'type': 'null'}], 'title': 'Files Alias'}} # ===================================================================================== # Field(validation_alias=...) -# Current situation: doesn't work due to validation error +# Current situation: validation doesn't work, but schema generation works # ------------------------------ @@ -230,15 +256,15 @@ def test_optional_list_field_alias_model_schema(): class RequiredFieldValidationAliasModel(BaseModel): - file: bytes = Field(validation_alias="file_val_alias") + file: bytes = File(validation_alias="file_val_alias") @app.post( "/required-field-validation-alias-model", operation_id="required_field_validation_alias_model", ) -async def required_field_validation_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: RequiredFieldValidationAliasModel = File(...), +async def required_field_validation_alias_model( + data: RequiredFieldValidationAliasModel = Form(), ): return {"file_size": len(data.file)} @@ -261,7 +287,6 @@ def test_required_field_validation_alias_model_by_alias(): "/required-field-validation-alias-model", files={"file_val_alias": b"content"} ) assert resp.status_code == 200, resp.text - # Fails with: # AssertionError: assert 422 == 200 # {"detail":[{"type":"bytes_type","loc":["body","file_val_alias"],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":7,"headers":{"content-disposition":"form-data; name=\"file_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} @@ -286,15 +311,15 @@ def test_required_field_validation_alias_model_schema(): class OptionalFieldValidationAliasModel(BaseModel): - file: Optional[bytes] = Field(None, validation_alias="file_val_alias") + file: Optional[bytes] = File(None, validation_alias="file_val_alias") @app.post( "/optional-field-validation-alias-model", operation_id="optional_field_validation_alias_model", ) -async def optional_field_validation_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: OptionalFieldValidationAliasModel = File(...), +async def optional_field_validation_alias_model( + data: OptionalFieldValidationAliasModel = Form(), ): if data.file is None: return {"file_size": None} @@ -317,7 +342,6 @@ def test_optional_field_validation_alias_model_by_alias(): "/optional-field-validation-alias-model", files={"file_val_alias": b"content"} ) assert resp.status_code == 200, resp.text - # Fails with: # AssertionError: assert 422 == 200 # {"detail":[{"type":"bytes_type","loc":["body","file_val_alias"],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":7,"headers":{"content-disposition":"form-data; name=\"file_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} @@ -341,15 +365,15 @@ def test_optional_field_validation_alias_model_schema(): class ListFieldValidationAliasModel(BaseModel): - files: List[bytes] = Field(validation_alias="files_val_alias") + files: List[bytes] = File(validation_alias="files_val_alias") @app.post( "/list-field-validation-alias-model", operation_id="list_field_validation_alias_model", ) -async def list_field_validation_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: ListFieldValidationAliasModel = File(...), +async def list_field_validation_alias_model( + data: ListFieldValidationAliasModel = Form(), ): return {"file_sizes": [len(file) for file in data.files]} @@ -374,7 +398,6 @@ def test_list_field_validation_alias_model_by_alias(): files=[("files_val_alias", b"content1"), ("files_val_alias", b"content2")], ) assert resp.status_code == 200, resp.text - # Fails with: # AssertionError: assert 422 == 200 # {"detail":[{"type":"bytes_type","loc":["body","files_val_alias",0],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}},{"type":"bytes_type","loc":["body","files_val_alias",1],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} @@ -399,15 +422,15 @@ def test_list_field_validation_alias_model_schema(): class OptionalListFieldValidationAliasModel(BaseModel): - files: Optional[List[bytes]] = Field(None, validation_alias="files_val_alias") + files: Optional[List[bytes]] = File(None, validation_alias="files_val_alias") @app.post( "/optional-list-field-validation-alias-model", operation_id="optional_list_field_validation_alias_model", ) -async def optional_list_field_validation_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: OptionalListFieldValidationAliasModel = File(...), +async def optional_list_field_validation_alias_model( + data: OptionalListFieldValidationAliasModel = Form(), ): if data.files is None: return {"file_sizes": None} @@ -432,7 +455,6 @@ def test_optional_list_field_validation_alias_model_by_alias(): files=[("files_val_alias", b"content1"), ("files_val_alias", b"content2")], ) assert resp.status_code == 200, resp.text - # Fails with: # AssertionError: assert 422 == 200 # {"detail":[{"type":"bytes_type","loc":["body","files_val_alias",0],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}},{"type":"bytes_type","loc":["body","files_val_alias",1],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} @@ -458,22 +480,22 @@ def test_optional_list_field_validation_alias_model_schema(): # ===================================================================================== # Field(alias=..., validation_alias=...) -# Current situation: doesn't work due to validation error +# Current situation: validation doesn't work, but schema generation works # ------------------------------ # required field class RequiredFieldAliasAndValidationAliasModel(BaseModel): - file: bytes = Field(alias="file_alias", validation_alias="file_val_alias") + file: bytes = File(alias="file_alias", validation_alias="file_val_alias") @app.post( "/required-field-alias-and-validation-alias-model", operation_id="required_field_alias_and_validation_alias_model", ) -async def required_field_alias_and_validation_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: RequiredFieldAliasAndValidationAliasModel = File(...), +async def required_field_alias_and_validation_alias_model( + data: RequiredFieldAliasAndValidationAliasModel = Form(), ): return {"file_size": len(data.file)} @@ -509,7 +531,6 @@ def test_required_field_alias_and_validation_alias_model_by_validation_alias(): files={"file_val_alias": b"content"}, ) assert resp.status_code == 200, resp.text - # Fails with: # AssertionError: assert 422 == 200 # {"detail":[{"type":"bytes_type","loc":["body","file_val_alias"],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":7,"headers":{"content-disposition":"form-data; name=\"file_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} @@ -536,7 +557,7 @@ def test_required_field_alias_and_validation_alias_model_schema(): class OptionalFieldAliasAndValidationAliasModel(BaseModel): - file: Optional[bytes] = Field( + file: Optional[bytes] = File( None, alias="file_alias", validation_alias="file_val_alias" ) @@ -545,8 +566,8 @@ class OptionalFieldAliasAndValidationAliasModel(BaseModel): "/optional-field-alias-and-validation-alias-model", operation_id="optional_field_alias_and_validation_alias_model", ) -async def optional_field_alias_and_validation_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: OptionalFieldAliasAndValidationAliasModel = File(...), +async def optional_field_alias_and_validation_alias_model( + data: OptionalFieldAliasAndValidationAliasModel = Form(), ): if data.file is None: return {"file_size": None} @@ -580,7 +601,6 @@ def test_optional_field_alias_and_validation_alias_model_by_validation_alias(): files={"file_val_alias": b"content"}, ) assert resp.status_code == 200, resp.text - # Fails with: # AssertionError: assert 422 == 200 # {"detail":[{"type":"bytes_type","loc":["body","file_val_alias"],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":7,"headers":{"content-disposition":"form-data; name=\"file_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} @@ -606,15 +626,15 @@ def test_optional_field_alias_and_validation_alias_model_schema(): class ListFieldAliasAndValidationAliasModel(BaseModel): - files: List[bytes] = Field(alias="files_alias", validation_alias="files_val_alias") + files: List[bytes] = File(alias="files_alias", validation_alias="files_val_alias") @app.post( "/list-field-alias-and-validation-alias-model", operation_id="list_field_alias_and_validation_alias_model", ) -async def list_field_alias_and_validation_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: ListFieldAliasAndValidationAliasModel = File(...), +async def list_field_alias_and_validation_alias_model( + data: ListFieldAliasAndValidationAliasModel = Form(), ): return {"file_sizes": [len(file) for file in data.files]} @@ -651,7 +671,6 @@ def test_list_field_alias_and_validation_alias_model_by_validation_alias(): files=[("files_val_alias", b"content1"), ("files_val_alias", b"content2")], ) assert resp.status_code == 200, resp.text - # Fails with: # AssertionError: assert 422 == 200 # {"detail":[{"type":"bytes_type","loc":["body","files_val_alias",0],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}},{"type":"bytes_type","loc":["body","files_val_alias",1],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} @@ -678,7 +697,7 @@ def test_list_field_alias_and_validation_alias_model_schema(): class OptionalListFieldAliasAndValidationAliasModel(BaseModel): - files: Optional[List[bytes]] = Field( + files: Optional[List[bytes]] = File( None, alias="files_alias", validation_alias="files_val_alias" ) @@ -687,8 +706,8 @@ class OptionalListFieldAliasAndValidationAliasModel(BaseModel): "/optional-list-field-alias-and-validation-alias-model", operation_id="optional_list_field_alias_and_validation_alias_model", ) -async def optional_list_field_alias_and_validation_alias_model( # pragma: no cover (remove `no cover` when bug fixed) - data: OptionalListFieldAliasAndValidationAliasModel = File(...), +async def optional_list_field_alias_and_validation_alias_model( + data: OptionalListFieldAliasAndValidationAliasModel = Form(), ): if data.files is None: return {"file_sizes": None} @@ -723,7 +742,6 @@ def test_optional_list_field_alias_and_validation_alias_model_by_validation_alia files=[("files_val_alias", b"content1"), ("files_val_alias", b"content2")], ) assert resp.status_code == 200, resp.text - # Fails with: # AssertionError: assert 422 == 200 # {"detail":[{"type":"bytes_type","loc":["body","files_val_alias",0],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}},{"type":"bytes_type","loc":["body","files_val_alias",1],"msg":"Input should be a valid bytes","input":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":8,"headers":{"content-disposition":"form-data; name=\"files_val_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}]} diff --git a/tests/test_aliases/test_uploadfile_model_aliases.py b/tests/test_aliases/test_uploadfile_model_aliases.py index d13c1e54c..3fa7f3ee9 100644 --- a/tests/test_aliases/test_uploadfile_model_aliases.py +++ b/tests/test_aliases/test_uploadfile_model_aliases.py @@ -1,8 +1,9 @@ from typing import List, Optional -from fastapi import FastAPI, File, UploadFile +import pytest +from fastapi import FastAPI, File, Form, UploadFile from fastapi.testclient import TestClient -from pydantic import BaseModel, Field +from pydantic import BaseModel from ..utils import needs_pydanticv2 @@ -12,43 +13,56 @@ app = FastAPI() # ===================================================================================== # Field(alias=...) -# Current situation: Works +# Current situation: doesn't work (neither validation nor schema generation) # ------------------------------ # required field class RequiredFieldAliasModel(BaseModel): - file: UploadFile = Field(alias="file_alias") + file: UploadFile = File(alias="file_alias") @app.post("/required-field-alias-model") -async def required_field_alias_model(data: RequiredFieldAliasModel = File(...)): +async def required_field_alias_model(data: RequiredFieldAliasModel = Form()): return {"file_size": data.file.size} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_model_by_name(): client = TestClient(app) resp = client.post("/required-field-alias-model", files={"file": b"content"}) assert resp.status_code == 422 - detail = resp.json()["detail"] - assert detail[0]["msg"] == "Field required" - assert "file_alias" in detail[0]["loc"] + # assert 200 == 422 + + # Uncomment when the assertion above passes: + # detail = resp.json()["detail"] + # assert detail[0]["msg"] == "Field required" + # assert "file_alias" in detail[0]["loc"] +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_model_by_alias(): client = TestClient(app) resp = client.post("/required-field-alias-model", files={"file_alias": b"content"}) assert resp.status_code == 200, resp.text - assert resp.json() == {"file_size": 7} + # AssertionError: assert 422 == 200 + # {"detail":[{"type":"missing","loc":["body","file"],"msg":"Field required","input":{"file_alias":{"filename":"upload","file":{"_file":{},"_max_size":1048576,"_rolled":false,"_TemporaryFileArgs":{"mode":"w+b","buffering":-1,"suffix":null,"prefix":null,"encoding":null,"newline":null,"dir":null,"errors":null}},"size":7,"headers":{"content-disposition":"form-data; name=\"file_alias\"; filename=\"upload\"","content-type":"application/octet-stream"},"_max_mem_size":1048576}}}]} + + # Uncomment when the assertion above passes: + # assert resp.json() == {"file_size": 7} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_required_field_alias_model_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["RequiredFieldAliasModel"] assert body_schema["properties"] == { "file_alias": {"title": "File Alias", "type": "string", "format": "binary"} } + # AssertionError: assert + # {'file': {'type': 'string', 'format': 'binary', 'title': 'File'}} == + # {'file_alias': {'title': 'File Alias', 'type': 'string', 'format': 'binary'}} # ------------------------------ @@ -56,30 +70,35 @@ def test_required_field_alias_model_schema(): class OptionalFieldAliasModel(BaseModel): - file: Optional[UploadFile] = Field(None, alias="file_alias") + file: Optional[UploadFile] = File(None, alias="file_alias") @app.post("/optional-field-alias-model") -async def optional_field_alias_model(data: OptionalFieldAliasModel = File(...)): +async def optional_field_alias_model(data: OptionalFieldAliasModel = Form()): if data.file is None: return {"file_size": None} return {"file_size": data.file.size} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_alias_model_by_name(): client = TestClient(app) resp = client.post("/optional-field-alias-model", files={"file": b"content"}) assert resp.status_code == 200 assert resp.json() == {"file_size": None} + # AssertionError: assert {'file_size': 7} == {'file_size': None} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_alias_model_by_alias(): client = TestClient(app) resp = client.post("/optional-field-alias-model", files={"file_alias": b"content"}) assert resp.status_code == 200, resp.text assert resp.json() == {"file_size": 7} + # AssertionError: assert {'file_size': None} == {'file_size': 7} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_field_alias_model_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["OptionalFieldAliasModel"] @@ -89,6 +108,9 @@ def test_optional_field_alias_model_schema(): "title": "File Alias", }, } + # AssertionError: assert + # {'file': {'anyOf': [{'type': 'string', 'format': 'binary'}, {'type': 'null'}], 'title': 'File'}} == + # {'file_alias': {'anyOf': [{'type': 'string', 'format': 'binary'}, {'type': 'null'}], 'title': 'File Alias'}} # ------------------------------ @@ -96,14 +118,15 @@ def test_optional_field_alias_model_schema(): class ListFieldAliasModel(BaseModel): - files: List[UploadFile] = Field(alias="files_alias") + files: List[UploadFile] = File(alias="files_alias") @app.post("/list-field-alias-model") -async def list_field_alias_model(data: ListFieldAliasModel = File(...)): +async def list_field_alias_model(data: ListFieldAliasModel = Form()): return {"file_sizes": [file.size for file in data.files]} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_model_by_name(): client = TestClient(app) resp = client.post( @@ -111,11 +134,14 @@ def test_list_field_alias_model_by_name(): files=[("files", b"content1"), ("files", b"content2")], ) assert resp.status_code == 422 - detail = resp.json()["detail"] - assert detail[0]["msg"] == "Field required" - assert "files_alias" in detail[0]["loc"] + + # Uncomment when the assertion above passes: + # detail = resp.json()["detail"] + # assert detail[0]["msg"] == "Field required" + # assert "files_alias" in detail[0]["loc"] +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_model_by_alias(): client = TestClient(app) resp = client.post( @@ -123,9 +149,12 @@ def test_list_field_alias_model_by_alias(): files=[("files_alias", b"content1"), ("files_alias", b"content2")], ) assert resp.status_code == 200, resp.text - assert resp.json() == {"file_sizes": [8, 8]} + + # Uncomment when the assertion above passes: + # assert resp.json() == {"file_sizes": [8, 8]} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_list_field_alias_model_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["ListFieldAliasModel"] @@ -143,16 +172,17 @@ def test_list_field_alias_model_schema(): class OptionalListFieldAliasModel(BaseModel): - files: Optional[List[UploadFile]] = Field(None, alias="files_alias") + files: Optional[List[UploadFile]] = File(None, alias="files_alias") @app.post("/optional-list-field-alias-model") -async def optional_list_field_alias_model(data: OptionalListFieldAliasModel = File()): +async def optional_list_field_alias_model(data: OptionalListFieldAliasModel = Form()): if data.files is None: return {"file_sizes": None} return {"file_sizes": [file.size for file in data.files]} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_alias_model_by_name(): client = TestClient(app) resp = client.post( @@ -163,6 +193,7 @@ def test_optional_list_field_alias_model_by_name(): assert resp.json() == {"file_sizes": None} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_alias_model_by_alias(): client = TestClient(app) resp = client.post( @@ -173,6 +204,7 @@ def test_optional_list_field_alias_model_by_alias(): assert resp.json() == {"file_sizes": [8, 8]} +@pytest.mark.xfail(raises=AssertionError, strict=False) def test_optional_list_field_alias_model_schema(): openapi = app.openapi() body_schema = openapi["components"]["schemas"]["OptionalListFieldAliasModel"] @@ -197,7 +229,7 @@ def test_optional_list_field_alias_model_schema(): class RequiredFieldValidationAliasModel(BaseModel): - file: UploadFile = Field(validation_alias="file_val_alias") + file: UploadFile = File(validation_alias="file_val_alias") @app.post( @@ -205,7 +237,7 @@ class RequiredFieldValidationAliasModel(BaseModel): operation_id="required_field_validation_alias_model", ) async def required_field_validation_alias_model( - data: RequiredFieldValidationAliasModel = File(...), + data: RequiredFieldValidationAliasModel = Form(), ): return {"file_size": data.file.size} @@ -247,7 +279,7 @@ def test_required_field_validation_alias_model_schema(): class OptionalFieldValidationAliasModel(BaseModel): - file: Optional[UploadFile] = Field(None, validation_alias="file_val_alias") + file: Optional[UploadFile] = File(None, validation_alias="file_val_alias") @app.post( @@ -255,7 +287,7 @@ class OptionalFieldValidationAliasModel(BaseModel): operation_id="optional_field_validation_alias_model", ) async def optional_field_validation_alias_model( - data: OptionalFieldValidationAliasModel = File(...), + data: OptionalFieldValidationAliasModel = Form(), ): if data.file is None: return {"file_size": None} @@ -296,7 +328,7 @@ def test_optional_field_validation_alias_model_schema(): class ListFieldValidationAliasModel(BaseModel): - files: List[UploadFile] = Field(validation_alias="files_val_alias") + files: List[UploadFile] = File(validation_alias="files_val_alias") @app.post( @@ -304,7 +336,7 @@ class ListFieldValidationAliasModel(BaseModel): operation_id="list_field_validation_alias_model", ) async def list_field_validation_alias_model( - data: ListFieldValidationAliasModel = File(...), + data: ListFieldValidationAliasModel = Form(), ): return {"file_sizes": [file.size for file in data.files]} @@ -350,7 +382,7 @@ def test_list_field_validation_alias_model_schema(): class OptionalListFieldValidationAliasModel(BaseModel): - files: Optional[List[UploadFile]] = Field(None, validation_alias="files_val_alias") + files: Optional[List[UploadFile]] = File(None, validation_alias="files_val_alias") @app.post( @@ -358,7 +390,7 @@ class OptionalListFieldValidationAliasModel(BaseModel): operation_id="optional_list_field_validation_alias_model", ) async def optional_list_field_validation_alias_model( - data: OptionalListFieldValidationAliasModel = File(...), + data: OptionalListFieldValidationAliasModel = Form(), ): if data.files is None: return {"file_sizes": None} @@ -414,7 +446,7 @@ def test_optional_list_field_validation_alias_model_schema(): class RequiredFieldAliasAndValidationAliasModel(BaseModel): - file: UploadFile = Field(alias="file_alias", validation_alias="file_val_alias") + file: UploadFile = File(alias="file_alias", validation_alias="file_val_alias") @app.post( @@ -422,7 +454,7 @@ class RequiredFieldAliasAndValidationAliasModel(BaseModel): operation_id="required_field_alias_and_validation_alias_model", ) async def required_field_alias_and_validation_alias_model( - data: RequiredFieldAliasAndValidationAliasModel = File(...), + data: RequiredFieldAliasAndValidationAliasModel = Form(), ): return {"file_size": data.file.size} @@ -479,7 +511,7 @@ def test_required_field_alias_and_validation_alias_model_schema(): class OptionalFieldAliasAndValidationAliasModel(BaseModel): - file: Optional[UploadFile] = Field( + file: Optional[UploadFile] = File( None, alias="file_alias", validation_alias="file_val_alias" ) @@ -489,7 +521,7 @@ class OptionalFieldAliasAndValidationAliasModel(BaseModel): operation_id="optional_field_alias_and_validation_alias_model", ) async def optional_field_alias_and_validation_alias_model( - data: OptionalFieldAliasAndValidationAliasModel = File(...), + data: OptionalFieldAliasAndValidationAliasModel = Form(), ): if data.file is None: return {"file_size": None} @@ -543,7 +575,7 @@ def test_optional_field_alias_and_validation_alias_model_schema(): class ListFieldAliasAndValidationAliasModel(BaseModel): - files: List[UploadFile] = Field( + files: List[UploadFile] = File( alias="files_alias", validation_alias="files_val_alias" ) @@ -553,7 +585,7 @@ class ListFieldAliasAndValidationAliasModel(BaseModel): operation_id="list_field_alias_and_validation_alias_model", ) async def list_field_alias_and_validation_alias_model( - data: ListFieldAliasAndValidationAliasModel = File(...), + data: ListFieldAliasAndValidationAliasModel = Form(), ): return {"file_sizes": [file.size for file in data.files]} @@ -613,7 +645,7 @@ def test_list_field_alias_and_validation_alias_model_schema(): class OptionalListFieldAliasAndValidationAliasModel(BaseModel): - files: Optional[List[UploadFile]] = Field( + files: Optional[List[UploadFile]] = File( None, alias="files_alias", validation_alias="files_val_alias" ) @@ -623,7 +655,7 @@ class OptionalListFieldAliasAndValidationAliasModel(BaseModel): operation_id="optional_list_field_alias_and_validation_alias_model", ) async def optional_list_field_alias_and_validation_alias_model( - data: OptionalListFieldAliasAndValidationAliasModel = File(...), + data: OptionalListFieldAliasAndValidationAliasModel = Form(), ): if data.files is None: return {"file_sizes": None}