Coverage for tests/test_compat.py: 100%
83 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-09-22 00:03 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-09-22 00:03 +0000
1from typing import Any, Dict, List, Union 1abcdef
3from fastapi import FastAPI, UploadFile 1abcdef
4from fastapi._compat import ( 1abcdef
5 ModelField,
6 Undefined,
7 _get_model_config,
8 get_cached_model_fields,
9 get_model_fields,
10 is_bytes_sequence_annotation,
11 is_scalar_field,
12 is_uploadfile_sequence_annotation,
13)
14from fastapi.testclient import TestClient 1abcdef
15from pydantic import BaseConfig, BaseModel, ConfigDict 1abcdef
16from pydantic.fields import FieldInfo 1abcdef
18from .utils import needs_pydanticv1, needs_pydanticv2 1abcdef
21@needs_pydanticv2 1abcdef
22def test_model_field_default_required(): 1abcdef
23 # For coverage
24 field_info = FieldInfo(annotation=str) 1QRSTUV
25 field = ModelField(name="foo", field_info=field_info) 1QRSTUV
26 assert field.default is Undefined 1QRSTUV
29@needs_pydanticv1 1abcdef
30def test_upload_file_dummy_with_info_plain_validator_function(): 1abcdef
31 # For coverage
32 assert UploadFile.__get_pydantic_core_schema__(str, lambda x: None) == {} 1WXYZ01
35@needs_pydanticv1 1abcdef
36def test_union_scalar_list(): 1abcdef
37 # For coverage
38 # TODO: there might not be a current valid code path that uses this, it would
39 # potentially enable query parameters defined as both a scalar and a list
40 # but that would require more refactors, also not sure it's really useful
41 from fastapi._compat import is_pv1_scalar_field 1EFGHIJ
43 field_info = FieldInfo() 1EFGHIJ
44 field = ModelField( 1EFGHIJ
45 name="foo",
46 field_info=field_info,
47 type_=Union[str, List[int]],
48 class_validators={},
49 model_config=BaseConfig,
50 )
51 assert not is_pv1_scalar_field(field) 1EFGHIJ
54@needs_pydanticv2 1abcdef
55def test_get_model_config(): 1abcdef
56 # For coverage in Pydantic v2
57 class Foo(BaseModel): 1yzABCD
58 model_config = ConfigDict(from_attributes=True) 1yzABCD
60 foo = Foo() 1yzABCD
61 config = _get_model_config(foo) 1yzABCD
62 assert config == {"from_attributes": True} 1yzABCD
65def test_complex(): 1abcdef
66 app = FastAPI() 1mnopqr
68 @app.post("/") 1mnopqr
69 def foo(foo: Union[str, List[int]]): 1mnopqr
70 return foo 1mnopqr
72 client = TestClient(app) 1mnopqr
74 response = client.post("/", json="bar") 1mnopqr
75 assert response.status_code == 200, response.text 1mnopqr
76 assert response.json() == "bar" 1mnopqr
78 response2 = client.post("/", json=[1, 2]) 1mnopqr
79 assert response2.status_code == 200, response2.text 1mnopqr
80 assert response2.json() == [1, 2] 1mnopqr
83@needs_pydanticv2 1abcdef
84def test_propagates_pydantic2_model_config(): 1abcdef
85 app = FastAPI() 1ghijkl
87 class Missing: 1ghijkl
88 def __bool__(self): 1ghijkl
89 return False 1ghijkl
91 class EmbeddedModel(BaseModel): 1ghijkl
92 model_config = ConfigDict(arbitrary_types_allowed=True) 1ghijkl
93 value: Union[str, Missing] = Missing() 1ghijkl
95 class Model(BaseModel): 1ghijkl
96 model_config = ConfigDict( 1ghijkl
97 arbitrary_types_allowed=True,
98 )
99 value: Union[str, Missing] = Missing() 1ghijkl
100 embedded_model: EmbeddedModel = EmbeddedModel() 1ghijkl
102 @app.post("/") 1ghijkl
103 def foo(req: Model) -> Dict[str, Union[str, None]]: 1ghijkl
104 return { 1ghijkl
105 "value": req.value or None,
106 "embedded_value": req.embedded_model.value or None,
107 }
109 client = TestClient(app) 1ghijkl
111 response = client.post("/", json={}) 1ghijkl
112 assert response.status_code == 200, response.text 1ghijkl
113 assert response.json() == { 1ghijkl
114 "value": None,
115 "embedded_value": None,
116 }
118 response2 = client.post( 1ghijkl
119 "/", json={"value": "foo", "embedded_model": {"value": "bar"}}
120 )
121 assert response2.status_code == 200, response2.text 1ghijkl
122 assert response2.json() == { 1ghijkl
123 "value": "foo",
124 "embedded_value": "bar",
125 }
128def test_is_bytes_sequence_annotation_union(): 1abcdef
129 # For coverage
130 # TODO: in theory this would allow declaring types that could be lists of bytes
131 # to be read from files and other types, but I'm not even sure it's a good idea
132 # to support it as a first class "feature"
133 assert is_bytes_sequence_annotation(Union[List[str], List[bytes]]) 1234567
136def test_is_uploadfile_sequence_annotation(): 1abcdef
137 # For coverage
138 # TODO: in theory this would allow declaring types that could be lists of UploadFile
139 # and other types, but I'm not even sure it's a good idea to support it as a first
140 # class "feature"
141 assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]]) 189!#$%
144def test_is_pv1_scalar_field(): 1abcdef
145 # For coverage
146 class Model(BaseModel): 1KLMNOP
147 foo: Union[str, Dict[str, Any]] 1KLMNOP
149 fields = get_model_fields(Model) 1KLMNOP
150 assert not is_scalar_field(fields[0]) 1KLMNOP
153def test_get_model_fields_cached(): 1abcdef
154 class Model(BaseModel): 1stuvwx
155 foo: str 1stuvwx
157 non_cached_fields = get_model_fields(Model) 1stuvwx
158 non_cached_fields2 = get_model_fields(Model) 1stuvwx
159 cached_fields = get_cached_model_fields(Model) 1stuvwx
160 cached_fields2 = get_cached_model_fields(Model) 1stuvwx
161 for f1, f2 in zip(cached_fields, cached_fields2): 1stuvwx
162 assert f1 is f2 1stuvwx
164 assert non_cached_fields is not non_cached_fields2 1stuvwx
165 assert cached_fields is cached_fields2 1stuvwx