Skip to content

Commit d7db6d0

Browse files
author
Jordi Sanchez
committed
Code cleanup.
Adds missing tests for a number of parameter-related methods.
1 parent 53912d7 commit d7db6d0

File tree

6 files changed

+214
-47
lines changed

6 files changed

+214
-47
lines changed

openapi_python_client/parser/openapi.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -521,9 +521,7 @@ def from_dict(data: Dict[str, Any], *, config: Config) -> Union["GeneratorData",
521521
if openapi.components and openapi.components.schemas:
522522
schemas = build_schemas(components=openapi.components.schemas, schemas=schemas, config=config)
523523
if openapi.components and openapi.components.parameters:
524-
parameters = build_parameters(
525-
components=openapi.components.parameters, schemas=schemas, parameters=parameters, config=config
526-
)
524+
parameters = build_parameters(components=openapi.components.parameters, parameters=parameters)
527525
endpoint_collections_by_tag, schemas, parameters = EndpointCollection.from_data(
528526
data=openapi.paths, schemas=schemas, parameters=parameters, config=config
529527
)

openapi_python_client/parser/properties/__init__.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -743,10 +743,8 @@ def build_parameters(
743743
*,
744744
components: Dict[str, Union[oai.Reference, oai.Parameter]],
745745
parameters: Parameters,
746-
schemas: Schemas,
747-
config: Config,
748746
) -> Parameters:
749-
"""Get a list of Schemas from an OpenAPI dict"""
747+
"""Get a list of Parameters from an OpenAPI dict"""
750748
to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Parameter]]] = []
751749
if components is not None:
752750
to_process = components.items()
@@ -761,15 +759,13 @@ def build_parameters(
761759
# Only accumulate errors from the last round, since we might fix some along the way
762760
for name, data in to_process:
763761
if isinstance(data, oai.Reference):
764-
parameters.errors.append(PropertyError(data=data, detail="Reference schemas are not supported."))
762+
parameters.errors.append(ParameterError(data=data, detail="Reference parameters are not supported."))
765763
continue
766764
ref_path = parse_reference_path(f"#/components/parameters/{name}")
767765
if isinstance(ref_path, ParseError):
768-
parameters.errors.append(PropertyError(detail=ref_path.detail, data=data))
766+
parameters.errors.append(ParameterError(detail=ref_path.detail, data=data))
769767
continue
770-
parameters_or_err = update_parameters_with_data(
771-
ref_path=ref_path, data=data, schemas=schemas, parameters=parameters, config=config
772-
)
768+
parameters_or_err = update_parameters_with_data(ref_path=ref_path, data=data, parameters=parameters)
773769
if isinstance(parameters_or_err, ParameterError):
774770
next_round.append((name, data))
775771
errors.append(parameters_or_err)

openapi_python_client/parser/properties/schemas.py

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
"Parameters",
55
"parse_reference_path",
66
"update_schemas_with_data",
7+
"update_parameters_with_data",
78
"parameter_from_reference",
9+
"parameter_from_data",
810
]
911

1012
from typing import TYPE_CHECKING, Dict, List, NewType, Tuple, Union, cast
@@ -129,27 +131,15 @@ def parameter_from_data(
129131
name: str,
130132
required: bool,
131133
data: Union[oai.Reference, oai.Parameter],
132-
schemas: Schemas,
133134
parameters: Parameters,
134-
config: Config,
135-
) -> Tuple[Union[Parameter, ParameterError], Schemas, Parameters]:
136-
"""Generates parameters from"""
137-
from . import property_from_data
135+
) -> Tuple[Union[Parameter, ParameterError], Parameters]:
136+
"""Generates parameters from an OpenAPI Parameter spec."""
138137

139138
if isinstance(data, oai.Reference):
140-
return ParameterError("Unable to resolve another reference"), schemas, parameters
139+
return ParameterError("Unable to resolve another reference"), parameters
141140

142141
if data.param_schema is None:
143-
return ParameterError("Parameter has no schema"), schemas, parameters
144-
145-
_, new_schemas = property_from_data(
146-
name=name,
147-
required=required,
148-
data=data.param_schema,
149-
schemas=schemas,
150-
parent_name="",
151-
config=config,
152-
)
142+
return ParameterError("Parameter has no schema"), parameters
153143

154144
new_param = Parameter(
155145
name=name,
@@ -160,34 +150,32 @@ def parameter_from_data(
160150
param_in=data.param_in,
161151
)
162152
parameters = attr.evolve(parameters, classes_by_name={**parameters.classes_by_name, name: new_param})
163-
return new_param, new_schemas, parameters
153+
return new_param, parameters
164154

165155

166156
def update_parameters_with_data(
167-
*, ref_path: _ReferencePath, data: oai.Parameter, parameters: Parameters, schemas: Schemas, config: Config
157+
*, ref_path: _ReferencePath, data: oai.Parameter, parameters: Parameters
168158
) -> Union[Parameters, ParameterError]:
169159
"""
170-
Update a `Schemas` using some new reference.
160+
Update a `Parameters` using some new reference.
171161
172162
Args:
173163
ref_path: The output of `parse_reference_path` (validated $ref).
174164
data: The schema of the thing to add to Schemas.
175-
schemas: `Schemas` up until now.
165+
parameters: `Parameters` up until now.
176166
config: User-provided config for overriding default behavior.
177167
178168
Returns:
179-
Either the updated `schemas` input or a `PropertyError` if something went wrong.
169+
Either the updated `parameters` input or a `PropertyError` if something went wrong.
180170
181171
See Also:
182172
- https://swagger.io/docs/specification/using-ref/
183173
"""
184-
param, schemas, parameters = parameter_from_data(
185-
data=data, name=data.name, parameters=parameters, schemas=schemas, required=True, config=config
186-
)
174+
param, parameters = parameter_from_data(data=data, name=data.name, parameters=parameters, required=True)
187175

188176
if isinstance(param, ParameterError):
189177
param.detail = f"{param.header}: {param.detail}"
190-
param.header = f"Unable to parse schema {ref_path}"
178+
param.header = f"Unable to parse parameter {ref_path}"
191179
if isinstance(param.data, oai.Reference) and param.data.ref.endswith(ref_path): # pragma: nocover
192180
param.detail += (
193181
"\n\nRecursive and circular references are not supported. "
@@ -196,6 +184,7 @@ def update_parameters_with_data(
196184
return param
197185

198186
parameters = attr.evolve(parameters, classes_by_reference={ref_path: param, **parameters.classes_by_reference})
187+
print("ref_path is: %s", ref_path)
199188
return parameters
200189

201190

tests/test_parser/test_openapi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ def test_from_dict(self, mocker, model_property_factory, enum_property_factory):
4242
build_schemas.assert_called_once_with(components=openapi.components.schemas, config=config, schemas=Schemas())
4343
build_parameters.assert_called_once_with(
4444
components=openapi.components.parameters,
45-
config=config,
46-
schemas=build_schemas.return_value,
4745
parameters=parameters,
4846
)
4947
EndpointCollection.from_data.assert_called_once_with(
@@ -63,10 +61,12 @@ def test_from_dict(self, mocker, model_property_factory, enum_property_factory):
6361
# Test no components
6462
openapi.components = None
6563
build_schemas.reset_mock()
64+
build_parameters.reset_mock()
6665

6766
GeneratorData.from_dict(in_dict, config=config)
6867

6968
build_schemas.assert_not_called()
69+
build_parameters.assert_not_called()
7070

7171
def test_from_dict_invalid_schema(self, mocker):
7272
Schemas = mocker.patch(f"{MODULE_NAME}.Schemas")

tests/test_parser/test_properties/test_init.py

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,8 @@
66

77
import openapi_python_client.schema as oai
88
from openapi_python_client import Config
9-
from openapi_python_client.parser.errors import PropertyError, ValidationError
10-
from openapi_python_client.parser.properties import (
11-
BooleanProperty,
12-
FloatProperty,
13-
IntProperty,
14-
NoneProperty,
15-
Property,
16-
Schemas,
17-
)
9+
from openapi_python_client.parser.errors import ParameterError, PropertyError, ValidationError
10+
from openapi_python_client.parser.properties import BooleanProperty, FloatProperty, IntProperty, Property, Schemas
1811

1912
MODULE_NAME = "openapi_python_client.parser.properties"
2013

@@ -995,6 +988,84 @@ def test_retries_failing_properties_while_making_progress(self, mocker):
995988
assert result.errors == [PropertyError()]
996989

997990

991+
class TestBuildParameters:
992+
def test_skips_references_and_keeps_going(self, mocker):
993+
from openapi_python_client.parser.properties import Parameters, build_parameters
994+
from openapi_python_client.schema import Parameter, Reference
995+
996+
parameters = {
997+
"reference": Reference(ref="#/components/parameters/another_parameter"),
998+
"defined": Parameter(
999+
name="page",
1000+
param_in="query",
1001+
required=False,
1002+
style="form",
1003+
explode=True,
1004+
schema=oai.Schema(type="integer", default=0),
1005+
),
1006+
}
1007+
1008+
update_parameters_with_data = mocker.patch(f"{MODULE_NAME}.update_parameters_with_data")
1009+
parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path")
1010+
1011+
result = build_parameters(components=parameters, parameters=Parameters())
1012+
# Should not even try to parse a path for the Reference
1013+
parse_reference_path.assert_called_once_with("#/components/parameters/defined")
1014+
update_parameters_with_data.assert_called_once_with(
1015+
ref_path=parse_reference_path.return_value,
1016+
data=parameters["defined"],
1017+
parameters=Parameters(
1018+
errors=[ParameterError(detail="Reference parameters are not supported.", data=parameters["reference"])]
1019+
),
1020+
)
1021+
assert result == update_parameters_with_data.return_value
1022+
1023+
def test_records_bad_uris_and_keeps_going(self, mocker):
1024+
from openapi_python_client.parser.properties import Parameters, build_parameters
1025+
from openapi_python_client.schema import Parameter
1026+
1027+
parameters = {"first": Parameter.construct(), "second": Parameter.construct()}
1028+
update_parameters_with_data = mocker.patch(f"{MODULE_NAME}.update_parameters_with_data")
1029+
parse_reference_path = mocker.patch(
1030+
f"{MODULE_NAME}.parse_reference_path", side_effect=[ParameterError(detail="some details"), "a_path"]
1031+
)
1032+
1033+
result = build_parameters(components=parameters, parameters=Parameters())
1034+
parse_reference_path.assert_has_calls(
1035+
[
1036+
call("#/components/parameters/first"),
1037+
call("#/components/parameters/second"),
1038+
]
1039+
)
1040+
update_parameters_with_data.assert_called_once_with(
1041+
ref_path="a_path",
1042+
data=parameters["second"],
1043+
parameters=Parameters(errors=[ParameterError(detail="some details", data=parameters["first"])]),
1044+
)
1045+
assert result == update_parameters_with_data.return_value
1046+
1047+
def test_retries_failing_parameters_while_making_progress(self, mocker):
1048+
from openapi_python_client.parser.properties import Parameters, build_parameters
1049+
from openapi_python_client.schema import Parameter
1050+
1051+
parameters = {"first": Parameter.construct(), "second": Parameter.construct()}
1052+
update_parameters_with_data = mocker.patch(
1053+
f"{MODULE_NAME}.update_parameters_with_data", side_effect=[ParameterError(), Parameters(), ParameterError()]
1054+
)
1055+
1056+
parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path")
1057+
result = build_parameters(components=parameters, parameters=Parameters())
1058+
parse_reference_path.assert_has_calls(
1059+
[
1060+
call("#/components/parameters/first"),
1061+
call("#/components/parameters/second"),
1062+
call("#/components/parameters/first"),
1063+
]
1064+
)
1065+
assert update_parameters_with_data.call_count == 3
1066+
assert result.errors == [ParameterError()]
1067+
1068+
9981069
def test_build_enum_property_conflict():
9991070
from openapi_python_client.parser.properties import Schemas, build_enum_property
10001071

tests/test_parser/test_properties/test_schemas.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import pytest
22

3+
from openapi_python_client.parser.errors import ParameterError
4+
from openapi_python_client.parser.properties import Class, Parameters
5+
from openapi_python_client.parser.properties.schemas import parameter_from_reference
6+
from openapi_python_client.schema import Parameter, Reference
7+
8+
MODULE_NAME = "openapi_python_client.parser.properties.schemas"
9+
310

411
def test_class_from_string_default_config():
512
from openapi_python_client import Config
@@ -32,3 +39,109 @@ def test_class_from_string(class_override, module_override, expected_class, expe
3239
result = Class.from_string(string=ref, config=config)
3340
assert result.name == expected_class
3441
assert result.module_name == expected_module
42+
43+
44+
class TestParameterFromData:
45+
def test_cannot_parse_parameters_by_reference(self):
46+
from openapi_python_client.parser.properties import Parameters
47+
from openapi_python_client.parser.properties.schemas import parameter_from_data
48+
49+
ref = Reference.construct(ref="#/components/parameters/a_param")
50+
parameters = Parameters()
51+
param_or_error, new_parameters = parameter_from_data(
52+
name="a_param", required=True, data=ref, parameters=parameters
53+
)
54+
assert param_or_error == ParameterError("Unable to resolve another reference")
55+
assert new_parameters == parameters
56+
57+
def test_parameters_without_schema_are_ignored(self):
58+
from openapi_python_client.parser.properties import Parameters
59+
from openapi_python_client.parser.properties.schemas import parameter_from_data
60+
from openapi_python_client.schema import ParameterLocation, Schema
61+
62+
param = Parameter(name="a_schemaless_param", param_in=ParameterLocation.QUERY)
63+
parameters = Parameters()
64+
param_or_error, new_parameters = parameter_from_data(
65+
name=param.name, required=param.required, data=param, parameters=parameters
66+
)
67+
assert param_or_error == ParameterError("Parameter has no schema")
68+
assert new_parameters == parameters
69+
70+
def test_registers_new_parameters(self):
71+
from openapi_python_client.parser.properties import Parameters
72+
from openapi_python_client.parser.properties.schemas import parameter_from_data
73+
from openapi_python_client.schema import ParameterLocation, Schema
74+
75+
param = Parameter.construct(name="a_param", param_in=ParameterLocation.QUERY, param_schema=Schema.construct())
76+
parameters = Parameters()
77+
param_or_error, new_parameters = parameter_from_data(
78+
name=param.name, required=param.required, data=param, parameters=parameters
79+
)
80+
assert param_or_error == param
81+
assert new_parameters.classes_by_name[param.name] == param
82+
83+
84+
class TestParameterFromReference:
85+
def test_returns_parameter_if_parameter_provided(self):
86+
param = Parameter.construct()
87+
params = Parameters()
88+
param_or_error = parameter_from_reference(param=param, parameters=params)
89+
assert param_or_error == param
90+
91+
def test_errors_out_if_reference_not_in_parameters(self):
92+
ref = Reference.construct(ref="#/components/parameters/a_param")
93+
class_info = Class(name="a_param", module_name="module_name")
94+
existing_param = Parameter.construct(name="a_param")
95+
param_by_ref = Reference.construct(ref="#/components/parameters/another_param")
96+
params = Parameters(
97+
classes_by_name={class_info.name: existing_param}, classes_by_reference={ref.ref: existing_param}
98+
)
99+
param_or_error = parameter_from_reference(param=param_by_ref, parameters=params)
100+
assert param_or_error == ParameterError(
101+
detail="Reference `/components/parameters/another_param` not found.",
102+
)
103+
104+
def test_returns_reference_from_registry(self):
105+
existing_param = Parameter.construct(name="a_param")
106+
class_info = Class(name="MyParameter", module_name="module_name")
107+
params = Parameters(
108+
classes_by_name={class_info.name: existing_param},
109+
classes_by_reference={"/components/parameters/a_param": existing_param},
110+
)
111+
112+
param_by_ref = Reference.construct(ref="#/components/parameters/a_param")
113+
param_or_error = parameter_from_reference(param=param_by_ref, parameters=params)
114+
assert param_or_error == existing_param
115+
116+
117+
class TestUpdateParametersFromData:
118+
def test_reports_parameters_with_errors(self, mocker):
119+
from openapi_python_client.parser.properties.schemas import update_parameters_with_data
120+
from openapi_python_client.schema import ParameterLocation, Schema
121+
122+
parameters = Parameters()
123+
param = Parameter.construct(name="a_param", param_in=ParameterLocation.QUERY, param_schema=Schema.construct())
124+
parameter_from_data = mocker.patch(
125+
f"{MODULE_NAME}.parameter_from_data", side_effect=[(ParameterError(), parameters)]
126+
)
127+
ref_path = Reference.construct(ref="#/components/parameters/a_param")
128+
new_parameters_or_error = update_parameters_with_data(ref_path=ref_path.ref, data=param, parameters=parameters)
129+
130+
parameter_from_data.assert_called_once()
131+
assert new_parameters_or_error == ParameterError(
132+
detail="Unable to parse this part of your OpenAPI document: : None",
133+
header="Unable to parse parameter #/components/parameters/a_param",
134+
)
135+
136+
def test_records_references_to_parameters(self, mocker):
137+
from openapi_python_client.parser.properties.schemas import update_parameters_with_data
138+
from openapi_python_client.schema import ParameterLocation, Schema
139+
140+
parameters = Parameters()
141+
param = Parameter.construct(name="a_param", param_in=ParameterLocation.QUERY, param_schema=Schema.construct())
142+
parameter_from_data = mocker.patch(f"{MODULE_NAME}.parameter_from_data", side_effect=[(param, parameters)])
143+
ref_path = "#/components/parameters/a_param"
144+
new_parameters = update_parameters_with_data(ref_path=ref_path, data=param, parameters=parameters)
145+
146+
parameter_from_data.assert_called_once()
147+
assert new_parameters.classes_by_reference[ref_path] == param

0 commit comments

Comments
 (0)