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

1from typing import Any, Dict, List, Union 1abcdef

2 

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

17 

18from .utils import needs_pydanticv1, needs_pydanticv2 1abcdef

19 

20 

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

27 

28 

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

33 

34 

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

42 

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

52 

53 

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

59 

60 foo = Foo() 1yzABCD

61 config = _get_model_config(foo) 1yzABCD

62 assert config == {"from_attributes": True} 1yzABCD

63 

64 

65def test_complex(): 1abcdef

66 app = FastAPI() 1mnopqr

67 

68 @app.post("/") 1mnopqr

69 def foo(foo: Union[str, List[int]]): 1mnopqr

70 return foo 1mnopqr

71 

72 client = TestClient(app) 1mnopqr

73 

74 response = client.post("/", json="bar") 1mnopqr

75 assert response.status_code == 200, response.text 1mnopqr

76 assert response.json() == "bar" 1mnopqr

77 

78 response2 = client.post("/", json=[1, 2]) 1mnopqr

79 assert response2.status_code == 200, response2.text 1mnopqr

80 assert response2.json() == [1, 2] 1mnopqr

81 

82 

83@needs_pydanticv2 1abcdef

84def test_propagates_pydantic2_model_config(): 1abcdef

85 app = FastAPI() 1ghijkl

86 

87 class Missing: 1ghijkl

88 def __bool__(self): 1ghijkl

89 return False 1ghijkl

90 

91 class EmbeddedModel(BaseModel): 1ghijkl

92 model_config = ConfigDict(arbitrary_types_allowed=True) 1ghijkl

93 value: Union[str, Missing] = Missing() 1ghijkl

94 

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

101 

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 } 

108 

109 client = TestClient(app) 1ghijkl

110 

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 } 

117 

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 } 

126 

127 

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

134 

135 

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!#$%

142 

143 

144def test_is_pv1_scalar_field(): 1abcdef

145 # For coverage 

146 class Model(BaseModel): 1KLMNOP

147 foo: Union[str, Dict[str, Any]] 1KLMNOP

148 

149 fields = get_model_fields(Model) 1KLMNOP

150 assert not is_scalar_field(fields[0]) 1KLMNOP

151 

152 

153def test_get_model_fields_cached(): 1abcdef

154 class Model(BaseModel): 1stuvwx

155 foo: str 1stuvwx

156 

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

163 

164 assert non_cached_fields is not non_cached_fields2 1stuvwx

165 assert cached_fields is cached_fields2 1stuvwx