Coverage for tests/test_filter_pydantic_sub_model_pv2.py: 100%

46 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-08 03:53 +0000

1from typing import Optional 1abcde

2 

3import pytest 1abcde

4from dirty_equals import HasRepr, IsDict, IsOneOf 1abcde

5from fastapi import Depends, FastAPI 1abcde

6from fastapi.exceptions import ResponseValidationError 1abcde

7from fastapi.testclient import TestClient 1abcde

8 

9from .utils import needs_pydanticv2 1abcde

10 

11 

12@pytest.fixture(name="client") 1abcde

13def get_client(): 1abcde

14 from pydantic import BaseModel, ValidationInfo, field_validator 1abcde

15 

16 app = FastAPI() 1abcde

17 

18 class ModelB(BaseModel): 1abcde

19 username: str 1abcde

20 

21 class ModelC(ModelB): 1abcde

22 password: str 1abcde

23 

24 class ModelA(BaseModel): 1abcde

25 name: str 1abcde

26 description: Optional[str] = None 1abcde

27 foo: ModelB 1abcde

28 

29 @field_validator("name") 1abcde

30 def lower_username(cls, name: str, info: ValidationInfo): 1abcde

31 if not name.endswith("A"): 1abcde

32 raise ValueError("name must end in A") 1abcde

33 return name 1abcde

34 

35 async def get_model_c() -> ModelC: 1abcde

36 return ModelC(username="test-user", password="test-password") 1abcde

37 

38 @app.get("/model/{name}", response_model=ModelA) 1abcde

39 async def get_model_a(name: str, model_c=Depends(get_model_c)): 1abcde

40 return {"name": name, "description": "model-a-desc", "foo": model_c} 1abcde

41 

42 client = TestClient(app) 1abcde

43 return client 1abcde

44 

45 

46@needs_pydanticv2 1abcde

47def test_filter_sub_model(client: TestClient): 1abcde

48 response = client.get("/model/modelA") 1abcde

49 assert response.status_code == 200, response.text 1abcde

50 assert response.json() == { 1abcde

51 "name": "modelA", 

52 "description": "model-a-desc", 

53 "foo": {"username": "test-user"}, 

54 } 

55 

56 

57@needs_pydanticv2 1abcde

58def test_validator_is_cloned(client: TestClient): 1abcde

59 with pytest.raises(ResponseValidationError) as err: 1abcde

60 client.get("/model/modelX") 1abcde

61 assert err.value.errors() == [ 1abcde

62 IsDict( 

63 { 

64 "type": "value_error", 

65 "loc": ("response", "name"), 

66 "msg": "Value error, name must end in A", 

67 "input": "modelX", 

68 "ctx": {"error": HasRepr("ValueError('name must end in A')")}, 

69 } 

70 ) 

71 | IsDict( 

72 # TODO remove when deprecating Pydantic v1 

73 { 

74 "loc": ("response", "name"), 

75 "msg": "name must end in A", 

76 "type": "value_error", 

77 } 

78 ) 

79 ] 

80 

81 

82@needs_pydanticv2 1abcde

83def test_openapi_schema(client: TestClient): 1abcde

84 response = client.get("/openapi.json") 1abcde

85 assert response.status_code == 200, response.text 1abcde

86 assert response.json() == { 1abcde

87 "openapi": "3.1.0", 

88 "info": {"title": "FastAPI", "version": "0.1.0"}, 

89 "paths": { 

90 "/model/{name}": { 

91 "get": { 

92 "summary": "Get Model A", 

93 "operationId": "get_model_a_model__name__get", 

94 "parameters": [ 

95 { 

96 "required": True, 

97 "schema": {"title": "Name", "type": "string"}, 

98 "name": "name", 

99 "in": "path", 

100 } 

101 ], 

102 "responses": { 

103 "200": { 

104 "description": "Successful Response", 

105 "content": { 

106 "application/json": { 

107 "schema": {"$ref": "#/components/schemas/ModelA"} 

108 } 

109 }, 

110 }, 

111 "422": { 

112 "description": "Validation Error", 

113 "content": { 

114 "application/json": { 

115 "schema": { 

116 "$ref": "#/components/schemas/HTTPValidationError" 

117 } 

118 } 

119 }, 

120 }, 

121 }, 

122 } 

123 } 

124 }, 

125 "components": { 

126 "schemas": { 

127 "HTTPValidationError": { 

128 "title": "HTTPValidationError", 

129 "type": "object", 

130 "properties": { 

131 "detail": { 

132 "title": "Detail", 

133 "type": "array", 

134 "items": {"$ref": "#/components/schemas/ValidationError"}, 

135 } 

136 }, 

137 }, 

138 "ModelA": { 

139 "title": "ModelA", 

140 "required": IsOneOf( 

141 ["name", "description", "foo"], 

142 # TODO remove when deprecating Pydantic v1 

143 ["name", "foo"], 

144 ), 

145 "type": "object", 

146 "properties": { 

147 "name": {"title": "Name", "type": "string"}, 

148 "description": IsDict( 

149 { 

150 "title": "Description", 

151 "anyOf": [{"type": "string"}, {"type": "null"}], 

152 } 

153 ) 

154 | 

155 # TODO remove when deprecating Pydantic v1 

156 IsDict({"title": "Description", "type": "string"}), 

157 "foo": {"$ref": "#/components/schemas/ModelB"}, 

158 }, 

159 }, 

160 "ModelB": { 

161 "title": "ModelB", 

162 "required": ["username"], 

163 "type": "object", 

164 "properties": {"username": {"title": "Username", "type": "string"}}, 

165 }, 

166 "ValidationError": { 

167 "title": "ValidationError", 

168 "required": ["loc", "msg", "type"], 

169 "type": "object", 

170 "properties": { 

171 "loc": { 

172 "title": "Location", 

173 "type": "array", 

174 "items": { 

175 "anyOf": [{"type": "string"}, {"type": "integer"}] 

176 }, 

177 }, 

178 "msg": {"title": "Message", "type": "string"}, 

179 "type": {"title": "Error Type", "type": "string"}, 

180 }, 

181 }, 

182 } 

183 }, 

184 }