Coverage for tests/test_compat.py: 100%
113 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-04 08:29 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-04 08:29 +0000
1from typing import Any, Dict, List, Union 1abcdefg
3from fastapi import FastAPI, UploadFile 1abcdefg
4from fastapi._compat import ( 1abcdefg
5 Undefined,
6 _get_model_config,
7 get_cached_model_fields,
8 is_scalar_field,
9 is_uploadfile_sequence_annotation,
10 may_v1,
11)
12from fastapi._compat.shared import is_bytes_sequence_annotation 1abcdefg
13from fastapi.testclient import TestClient 1abcdefg
14from pydantic import BaseModel, ConfigDict 1abcdefg
15from pydantic.fields import FieldInfo 1abcdefg
17from .utils import needs_py310, needs_py_lt_314, needs_pydanticv2 1abcdefg
20@needs_pydanticv2 1abcdefg
21def test_model_field_default_required(): 1abcdefg
22 from fastapi._compat import v2 156789!#
24 # For coverage
25 field_info = FieldInfo(annotation=str) 156789!#
26 field = v2.ModelField(name="foo", field_info=field_info) 156789!#
27 assert field.default is Undefined 156789!#
30@needs_py_lt_314 1abcdefg
31def test_v1_plain_validator_function(): 1abcdefg
32 from fastapi._compat import v1 1$%'()
34 # For coverage
35 def func(v): # pragma: no cover 1$%'()
36 return v
38 result = v1.with_info_plain_validator_function(func) 1$%'()
39 assert result == {} 1$%'()
42def test_is_model_field(): 1abcdefg
43 # For coverage
44 from fastapi._compat import _is_model_field 1*+,-./:
46 assert not _is_model_field(str) 1*+,-./:
49@needs_pydanticv2 1abcdefg
50def test_get_model_config(): 1abcdefg
51 # For coverage in Pydantic v2
52 class Foo(BaseModel): 1TUVWXYZ
53 model_config = ConfigDict(from_attributes=True) 1TUVWXYZ
55 foo = Foo() 1TUVWXYZ
56 config = _get_model_config(foo) 1TUVWXYZ
57 assert config == {"from_attributes": True} 1TUVWXYZ
60def test_complex(): 1abcdefg
61 app = FastAPI() 1opqrstu
63 @app.post("/") 1opqrstu
64 def foo(foo: Union[str, List[int]]): 1opqrstu
65 return foo 1opqrstu
67 client = TestClient(app) 1opqrstu
69 response = client.post("/", json="bar") 1opqrstu
70 assert response.status_code == 200, response.text 1opqrstu
71 assert response.json() == "bar" 1opqrstu
73 response2 = client.post("/", json=[1, 2]) 1opqrstu
74 assert response2.status_code == 200, response2.text 1opqrstu
75 assert response2.json() == [1, 2] 1opqrstu
78@needs_pydanticv2 1abcdefg
79def test_propagates_pydantic2_model_config(): 1abcdefg
80 app = FastAPI() 1hijklmn
82 class Missing: 1hijklmn
83 def __bool__(self): 1hijklmn
84 return False 1hijklmn
86 class EmbeddedModel(BaseModel): 1hijklmn
87 model_config = ConfigDict(arbitrary_types_allowed=True) 1hijklmn
88 value: Union[str, Missing] = Missing() 1hijklmn
90 class Model(BaseModel): 1hijklmn
91 model_config = ConfigDict( 1hijklmn
92 arbitrary_types_allowed=True,
93 )
94 value: Union[str, Missing] = Missing() 1hijklmn
95 embedded_model: EmbeddedModel = EmbeddedModel() 1hijklmn
97 @app.post("/") 1hijklmn
98 def foo(req: Model) -> Dict[str, Union[str, None]]: 1hijklmn
99 return { 1hijklmn
100 "value": req.value or None,
101 "embedded_value": req.embedded_model.value or None,
102 }
104 client = TestClient(app) 1hijklmn
106 response = client.post("/", json={}) 1hijklmn
107 assert response.status_code == 200, response.text 1hijklmn
108 assert response.json() == { 1hijklmn
109 "value": None,
110 "embedded_value": None,
111 }
113 response2 = client.post( 1hijklmn
114 "/", json={"value": "foo", "embedded_model": {"value": "bar"}}
115 )
116 assert response2.status_code == 200, response2.text 1hijklmn
117 assert response2.json() == { 1hijklmn
118 "value": "foo",
119 "embedded_value": "bar",
120 }
123def test_is_bytes_sequence_annotation_union(): 1abcdefg
124 # For coverage
125 # TODO: in theory this would allow declaring types that could be lists of bytes
126 # to be read from files and other types, but I'm not even sure it's a good idea
127 # to support it as a first class "feature"
128 assert is_bytes_sequence_annotation(Union[List[str], List[bytes]]) 1;=?@[]^
131def test_is_uploadfile_sequence_annotation(): 1abcdefg
132 # For coverage
133 # TODO: in theory this would allow declaring types that could be lists of UploadFile
134 # and other types, but I'm not even sure it's a good idea to support it as a first
135 # class "feature"
136 assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]]) 2_ ` { | } ~ ab
139@needs_pydanticv2 1abcdefg
140def test_serialize_sequence_value_with_optional_list(): 1abcdefg
141 """Test that serialize_sequence_value handles optional lists correctly."""
142 from fastapi._compat import v2 1ABCDEFG
144 field_info = FieldInfo(annotation=Union[List[str], None]) 1ABCDEFG
145 field = v2.ModelField(name="items", field_info=field_info) 1ABCDEFG
146 result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"]) 1ABCDEFG
147 assert result == ["a", "b", "c"] 1ABCDEFG
148 assert isinstance(result, list) 1ABCDEFG
151@needs_pydanticv2 1abcdefg
152@needs_py310 1abcdefg
153def test_serialize_sequence_value_with_optional_list_pipe_union(): 1abcdefg
154 """Test that serialize_sequence_value handles optional lists correctly (with new syntax)."""
155 from fastapi._compat import v2 1HIJKL
157 field_info = FieldInfo(annotation=list[str] | None) 1HIJKL
158 field = v2.ModelField(name="items", field_info=field_info) 1HIJKL
159 result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"]) 1HIJKL
160 assert result == ["a", "b", "c"] 1HIJKL
161 assert isinstance(result, list) 1HIJKL
164@needs_pydanticv2 1abcdefg
165def test_serialize_sequence_value_with_none_first_in_union(): 1abcdefg
166 """Test that serialize_sequence_value handles Union[None, List[...]] correctly."""
167 from fastapi._compat import v2 1MNOPQRS
169 field_info = FieldInfo(annotation=Union[None, List[str]]) 1MNOPQRS
170 field = v2.ModelField(name="items", field_info=field_info) 1MNOPQRS
171 result = v2.serialize_sequence_value(field=field, value=["x", "y"]) 1MNOPQRS
172 assert result == ["x", "y"] 1MNOPQRS
173 assert isinstance(result, list) 1MNOPQRS
176@needs_py_lt_314 1abcdefg
177def test_is_pv1_scalar_field(): 1abcdefg
178 from fastapi._compat import v1 101234
180 # For coverage
181 class Model(v1.BaseModel): 101234
182 foo: Union[str, Dict[str, Any]] 101234
184 fields = v1.get_model_fields(Model) 101234
185 assert not is_scalar_field(fields[0]) 101234
188@needs_py_lt_314 1abcdefg
189def test_get_model_fields_cached(): 1abcdefg
190 from fastapi._compat import v1 1vwxyz
192 class Model(may_v1.BaseModel): 1vwxyz
193 foo: str 1vwxyz
195 non_cached_fields = v1.get_model_fields(Model) 1vwxyz
196 non_cached_fields2 = v1.get_model_fields(Model) 1vwxyz
197 cached_fields = get_cached_model_fields(Model) 1vwxyz
198 cached_fields2 = get_cached_model_fields(Model) 1vwxyz
199 for f1, f2 in zip(cached_fields, cached_fields2): 1vwxyz
200 assert f1 is f2 1vwxyz
202 assert non_cached_fields is not non_cached_fields2 1vwxyz
203 assert cached_fields is cached_fields2 1vwxyz