Skip to content

Commit 8a37325

Browse files
committed
feat chaotic: use pydantic in jsonschema
commit_hash:aef03af9688103b900014af98aa4c9e47b9ec34d
1 parent 727d2e3 commit 8a37325

29 files changed

+408
-304
lines changed

.mapping.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@
191191
"chaotic/chaotic/cpp_names.py":"taxi/uservices/userver/chaotic/chaotic/cpp_names.py",
192192
"chaotic/chaotic/error.py":"taxi/uservices/userver/chaotic/chaotic/error.py",
193193
"chaotic/chaotic/front/__init__.py":"taxi/uservices/userver/chaotic/chaotic/front/__init__.py",
194+
"chaotic/chaotic/front/base_model.py":"taxi/uservices/userver/chaotic/chaotic/front/base_model.py",
194195
"chaotic/chaotic/front/parser.py":"taxi/uservices/userver/chaotic/chaotic/front/parser.py",
195196
"chaotic/chaotic/front/ref.py":"taxi/uservices/userver/chaotic/chaotic/front/ref.py",
196197
"chaotic/chaotic/front/ref_resolver.py":"taxi/uservices/userver/chaotic/chaotic/front/ref_resolver.py",

chaotic-openapi/chaotic_openapi/front/base_model.py

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,8 @@
1-
from typing import Any
2-
31
import pydantic
42

3+
from chaotic.front import base_model
54

6-
class BaseModel(pydantic.BaseModel):
7-
model_config = pydantic.ConfigDict(extra='allow')
8-
_model_userver_tags: list[str] = []
9-
10-
def model_post_init(self, context: Any) -> None:
11-
super().model_post_init(context)
12-
13-
if not self.__pydantic_extra__:
14-
return
15-
for field in self.__pydantic_extra__:
16-
if field.startswith('x-taxi-py3'):
17-
continue
18-
if field.startswith('x-taxi-go-'):
19-
continue
20-
if field.startswith('x-taxi-') or field.startswith('x-usrv-'):
21-
assert field in self._model_userver_tags, f'Field {field} is not allowed in this context'
22-
continue
23-
24-
if field.startswith('x-'):
25-
continue
26-
raise Exception(f'Unknown field "{field}"')
5+
BaseModel = base_model.BaseModel
276

287

298
class XMiddlewares(pydantic.BaseModel):
Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,10 @@
1-
import pydantic
1+
from chaotic.front import parser
22

3-
import chaotic.error
4-
5-
ERROR_MESSAGES = {
6-
'extra_forbidden': 'Extra fields are forbidden ({input})',
7-
'missing': 'Required field "{field}" is missing',
8-
'string_type': 'String type is expected, {input} is found',
9-
'bool_type': 'Boolean type is expected, {input} is found',
10-
'int_type': 'Integer type is expected, {input} is found',
11-
}
3+
ERROR_MESSAGES = parser.ERROR_MESSAGES
124

135

146
def missing_field_msg(field: str) -> str:
157
return ERROR_MESSAGES['missing'].format(field=field)
168

179

18-
def convert_error(full_filepath: str, schema_type: str, err: pydantic.ValidationError) -> chaotic.error.BaseError:
19-
assert len(err.errors()) >= 1
20-
21-
# show only the first error
22-
error = err.errors()[0]
23-
24-
if len(error['loc']) > 0:
25-
# the last location is the missing field name
26-
field = error['loc'][-1]
27-
else:
28-
field = ''
29-
30-
if error['type'] in ERROR_MESSAGES:
31-
msg = ERROR_MESSAGES[error['type']].format(**error, field=field)
32-
else:
33-
msg = error['msg']
34-
return chaotic.error.BaseError(
35-
full_filepath=full_filepath,
36-
infile_path='.'.join(map(str, error['loc'])),
37-
schema_type=schema_type,
38-
msg=msg,
39-
)
10+
convert_error = parser.convert_error

chaotic-openapi/chaotic_openapi/front/model.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import dataclasses
22
import enum
33
from typing import Any
4+
from typing import Callable
45
from typing import Optional
56
from typing import Union
67

@@ -156,3 +157,45 @@ class Service:
156157
headers: dict[str, Parameter] = dataclasses.field(default_factory=dict)
157158
requestBodies: dict[str, list[RequestBody]] = dataclasses.field(default_factory=dict)
158159
security: dict[str, Security] = dataclasses.field(default_factory=dict)
160+
161+
# For tests only
162+
def visit_all_schemas(
163+
self,
164+
callback: Callable[[types.Schema], None],
165+
) -> None:
166+
def callback2(child: types.Schema, _: types.Schema) -> None:
167+
callback(child)
168+
169+
def clear_response(response):
170+
for body2 in response.content.values():
171+
callback(body2.schema)
172+
body2.schema.visit_children(callback2)
173+
for header in response.headers.values():
174+
callback(header.schema)
175+
header.schema.visit_children(callback2)
176+
177+
for schema in self.schemas.values():
178+
callback(schema)
179+
schema.visit_children(callback2)
180+
for body in self.requestBodies.values():
181+
for b in body:
182+
callback(b.schema)
183+
b.schema.visit_children(callback2)
184+
for response in self.responses.values():
185+
clear_response(response)
186+
for parameter in self.parameters.values():
187+
callback(parameter.schema)
188+
parameter.schema.visit_children(callback2)
189+
190+
for operation in self.operations:
191+
if not isinstance(operation.requestBody, Ref):
192+
for body3 in operation.requestBody:
193+
callback(body3.schema)
194+
body3.schema.visit_children(callback2)
195+
for response2 in operation.responses.values():
196+
if isinstance(response2, Ref):
197+
continue
198+
clear_response(response2)
199+
for parameter in operation.parameters:
200+
callback(parameter.schema)
201+
parameter.schema.visit_children(callback2)

chaotic-openapi/chaotic_openapi/front/openapi.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Header(base_model.BaseModel):
4747
deprecated: bool = False
4848
allowEmptyValue: bool = False
4949

50-
style: Optional[Style] = None
50+
style: Optional[Style] = pydantic.Field(default=None, strict=False)
5151
explode: Optional[bool] = None
5252
allowReserved: bool = False
5353
schema_: Schema = pydantic.Field(alias='schema')
@@ -62,10 +62,10 @@ class MediaType(base_model.BaseModel):
6262
examples: dict[str, Any] = pydantic.Field(default_factory=dict)
6363
# encoding: dict[str, Encoding] = {}
6464

65-
_model_userver_tags: list[str] = [
66-
'x-taxi-non-std-type-reason',
67-
'x-usrv-non-std-type-reason',
68-
]
65+
x_non_std_type_reason: Optional[str] = pydantic.Field(
66+
default=None,
67+
validation_alias=pydantic.AliasChoices('x-taxi-non-std-type-reason', 'x-usrv-non-std-type-reason'),
68+
)
6969

7070

7171
# https://spec.openapis.org/oas/v3.0.0.html#reference-object
@@ -101,13 +101,13 @@ class QueryLogMode(str, enum.Enum):
101101
# https://spec.openapis.org/oas/v3.0.0.html#parameter-object
102102
class Parameter(base_model.BaseModel):
103103
name: str
104-
in_: In = pydantic.Field(alias='in')
104+
in_: In = pydantic.Field(alias='in', strict=False)
105105
description: Optional[str] = None
106106
required: bool = False
107107
deprecated: bool = False
108108
allowEmptyValue: bool = False
109109

110-
style: Optional[Style] = None
110+
style: Optional[Style] = pydantic.Field(default=None, strict=False)
111111
explode: Optional[bool] = None
112112
allowReserved: bool = False
113113
schema_: Schema = pydantic.Field(alias='schema')
@@ -127,6 +127,7 @@ class Parameter(base_model.BaseModel):
127127
x_query_log_mode: QueryLogMode = pydantic.Field(
128128
default=QueryLogMode.show,
129129
validation_alias=pydantic.AliasChoices('x-taxi-query-log-mode', 'x-usrv-query-log-mode'),
130+
strict=False,
130131
)
131132
x_explode_true_reason: str = pydantic.Field(
132133
default='',
@@ -201,10 +202,10 @@ class OAuthFlows(base_model.BaseModel):
201202

202203
# https://spec.openapis.org/oas/v3.0.0.html#security-scheme-object
203204
class SecurityScheme(base_model.BaseModel):
204-
type: SecurityType
205+
type: SecurityType = pydantic.Field(strict=False)
205206
description: Optional[str] = None
206207
name: Optional[str] = None
207-
in_: Optional[SecurityIn] = pydantic.Field(alias='in', default=None)
208+
in_: Optional[SecurityIn] = pydantic.Field(alias='in', default=None, strict=False)
208209
scheme_: Optional[str] = pydantic.Field(alias='scheme', default=None)
209210
bearerFormat: Optional[str] = None
210211
flows: Optional[OAuthFlows] = None
@@ -273,6 +274,7 @@ class Operation(base_model.BaseModel):
273274
x_query_log_mode: QueryLogMode = pydantic.Field(
274275
default=QueryLogMode.show,
275276
validation_alias=pydantic.AliasChoices('x-taxi-query-log-mode', 'x-usrv-query-log-mode'),
277+
strict=False,
276278
)
277279
x_client_codegen: bool = pydantic.Field(
278280
default=True,

chaotic-openapi/chaotic_openapi/front/parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def parse_schema(self, schema: dict, full_filepath: str, full_vfilepath: str) ->
4343
try:
4444
parsed = parser(**schema)
4545
except pydantic.ValidationError as exc:
46-
raise errors.convert_error(full_filepath, parser.schema_type(), exc) from None
46+
raise errors.convert_error(self._state.full_filepath, '', parser.schema_type(), exc) from None
4747

4848
self._append_schema(parsed)
4949

@@ -636,11 +636,11 @@ def _parse_schema(self, schema: Any, infile_path: str, allow_file=False) -> Unio
636636

637637
if isinstance(schema_ref, types.Ref):
638638
ref_ = types.Ref(
639-
chaotic_parser.SchemaParser._normalize_ref(schema_ref.ref),
639+
ref=chaotic_parser.SchemaParser._normalize_ref(schema_ref.ref),
640640
indirect=schema_ref.indirect,
641641
self_ref=schema_ref.self_ref,
642642
)
643-
ref_._source_location = schema_ref._source_location # type: ignore
643+
ref_.source_location_ = schema_ref.source_location_
644644
return ref_
645645
else:
646646
return schema_ref

chaotic-openapi/chaotic_openapi/front/swagger.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class In(str, enum.Enum):
3131
# https://spec.openapis.org/oas/v2.0.html#parameter-object
3232
class Parameter(base_model.BaseModel):
3333
name: str
34-
in_: In = pydantic.Field(alias='in')
34+
in_: In = pydantic.Field(alias='in', strict=False)
3535
description: str = ''
3636
required: bool = False
3737

@@ -118,11 +118,11 @@ class OAuthFlow(str, enum.Enum):
118118

119119
# https://spec.openapis.org/oas/v2.0.html#security-definitions-object
120120
class SecurityDef(base_model.BaseModel):
121-
type: SecurityType
121+
type: SecurityType = pydantic.Field(strict=False)
122122
description: Optional[str] = None
123123
name: Optional[str] = None
124-
in_: Optional[SecurityIn] = pydantic.Field(alias='in', default=None)
125-
flow: Optional[OAuthFlow] = None
124+
in_: Optional[SecurityIn] = pydantic.Field(alias='in', default=None, strict=False)
125+
flow: Optional[OAuthFlow] = pydantic.Field(default=None, strict=False)
126126
authorizationUrl: Optional[str] = None
127127
tokenUrl: Optional[str] = None
128128
scopes: dict[str, str] = pydantic.Field(default_factory=dict)

chaotic-openapi/tests/back/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
@pytest.fixture
77
def translate_single_schema():
8+
def clear_source_location(schema):
9+
schema.source_location_ = None
10+
811
def func(schema):
912
parser = front_parser.Parser('test')
1013
parser.parse_schema(schema, '<inline>', '<inline>')
@@ -17,6 +20,7 @@ def func(schema):
1720
include_dirs=[],
1821
middleware_plugins=[],
1922
)
23+
service.visit_all_schemas(clear_source_location)
2024
return tr.spec()
2125

2226
return func

chaotic-openapi/tests/back/test_parameters.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def test_parameters_schemas_ref(translate_single_schema):
182182
ref='<inline>#/components/schemas/Parameter',
183183
indirect=False,
184184
self_ref=False,
185-
schema=chaotic_types.Integer(
185+
schema_=chaotic_types.Integer(
186186
type='integer',
187187
),
188188
),
@@ -217,7 +217,7 @@ def test_parameters_schemas_ref(translate_single_schema):
217217
ref='<inline>#/components/schemas/Parameter',
218218
indirect=False,
219219
self_ref=False,
220-
schema=chaotic_types.Integer(
220+
schema_=chaotic_types.Integer(
221221
type='integer',
222222
default=None,
223223
nullable=False,

chaotic-openapi/tests/front/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44

55
@pytest.fixture
66
def simple_parser():
7+
def clear_source_location(schema):
8+
schema.source_location_ = None
9+
710
def _simple_parser(schema):
811
parser = front_parser.Parser('test')
912
parser.parse_schema(schema, '<inline>', '<inline>')
13+
parser.service().visit_all_schemas(clear_source_location)
1014
return parser.service()
1115

1216
return _simple_parser

0 commit comments

Comments
 (0)