Skip to content

Commit 87975f2

Browse files
committed
* Fix parameters generate schema.
* Fix array generate schema.
1 parent 9a76caf commit 87975f2

32 files changed

Lines changed: 3443 additions & 22 deletions

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ repos:
3232
rev: v3.21.2
3333
hooks:
3434
- id: pyupgrade
35-
args: [--py313-plus]
35+
args: [--py314-plus]
3636
- repo: https://github.com/hauntsaninja/no_implicit_optional
3737
rev: '1.4'
3838
hooks:

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.13
1+
3.14

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## Version 0.14.2
2+
3+
* Fix parameters generate schema.
4+
* Fix array generate schema.
5+
16
## Version 0.14.1
27

38
* Add few small fixes.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
VENV_DIR = venv
2-
PYTHON = python3.13
2+
PYTHON = python3.14
33
PIP = $(VENV_DIR)/bin/pip
44
PYTHON_VENV = $(VENV_DIR)/bin/python
55
PRE_COMMIT = $(VENV_DIR)/bin/pre-commit

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ license = "LGPL-2.1-only"
1919
license-files = ["LICENSE.txt"]
2020
name = "Schema-First"
2121
readme = "README.md"
22-
requires-python = ">=3.13"
23-
version = "0.14.1"
22+
requires-python = ">=3.14"
23+
version = "0.14.2"
2424

2525
[project.optional-dependencies]
2626
dev = [

src/schema_first/loaders/yaml_loader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def search_file(self, obj: dict or list) -> None:
5858
else:
5959
return
6060

61-
def load(self) -> 'YAMLReader':
61+
def load(self) -> YAMLReader:
6262
root_file = self._yaml_to_dict(self.path)
6363
self.store[self.root_file_name] = root_file
6464
self.search_file(root_file)
@@ -130,7 +130,7 @@ def _resolving_all_refs(self, file_path: str, obj: Any) -> Any:
130130

131131
return obj
132132

133-
def resolving(self) -> 'RefResolver':
133+
def resolving(self) -> RefResolver:
134134
root_file_path = self.yaml_reader.root_file_name
135135
root_spec = self.yaml_reader.store[root_file_path]
136136
self.resolved_spec = self._resolving_all_refs(root_file_path, root_spec)

src/schema_first/openapi/schemas/v3_2/openapi_object_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def validate_path_parameter(self, data, **kwargs) -> None:
5757
if param_names:
5858
params_from_components = data['components'].get('parameters')
5959
if params_from_components:
60-
param_names_from_components = params_from_components.key()
60+
param_names_from_components = params_from_components.keys()
6161

6262
if not set(param_names).issubset(set(param_names_from_components)):
6363
raise ValidationError(

src/schema_first/specification/__init__.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from copy import deepcopy
21
from pathlib import Path
32
from typing import Any
43

@@ -7,8 +6,10 @@
76
from marshmallow import RAISE
87
from marshmallow import Schema
98
from marshmallow import validate
9+
from marshmallow.fields import Field
1010

11-
from ..openapi import OpenAPI
11+
from schema_first.openapi import OpenAPI
12+
from schema_first.specification.spilli_api import ConverterOpenAPIToSpilliAPI
1213

1314
FIELDS_VIA_TYPES = {
1415
'boolean': fields.Boolean,
@@ -34,6 +35,7 @@
3435
class Specification:
3536
def __init__(self, spec_file: Path | str):
3637
self.openapi = OpenAPI(spec_file)
38+
self.spilli_api = None
3739
self.reassembly_spec = None
3840

3941
@staticmethod
@@ -132,13 +134,17 @@ def _convert_object_field(self, open_api_schema: dict) -> type[Schema]:
132134

133135
return Schema.from_dict(marshmallow_schema)
134136

135-
def _convert_array_field(self, open_api_schema: dict) -> type[Schema]:
137+
def _convert_array_field(self, open_api_schema: dict) -> Field:
136138
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
140146

141-
def _reassembly_of_schemas(self, obj: Any) -> Any:
147+
def _reassembly_of_schemas(self, obj: Any) -> None:
142148
if isinstance(obj, dict):
143149
for k, v in obj.items():
144150
# Checking for object type is needed to skip already resolved schemes.
@@ -148,10 +154,10 @@ def _reassembly_of_schemas(self, obj: Any) -> Any:
148154
else:
149155
self._reassembly_of_schemas(v)
150156

151-
def load(self) -> 'Specification':
157+
def load(self) -> Specification:
152158
self.openapi.load()
153-
self.reassembly_spec = deepcopy(self.openapi.raw_spec)
154159

160+
self.reassembly_spec = ConverterOpenAPIToSpilliAPI(self.openapi.raw_spec).convert()
155161
self._reassembly_of_schemas(self.reassembly_spec)
156162

157163
return self
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
openapi: 3.2.0
2+
info:
3+
title: Mini API for testing Flask-First
4+
version: 1.0.0
5+
paths:
6+
/mini_endpoint:
7+
post:
8+
operationId: mini_endpoint
9+
requestBody:
10+
description: Request item.
11+
content:
12+
application/json:
13+
schema:
14+
type: object
15+
properties:
16+
array:
17+
type: array
18+
items:
19+
type: string
20+
responses:
21+
'201':
22+
description: Response item.
23+
content:
24+
application/json:
25+
schema:
26+
type: object
27+
properties:
28+
array:
29+
type: array
30+
items:
31+
type: object
32+
properties:
33+
field:
34+
type: string

0 commit comments

Comments
 (0)