Schema-First 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {schema_first-0.3.0/src/Schema_First.egg-info → schema_first-0.4.0}/PKG-INFO +2 -2
- {schema_first-0.3.0 → schema_first-0.4.0}/pyproject.toml +2 -6
- {schema_first-0.3.0 → schema_first-0.4.0/src/Schema_First.egg-info}/PKG-INFO +2 -2
- {schema_first-0.3.0 → schema_first-0.4.0}/src/Schema_First.egg-info/requires.txt +1 -1
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/__init__.py +2 -1
- schema_first-0.4.0/src/schema_first/openapi/schemas/v3_1_1/schema_object_schema.py +120 -0
- schema_first-0.4.0/src/schema_first/specification/__init__.py +118 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/tests/test_specification.py +4 -4
- {schema_first-0.3.0 → schema_first-0.4.0}/tests/test_validator.py +8 -8
- schema_first-0.3.0/src/schema_first/openapi/schemas/v3_1_1/schema_object_schema.py +0 -17
- schema_first-0.3.0/src/schema_first/specification/__init__.py +0 -47
- {schema_first-0.3.0 → schema_first-0.4.0}/LICENSE +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/README.md +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/setup.cfg +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/Schema_First.egg-info/SOURCES.txt +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/Schema_First.egg-info/dependency_links.txt +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/Schema_First.egg-info/top_level.txt +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/__init__.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/exceptions.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/loaders/__init__.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/loaders/exc.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/loaders/yaml_loader.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/exc.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/__init__.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/base.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/constants.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/fields.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/components_object_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/contact_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/info_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/license_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/media_type_object_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/operation_object_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/path_item_object_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/reference_object_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/request_body_object_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/responses_object_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/root_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/server_schema.py +0 -0
- {schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/server_variable_object_schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Schema-First
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: OpenAPI specification validator and converter to Marshmallow schemas.
|
|
5
5
|
Author-email: Konstantin Fadeev <fadeev@legalact.pro>
|
|
6
6
|
License: MIT License
|
|
@@ -37,7 +37,7 @@ Requires-Dist: marshmallow>=4.0.0
|
|
|
37
37
|
Requires-Dist: PyYAML>=6.0.2
|
|
38
38
|
Provides-Extra: dev
|
|
39
39
|
Requires-Dist: bandit==1.8.6; extra == "dev"
|
|
40
|
-
Requires-Dist: build==1.
|
|
40
|
+
Requires-Dist: build==1.3.0; extra == "dev"
|
|
41
41
|
Requires-Dist: openapi-spec-validator>=0.5.0; extra == "dev"
|
|
42
42
|
Requires-Dist: pre-commit==4.2.0; extra == "dev"
|
|
43
43
|
Requires-Dist: pytest==8.4.1; extra == "dev"
|
|
@@ -20,12 +20,12 @@ license = {file = "LICENSE"}
|
|
|
20
20
|
name = "Schema-First"
|
|
21
21
|
readme = "README.md"
|
|
22
22
|
requires-python = ">=3.14"
|
|
23
|
-
version = "0.
|
|
23
|
+
version = "0.4.0"
|
|
24
24
|
|
|
25
25
|
[project.optional-dependencies]
|
|
26
26
|
dev = [
|
|
27
27
|
"bandit==1.8.6",
|
|
28
|
-
"build==1.
|
|
28
|
+
"build==1.3.0",
|
|
29
29
|
'openapi-spec-validator>=0.5.0',
|
|
30
30
|
"pre-commit==4.2.0",
|
|
31
31
|
"pytest==8.4.1",
|
|
@@ -53,10 +53,6 @@ target-version = ['py313']
|
|
|
53
53
|
profile = "google"
|
|
54
54
|
src_paths = ["src", "tests"]
|
|
55
55
|
|
|
56
|
-
[tool.pycln]
|
|
57
|
-
all = true
|
|
58
|
-
silence = true
|
|
59
|
-
|
|
60
56
|
[tool.setuptools.packages.find]
|
|
61
57
|
include = ["schema_first*"]
|
|
62
58
|
where = ["src"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Schema-First
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: OpenAPI specification validator and converter to Marshmallow schemas.
|
|
5
5
|
Author-email: Konstantin Fadeev <fadeev@legalact.pro>
|
|
6
6
|
License: MIT License
|
|
@@ -37,7 +37,7 @@ Requires-Dist: marshmallow>=4.0.0
|
|
|
37
37
|
Requires-Dist: PyYAML>=6.0.2
|
|
38
38
|
Provides-Extra: dev
|
|
39
39
|
Requires-Dist: bandit==1.8.6; extra == "dev"
|
|
40
|
-
Requires-Dist: build==1.
|
|
40
|
+
Requires-Dist: build==1.3.0; extra == "dev"
|
|
41
41
|
Requires-Dist: openapi-spec-validator>=0.5.0; extra == "dev"
|
|
42
42
|
Requires-Dist: pre-commit==4.2.0; extra == "dev"
|
|
43
43
|
Requires-Dist: pytest==8.4.1; extra == "dev"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from pprint import pformat
|
|
2
3
|
|
|
3
4
|
from marshmallow import ValidationError
|
|
4
5
|
|
|
@@ -16,4 +17,4 @@ class OpenAPI:
|
|
|
16
17
|
try:
|
|
17
18
|
return RootSchema().load(self.raw_spec)
|
|
18
19
|
except ValidationError as e:
|
|
19
|
-
raise OpenAPIValidationError(e)
|
|
20
|
+
raise OpenAPIValidationError(f'\n{pformat(e.messages)}')
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from marshmallow import fields
|
|
4
|
+
from marshmallow import validate
|
|
5
|
+
from marshmallow import validates
|
|
6
|
+
from marshmallow import validates_schema
|
|
7
|
+
from marshmallow import ValidationError
|
|
8
|
+
|
|
9
|
+
from ..base import BaseSchema
|
|
10
|
+
from ..constants import FORMATS
|
|
11
|
+
from ..constants import TYPES
|
|
12
|
+
from ..fields import DESCRIPTION_FIELD
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FormatBinarySchema(BaseSchema):
|
|
16
|
+
default = fields.String()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FormatEmailSchema(BaseSchema):
|
|
20
|
+
default = fields.Email()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FormatDateSchema(BaseSchema):
|
|
24
|
+
default = fields.Date()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FormatDateTimeSchema(BaseSchema):
|
|
28
|
+
default = fields.AwareDateTime(format='iso', default_timezone=None)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FormatIPv4Schema(BaseSchema):
|
|
32
|
+
default = fields.IPv4()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FormatIPv6Schema(BaseSchema):
|
|
36
|
+
default = fields.IPv6()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FormatTimeSchema(BaseSchema):
|
|
40
|
+
default = fields.Time(format='iso')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FormatURISchema(BaseSchema):
|
|
44
|
+
default = fields.URL()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FormatUUIDSchema(BaseSchema):
|
|
48
|
+
default = fields.UUID()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
format_schemas = {
|
|
52
|
+
'binary': FormatBinarySchema,
|
|
53
|
+
'date': FormatDateSchema,
|
|
54
|
+
'date-time': FormatDateTimeSchema,
|
|
55
|
+
'email': FormatEmailSchema,
|
|
56
|
+
'ipv4': FormatIPv4Schema,
|
|
57
|
+
'ipv6': FormatIPv6Schema,
|
|
58
|
+
'time': FormatTimeSchema,
|
|
59
|
+
'uri': FormatURISchema,
|
|
60
|
+
'uuid': FormatUUIDSchema,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class StringFieldSchema(BaseSchema):
|
|
65
|
+
format = fields.String(validate=validate.OneOf(FORMATS))
|
|
66
|
+
minLength = fields.Integer(validate=[validate.Range(min=0)])
|
|
67
|
+
maxLength = fields.Integer(validate=[validate.Range(min=0)])
|
|
68
|
+
pattern = fields.String()
|
|
69
|
+
|
|
70
|
+
@validates('pattern')
|
|
71
|
+
def validate_pattern(self, value: str, data_key: str) -> None:
|
|
72
|
+
try:
|
|
73
|
+
re.compile(value)
|
|
74
|
+
except re.PatternError as e:
|
|
75
|
+
raise ValidationError(f"Pattern <{value}> is error <{repr(e)}>.")
|
|
76
|
+
|
|
77
|
+
@validates_schema
|
|
78
|
+
def validate_default(self, data, **kwargs):
|
|
79
|
+
if 'default' in data and 'pattern' in data:
|
|
80
|
+
result = re.match(data['pattern'], data['default'])
|
|
81
|
+
if result is None:
|
|
82
|
+
raise ValidationError(f'<{data["default"]}> does not match <{data["pattern"]}>')
|
|
83
|
+
|
|
84
|
+
@validates_schema
|
|
85
|
+
def validate_default_via_format(self, data, **kwargs):
|
|
86
|
+
if 'default' in data and 'format' in data:
|
|
87
|
+
error = format_schemas[data['format']]().validate({'default': data['default']})
|
|
88
|
+
if error:
|
|
89
|
+
raise ValidationError(str(error))
|
|
90
|
+
|
|
91
|
+
@validates_schema
|
|
92
|
+
def validate_length(self, data, **kwargs):
|
|
93
|
+
if 'minLength' in data and 'maxLength' in data:
|
|
94
|
+
if data['minLength'] > data['maxLength']:
|
|
95
|
+
raise ValidationError(
|
|
96
|
+
f'<{data['minLength']}> cannot be greater than <{data['maxLength']}>'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class SchemaObjectSchema(StringFieldSchema):
|
|
101
|
+
type = fields.String(required=True, validate=validate.OneOf(TYPES))
|
|
102
|
+
default = fields.String()
|
|
103
|
+
description = DESCRIPTION_FIELD
|
|
104
|
+
nullable = fields.Boolean()
|
|
105
|
+
required = fields.List(fields.String())
|
|
106
|
+
additionalProperties = fields.Boolean()
|
|
107
|
+
|
|
108
|
+
properties = fields.Dict(
|
|
109
|
+
keys=fields.String(required=True, validate=validate.Length(min=1)),
|
|
110
|
+
values=fields.Nested(lambda: SchemaObjectSchema()),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@validates_schema
|
|
114
|
+
def validate_required(self, data, **kwargs):
|
|
115
|
+
if 'required' in data:
|
|
116
|
+
for field_name in data['required']:
|
|
117
|
+
if field_name not in data['properties']:
|
|
118
|
+
raise ValidationError(
|
|
119
|
+
f'Required field <{field_name}> not in <data["properties"]>'
|
|
120
|
+
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from marshmallow import fields
|
|
6
|
+
from marshmallow import INCLUDE
|
|
7
|
+
from marshmallow import RAISE
|
|
8
|
+
from marshmallow import Schema
|
|
9
|
+
from marshmallow import validate
|
|
10
|
+
|
|
11
|
+
from ..openapi import OpenAPI
|
|
12
|
+
|
|
13
|
+
FIELDS_VIA_TYPES = {
|
|
14
|
+
'boolean': fields.Boolean,
|
|
15
|
+
'number': fields.Float,
|
|
16
|
+
'string': fields.String,
|
|
17
|
+
'integer': fields.Integer,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
FIELDS_VIA_FORMATS = {
|
|
21
|
+
'uuid': fields.UUID,
|
|
22
|
+
'date-time': fields.AwareDateTime,
|
|
23
|
+
'date': fields.Date,
|
|
24
|
+
'time': fields.Time,
|
|
25
|
+
'email': fields.Email,
|
|
26
|
+
'ipv4': fields.IPv4,
|
|
27
|
+
'ipv6': fields.IPv6,
|
|
28
|
+
'uri': fields.Url,
|
|
29
|
+
'binary': fields.String,
|
|
30
|
+
'string': fields.String,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Specification:
|
|
35
|
+
def __init__(self, spec_file: Path | str):
|
|
36
|
+
self.openapi = OpenAPI(spec_file)
|
|
37
|
+
self.reassembly_spec = None
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _make_field_validators(schema: dict) -> list[validate.Validator]:
|
|
41
|
+
validators = []
|
|
42
|
+
|
|
43
|
+
if schema['type'] in ['string']:
|
|
44
|
+
validators.append(
|
|
45
|
+
validate.Length(min=schema.get('minLength'), max=schema.get('maxLength'))
|
|
46
|
+
)
|
|
47
|
+
if schema.get('pattern'):
|
|
48
|
+
validators.append(validate.Regexp(schema['pattern']))
|
|
49
|
+
|
|
50
|
+
if schema['type'] in ['integer', 'number']:
|
|
51
|
+
validators.append(validate.Range(min=schema.get('minimum'), max=schema.get('maximum')))
|
|
52
|
+
|
|
53
|
+
required_values = schema.get('enum')
|
|
54
|
+
if required_values:
|
|
55
|
+
validators.append(validate.OneOf(required_values))
|
|
56
|
+
|
|
57
|
+
return validators
|
|
58
|
+
|
|
59
|
+
def _convert_string_field(self, field_schema: dict, required: bool = False):
|
|
60
|
+
format_string = field_schema.get('format', 'string')
|
|
61
|
+
try:
|
|
62
|
+
schema = FIELDS_VIA_FORMATS[format_string]
|
|
63
|
+
except KeyError:
|
|
64
|
+
raise NotImplementedError(
|
|
65
|
+
f'Schema <{field_schema}> for format <{format_string}> not implemented.'
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
initialized_schema = schema()
|
|
69
|
+
initialized_schema.validate = self._make_field_validators(field_schema)
|
|
70
|
+
initialized_schema.allow_none = field_schema.get('nullable', False)
|
|
71
|
+
initialized_schema.required = required
|
|
72
|
+
return initialized_schema
|
|
73
|
+
|
|
74
|
+
def _convert_field_any_type(self, field_schema: dict, required: bool = False) -> dict:
|
|
75
|
+
if field_schema['type'] == 'string':
|
|
76
|
+
converted_field_schema = self._convert_string_field(field_schema, required=required)
|
|
77
|
+
else:
|
|
78
|
+
raise NotImplementedError(field_schema)
|
|
79
|
+
|
|
80
|
+
return converted_field_schema
|
|
81
|
+
|
|
82
|
+
def _convert_from_openapi_to_marshmallow_schema(self, open_api_schema: dict) -> type[Schema]:
|
|
83
|
+
marshmallow_schema = {}
|
|
84
|
+
for field_name, field_schema in open_api_schema['properties'].items():
|
|
85
|
+
required_fields = open_api_schema.get('required', [])
|
|
86
|
+
|
|
87
|
+
if field_name in required_fields:
|
|
88
|
+
is_required = True
|
|
89
|
+
else:
|
|
90
|
+
is_required = False
|
|
91
|
+
|
|
92
|
+
marshmallow_schema[field_name] = self._convert_field_any_type(
|
|
93
|
+
field_schema, required=is_required
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
additionalProperties = open_api_schema.get('additionalProperties', True)
|
|
97
|
+
if additionalProperties is False:
|
|
98
|
+
marshmallow_schema['unknown'] = RAISE
|
|
99
|
+
else:
|
|
100
|
+
marshmallow_schema['unknown'] = INCLUDE
|
|
101
|
+
|
|
102
|
+
return Schema.from_dict(marshmallow_schema)
|
|
103
|
+
|
|
104
|
+
def _reassembly_of_schemas(self, obj: Any) -> Any:
|
|
105
|
+
if isinstance(obj, dict):
|
|
106
|
+
for k, v in obj.items():
|
|
107
|
+
if k == 'schema':
|
|
108
|
+
obj[k] = self._convert_from_openapi_to_marshmallow_schema(v)
|
|
109
|
+
else:
|
|
110
|
+
self._reassembly_of_schemas(v)
|
|
111
|
+
|
|
112
|
+
def load(self) -> 'Specification':
|
|
113
|
+
self.openapi.load()
|
|
114
|
+
self.reassembly_spec = deepcopy(self.openapi.raw_spec)
|
|
115
|
+
|
|
116
|
+
self._reassembly_of_schemas(self.reassembly_spec)
|
|
117
|
+
|
|
118
|
+
return self
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from marshmallow import fields
|
|
2
|
+
from marshmallow import Schema
|
|
2
3
|
|
|
3
4
|
from src.schema_first.openapi import OpenAPI
|
|
4
5
|
from src.schema_first.specification import Specification
|
|
5
6
|
from tests.utils import get_schema_from_request
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
def
|
|
9
|
-
spec_file = fx_spec_as_file(
|
|
9
|
+
def test_specification__required(fx_spec_required, fx_spec_as_file):
|
|
10
|
+
spec_file = fx_spec_as_file(fx_spec_required)
|
|
10
11
|
spec = Specification(spec_file)
|
|
11
12
|
|
|
12
13
|
assert isinstance(spec.openapi, OpenAPI)
|
|
@@ -29,5 +30,4 @@ def test_specification__full(fx_spec_full, fx_spec_as_file):
|
|
|
29
30
|
spec.load()
|
|
30
31
|
|
|
31
32
|
request_schema = get_schema_from_request(spec.reassembly_spec, '/endpoint', '200')
|
|
32
|
-
assert isinstance(request_schema()
|
|
33
|
-
assert request_schema().load({'message': 'Valid string'})
|
|
33
|
+
assert isinstance(request_schema(), Schema)
|
|
@@ -4,11 +4,11 @@ from src.schema_first.openapi import OpenAPI
|
|
|
4
4
|
from src.schema_first.openapi import OpenAPIValidationError
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def
|
|
8
|
-
spec_file = fx_spec_as_file(
|
|
7
|
+
def test_validator_required(fx_spec_required, fx_spec_as_file):
|
|
8
|
+
spec_file = fx_spec_as_file(fx_spec_required)
|
|
9
9
|
open_api_spec = OpenAPI(spec_file)
|
|
10
10
|
open_api_spec.load()
|
|
11
|
-
assert open_api_spec.raw_spec ==
|
|
11
|
+
assert open_api_spec.raw_spec == fx_spec_required
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def test_validator__full(fx_spec_full, fx_spec_as_file):
|
|
@@ -18,8 +18,8 @@ def test_validator__full(fx_spec_full, fx_spec_as_file):
|
|
|
18
18
|
assert open_api_spec.raw_spec == fx_spec_full
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def
|
|
22
|
-
spec_file = fx_spec_as_file(
|
|
21
|
+
def test_validator__required__external_validator(fx_spec_required, fx_spec_as_file):
|
|
22
|
+
spec_file = fx_spec_as_file(fx_spec_required, external_validator=True)
|
|
23
23
|
open_api_spec = OpenAPI(spec_file)
|
|
24
24
|
open_api_spec.load()
|
|
25
25
|
|
|
@@ -30,10 +30,10 @@ def test_validator__full__external_validator(fx_spec_full, fx_spec_as_file):
|
|
|
30
30
|
open_api_spec.load()
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def test_validator__wrong_field_name(
|
|
34
|
-
|
|
33
|
+
def test_validator__wrong_field_name(fx_spec_required, fx_spec_as_file):
|
|
34
|
+
fx_spec_required['wrong_field_name'] = 'wrong'
|
|
35
35
|
|
|
36
|
-
spec_file = fx_spec_as_file(
|
|
36
|
+
spec_file = fx_spec_as_file(fx_spec_required)
|
|
37
37
|
|
|
38
38
|
open_api_spec = OpenAPI(spec_file)
|
|
39
39
|
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from marshmallow import fields
|
|
2
|
-
from marshmallow import validate
|
|
3
|
-
|
|
4
|
-
from ..base import BaseSchema
|
|
5
|
-
from ..constants import FORMATS
|
|
6
|
-
from ..constants import TYPES
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class SchemaObjectSchema(BaseSchema):
|
|
10
|
-
type = fields.String(required=True, validate=validate.OneOf(TYPES))
|
|
11
|
-
|
|
12
|
-
format = fields.String(validate=validate.OneOf(FORMATS))
|
|
13
|
-
pattern = fields.String()
|
|
14
|
-
|
|
15
|
-
properties = fields.Dict(
|
|
16
|
-
keys=fields.String(required=True), values=fields.Nested(lambda: SchemaObjectSchema())
|
|
17
|
-
)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from copy import deepcopy
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from marshmallow import fields
|
|
6
|
-
from marshmallow import Schema
|
|
7
|
-
|
|
8
|
-
from ..openapi import OpenAPI
|
|
9
|
-
|
|
10
|
-
FIELDS_VIA_TYPES = {
|
|
11
|
-
'boolean': fields.Boolean,
|
|
12
|
-
'number': fields.Float,
|
|
13
|
-
'string': fields.String,
|
|
14
|
-
'integer': fields.Integer,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Specification:
|
|
19
|
-
def __init__(self, spec_file: Path | str):
|
|
20
|
-
self.openapi = OpenAPI(spec_file)
|
|
21
|
-
self.reassembly_spec = None
|
|
22
|
-
|
|
23
|
-
def _convert_from_openapi_to_marshmallow_schema(self, open_api_schema: dict) -> type[Schema]:
|
|
24
|
-
if open_api_schema['type'] == 'object':
|
|
25
|
-
marshmallow_schema = {}
|
|
26
|
-
for field_name, field in open_api_schema['properties'].items():
|
|
27
|
-
marshmallow_schema[field_name] = FIELDS_VIA_TYPES[field['type']]()
|
|
28
|
-
else:
|
|
29
|
-
raise NotImplementedError(open_api_schema)
|
|
30
|
-
|
|
31
|
-
return Schema.from_dict(marshmallow_schema)
|
|
32
|
-
|
|
33
|
-
def _reassembly_of_schemas(self, obj: Any) -> Any:
|
|
34
|
-
if isinstance(obj, dict):
|
|
35
|
-
for k, v in obj.items():
|
|
36
|
-
if k == 'schema':
|
|
37
|
-
obj[k] = self._convert_from_openapi_to_marshmallow_schema(v)
|
|
38
|
-
else:
|
|
39
|
-
self._reassembly_of_schemas(v)
|
|
40
|
-
|
|
41
|
-
def load(self) -> 'Specification':
|
|
42
|
-
self.openapi.load()
|
|
43
|
-
self.reassembly_spec = deepcopy(self.openapi.raw_spec)
|
|
44
|
-
|
|
45
|
-
self._reassembly_of_schemas(self.reassembly_spec)
|
|
46
|
-
|
|
47
|
-
return self
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/contact_schema.py
RENAMED
|
File without changes
|
{schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/info_schema.py
RENAMED
|
File without changes
|
{schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/license_schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/root_schema.py
RENAMED
|
File without changes
|
{schema_first-0.3.0 → schema_first-0.4.0}/src/schema_first/openapi/schemas/v3_1_1/server_schema.py
RENAMED
|
File without changes
|
|
File without changes
|