Schema-First 0.14.0__tar.gz → 0.14.2__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.
Files changed (46) hide show
  1. {schema_first-0.14.0 → schema_first-0.14.2}/PKG-INFO +2 -2
  2. {schema_first-0.14.0 → schema_first-0.14.2}/pyproject.toml +2 -2
  3. {schema_first-0.14.0 → schema_first-0.14.2}/src/Schema_First.egg-info/PKG-INFO +2 -2
  4. {schema_first-0.14.0 → schema_first-0.14.2}/src/Schema_First.egg-info/SOURCES.txt +1 -0
  5. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/loaders/yaml_loader.py +2 -2
  6. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/fields.py +1 -1
  7. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/media_type_object_schema.py +1 -2
  8. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/openapi_object_schema.py +1 -1
  9. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/specification/__init__.py +15 -9
  10. schema_first-0.14.2/src/schema_first/specification/spilli_api.py +65 -0
  11. {schema_first-0.14.0 → schema_first-0.14.2}/tests/test_specification.py +4 -7
  12. {schema_first-0.14.0 → schema_first-0.14.2}/README.md +0 -0
  13. {schema_first-0.14.0 → schema_first-0.14.2}/setup.cfg +0 -0
  14. {schema_first-0.14.0 → schema_first-0.14.2}/src/Schema_First.egg-info/dependency_links.txt +0 -0
  15. {schema_first-0.14.0 → schema_first-0.14.2}/src/Schema_First.egg-info/requires.txt +0 -0
  16. {schema_first-0.14.0 → schema_first-0.14.2}/src/Schema_First.egg-info/top_level.txt +0 -0
  17. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/__init__.py +0 -0
  18. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/exceptions.py +0 -0
  19. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/loaders/__init__.py +0 -0
  20. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/loaders/exc.py +0 -0
  21. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/__init__.py +0 -0
  22. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/exc.py +0 -0
  23. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/__init__.py +0 -0
  24. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/base.py +0 -0
  25. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/constants.py +0 -0
  26. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/custom_fields.py +0 -0
  27. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/components_object_schema.py +0 -0
  28. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/contact_object_schema.py +0 -0
  29. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/example_object_schema.py +0 -0
  30. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/external_docs_object_schema.py +0 -0
  31. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/header_object_schema.py +0 -0
  32. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/info_object_schema.py +0 -0
  33. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/license_object_schema.py +0 -0
  34. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/operation_object_schema.py +0 -0
  35. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/parameter_object_schema.py +0 -0
  36. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/path_item_object_schema.py +0 -0
  37. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/reference_object_schema.py +0 -0
  38. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/request_body_object_schema.py +0 -0
  39. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/responses_object_schema.py +0 -0
  40. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/schema_object_schema.py +0 -0
  41. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/security_scheme_object_schema.py +0 -0
  42. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/server_object_schema.py +0 -0
  43. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/server_variable_object_schema.py +0 -0
  44. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/v3_2/tag_object_schema.py +0 -0
  45. {schema_first-0.14.0 → schema_first-0.14.2}/src/schema_first/openapi/schemas/validators.py +0 -0
  46. {schema_first-0.14.0 → schema_first-0.14.2}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Schema-First
3
- Version: 0.14.0
3
+ Version: 0.14.2
4
4
  Summary: OpenAPI specification validator and converter to Marshmallow schemas.
5
5
  Author-email: Konstantin Fadeev <jredkiy@yandex.ru>
6
6
  License-Expression: LGPL-2.1-only
@@ -8,7 +8,7 @@ Project-URL: changelog, https://github.com/flask-pro/schema-first/blob/master/CH
8
8
  Project-URL: repository, https://github.com/flask-pro/schema-first
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python :: 3
11
- Requires-Python: >=3.13
11
+ Requires-Python: >=3.14
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: marshmallow>=4.3.0
14
14
  Requires-Dist: PyYAML>=6.0.3
@@ -19,8 +19,8 @@ license = "LGPL-2.1-only"
19
19
  license-files = ["LICENSE.txt"]
20
20
  name = "Schema-First"
21
21
  readme = "README.md"
22
- requires-python = ">=3.13"
23
- version = "0.14.0"
22
+ requires-python = ">=3.14"
23
+ version = "0.14.2"
24
24
 
25
25
  [project.optional-dependencies]
26
26
  dev = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Schema-First
3
- Version: 0.14.0
3
+ Version: 0.14.2
4
4
  Summary: OpenAPI specification validator and converter to Marshmallow schemas.
5
5
  Author-email: Konstantin Fadeev <jredkiy@yandex.ru>
6
6
  License-Expression: LGPL-2.1-only
@@ -8,7 +8,7 @@ Project-URL: changelog, https://github.com/flask-pro/schema-first/blob/master/CH
8
8
  Project-URL: repository, https://github.com/flask-pro/schema-first
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python :: 3
11
- Requires-Python: >=3.13
11
+ Requires-Python: >=3.14
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: marshmallow>=4.3.0
14
14
  Requires-Dist: PyYAML>=6.0.3
@@ -39,5 +39,6 @@ src/schema_first/openapi/schemas/v3_2/server_object_schema.py
39
39
  src/schema_first/openapi/schemas/v3_2/server_variable_object_schema.py
40
40
  src/schema_first/openapi/schemas/v3_2/tag_object_schema.py
41
41
  src/schema_first/specification/__init__.py
42
+ src/schema_first/specification/spilli_api.py
42
43
  tests/test_specification.py
43
44
  tests/test_validator.py
@@ -58,7 +58,7 @@ class YAMLReader:
58
58
  else:
59
59
  return
60
60
 
61
- def load(self) -> 'YAMLReader':
61
+ def load(self) -> YAMLReader:
62
62
  root_file = self._yaml_to_dict(self.path)
63
63
  self.store[self.root_file_name] = root_file
64
64
  self.search_file(root_file)
@@ -130,7 +130,7 @@ class RefResolver:
130
130
 
131
131
  return obj
132
132
 
133
- def resolving(self) -> 'RefResolver':
133
+ def resolving(self) -> RefResolver:
134
134
  root_file_path = self.yaml_reader.root_file_name
135
135
  root_spec = self.yaml_reader.store[root_file_path]
136
136
  self.resolved_spec = self._resolving_all_refs(root_file_path, root_spec)
@@ -2,7 +2,7 @@ from marshmallow import fields
2
2
  from marshmallow import validate
3
3
 
4
4
  ENDPOINT_FIELD = fields.String(
5
- required=True, validate=validate.Regexp(r'^[/][0-9a-zA-Z-_{}/?.]*[^/]$')
5
+ required=True, validate=validate.Regexp(r'^/[.a-zA-Z0-9/{}_-]*[?]{0,1}[a-zA-Z0-9/=&_-]*$')
6
6
  )
7
7
  HTTP_CODE_FIELD = fields.String(required=True, validate=validate.Regexp(r'^[1-5]{1}\d{2}|default$'))
8
8
  SUMMARY_FIELD = fields.String(validate=validate.Length(min=1, max=150))
@@ -1,11 +1,10 @@
1
1
  from marshmallow import fields
2
2
 
3
3
  from ..base import BaseSchema
4
- from .example_object_schema import ExampleObjectSchema
5
4
  from .schema_object_schema import SchemaObjectSchema
6
5
 
7
6
 
8
7
  class MediaTypeObjectSchema(BaseSchema):
9
8
  schema = fields.Nested(SchemaObjectSchema)
10
9
  itemSchema = fields.Nested(SchemaObjectSchema)
11
- examples = fields.Dict(keys=fields.String(), values=fields.Nested(ExampleObjectSchema))
10
+ examples = fields.Dict(keys=fields.String(), values=fields.Raw())
@@ -57,7 +57,7 @@ class OpenAPIObjectSchema(BaseSchema):
57
57
  if param_names:
58
58
  params_from_components = data['components'].get('parameters')
59
59
  if params_from_components:
60
- param_names_from_components = params_from_components.key()
60
+ param_names_from_components = params_from_components.keys()
61
61
 
62
62
  if not set(param_names).issubset(set(param_names_from_components)):
63
63
  raise ValidationError(
@@ -1,4 +1,3 @@
1
- from copy import deepcopy
2
1
  from pathlib import Path
3
2
  from typing import Any
4
3
 
@@ -7,8 +6,10 @@ from marshmallow import INCLUDE
7
6
  from marshmallow import RAISE
8
7
  from marshmallow import Schema
9
8
  from marshmallow import validate
9
+ from marshmallow.fields import Field
10
10
 
11
- from ..openapi import OpenAPI
11
+ from schema_first.openapi import OpenAPI
12
+ from schema_first.specification.spilli_api import ConverterOpenAPIToSpilliAPI
12
13
 
13
14
  FIELDS_VIA_TYPES = {
14
15
  'boolean': fields.Boolean,
@@ -34,6 +35,7 @@ FIELDS_VIA_FORMATS = {
34
35
  class Specification:
35
36
  def __init__(self, spec_file: Path | str):
36
37
  self.openapi = OpenAPI(spec_file)
38
+ self.spilli_api = None
37
39
  self.reassembly_spec = None
38
40
 
39
41
  @staticmethod
@@ -132,13 +134,17 @@ class Specification:
132
134
 
133
135
  return Schema.from_dict(marshmallow_schema)
134
136
 
135
- def _convert_array_field(self, open_api_schema: dict) -> type[Schema]:
137
+ def _convert_array_field(self, open_api_schema: dict) -> Field:
136
138
  array_item_schema = open_api_schema['items']
137
- array_schema = self._convert_object_field(array_item_schema)
138
- array_schema.opts.many = True
139
- return array_schema
139
+ if array_item_schema['type'] == 'object':
140
+ nested_field = self._convert_object_field(array_item_schema)
141
+ array_field = fields.Nested(nested_field, many=True)
142
+ else:
143
+ nested_field = FIELDS_VIA_TYPES[array_item_schema['type']]()
144
+ array_field = fields.List(nested_field)
145
+ return array_field
140
146
 
141
- def _reassembly_of_schemas(self, obj: Any) -> Any:
147
+ def _reassembly_of_schemas(self, obj: Any) -> None:
142
148
  if isinstance(obj, dict):
143
149
  for k, v in obj.items():
144
150
  # Checking for object type is needed to skip already resolved schemes.
@@ -148,10 +154,10 @@ class Specification:
148
154
  else:
149
155
  self._reassembly_of_schemas(v)
150
156
 
151
- def load(self) -> 'Specification':
157
+ def load(self) -> Specification:
152
158
  self.openapi.load()
153
- self.reassembly_spec = deepcopy(self.openapi.raw_spec)
154
159
 
160
+ self.reassembly_spec = ConverterOpenAPIToSpilliAPI(self.openapi.raw_spec).convert()
155
161
  self._reassembly_of_schemas(self.reassembly_spec)
156
162
 
157
163
  return self
@@ -0,0 +1,65 @@
1
+ import json
2
+ from typing import Any
3
+ import typing as t
4
+
5
+
6
+ class ConverterOpenAPIToSpilliAPI:
7
+ def __init__(self, open_api_spec: dict) -> None:
8
+ self.open_api_spec = open_api_spec
9
+ self.spilli_api_spec = None
10
+
11
+ def _convert_params_to_schema(
12
+ self, params: list[dict], place: t.Literal['header', 'cookie', 'path', 'query']
13
+ ) -> Any | None:
14
+ schema = {
15
+ 'type': 'object',
16
+ 'additionalProperties': True,
17
+ }
18
+
19
+ required = []
20
+ properties = {}
21
+ for param in params:
22
+ if param['in'] == place:
23
+ if param.get('required', False):
24
+ required.append(param['name'])
25
+ properties[param['name']] = param['schema']
26
+
27
+ if not properties:
28
+ return None
29
+ else:
30
+ schema['properties'] = properties
31
+
32
+ if required:
33
+ schema['required'] = required
34
+
35
+ return schema
36
+
37
+ def _params_list_to_dict(self, params: list[dict]) -> dict:
38
+ params_as_dict = {}
39
+ if headers := self._convert_params_to_schema(params, 'header'):
40
+ params_as_dict['headers'] = {'schema': headers}
41
+ if cookies := self._convert_params_to_schema(params, 'cookie'):
42
+ params_as_dict['cookies'] = {'schema': cookies}
43
+ if paths := self._convert_params_to_schema(params, 'path'):
44
+ params_as_dict['paths'] = {'schema': paths}
45
+ if queries := self._convert_params_to_schema(params, 'query'):
46
+ params_as_dict['queries'] = {'schema': queries}
47
+
48
+ return params_as_dict
49
+
50
+ def _make_to_spilli_api_format(self) -> None:
51
+ self.spilli_api_spec = json.loads(json.dumps(self.open_api_spec))
52
+
53
+ for _, methods in self.spilli_api_spec['paths'].items():
54
+ common_params = []
55
+ if 'parameters' in methods:
56
+ common_params = methods.pop('parameters', [])
57
+
58
+ for _, operation_obj in methods.items():
59
+ if 'parameters' in operation_obj:
60
+ method_params = common_params + operation_obj.pop('parameters', [])
61
+ operation_obj['parameters'] = self._params_list_to_dict(method_params)
62
+
63
+ def convert(self) -> dict:
64
+ self._make_to_spilli_api_format()
65
+ return self.spilli_api_spec
@@ -7,15 +7,12 @@ from openapi_spec_validator import validate as osv_validate
7
7
  from openapi_spec_validator.readers import read_from_filename
8
8
  import pytest
9
9
 
10
- from src.schema_first.openapi import OpenAPI
11
10
  from src.schema_first.specification import Specification
11
+ from tests.conftest import specs_base_dir
12
12
  from tests.conftest import tests_dir_abspath
13
13
  from tests.utils import get_schema_from_request
14
14
 
15
- specs_base_dir = Path(tests_dir_abspath, '_contrib', 'specs')
16
- spec_file_paths = list(Path(specs_base_dir, 'v3.0').iterdir())
17
- spec_file_paths.extend(list(Path(specs_base_dir, 'v3.1').iterdir()))
18
- spec_file_paths.extend(list(Path(specs_base_dir, 'v3.2').iterdir()))
15
+ spec_file_paths = list(Path(specs_base_dir, 'v3.2').iterdir())
19
16
 
20
17
 
21
18
  @pytest.mark.parametrize('fx', ['fx_spec_required', 'fx_spec_full'])
@@ -23,7 +20,7 @@ def test_specification(request, fx_spec_as_file, fx):
23
20
  spec_file = fx_spec_as_file(request.getfixturevalue(fx))
24
21
  spec = Specification(spec_file)
25
22
 
26
- assert isinstance(spec.openapi, OpenAPI)
23
+ assert spec.openapi
27
24
  assert spec.reassembly_spec is None
28
25
 
29
26
  spec.load()
@@ -61,7 +58,7 @@ def test_specification__multifile_required(fx_spec_as_file, fx_spec_required):
61
58
 
62
59
  @pytest.mark.xfail(reason='Schema specification not fully realisation.')
63
60
  def test_specification__multifile():
64
- file_path = Path(tests_dir_abspath, '_contrib', 'specs', 'multifile', 'openapi', 'openapi.yaml')
61
+ file_path = Path(tests_dir_abspath, '_contrib', 'specs', 'v3.2', 'multifile', 'openapi.yaml')
65
62
  spec_as_dict, base_uri = read_from_filename(file_path)
66
63
  osv_validate(spec_as_dict, base_uri=base_uri)
67
64
 
File without changes
File without changes