Skip to content

Commit 4b7ef35

Browse files
author
Adam Gray
committed
Improvements to schema and enum naming
1 parent 5583d86 commit 4b7ef35

File tree

6 files changed

+51
-37
lines changed

6 files changed

+51
-37
lines changed

openapi_python_client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def _build_models(self) -> None:
150150
# Generate enums
151151
enum_template = self.env.get_template("enum.pyi")
152152
for enum in self.openapi.enums.values():
153-
module_path = models_dir / f"{enum.name}.py"
153+
module_path = models_dir / f"{enum.reference.module_name}.py"
154154
module_path.write_text(enum_template.render(enum=enum))
155155
imports.append(import_string_from_reference(enum.reference))
156156

openapi_python_client/openapi_parser/openapi.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -154,24 +154,32 @@ class Schema:
154154
relative_imports: Set[str]
155155

156156
@staticmethod
157-
def from_dict(d: Dict[str, Any], /) -> Schema:
158-
""" A single Schema from its dict representation """
157+
def from_dict(d: Dict[str, Any], /, name: str = None) -> Schema:
158+
""" A single Schema from its dict representation
159+
:param d: Dict representation of the schema
160+
:param name: Name by which the schema is referenced, such as a model name. Used to infer the type name if a `title` property is not available.
161+
"""
159162
required_set = set(d.get("required", []))
160163
required_properties: List[Property] = []
161164
optional_properties: List[Property] = []
162165
relative_imports: Set[str] = set()
163166

164-
for key, value in d["properties"].items():
165-
required = key in required_set
166-
p = property_from_dict(name=key, required=required, data=value)
167-
if required:
168-
required_properties.append(p)
169-
else:
170-
optional_properties.append(p)
171-
if isinstance(p, (ReferenceListProperty, EnumListProperty, RefProperty, EnumProperty)) and p.reference:
172-
relative_imports.add(import_string_from_reference(p.reference))
167+
ref = Reference.from_ref(d.get("title", name))
168+
169+
if "properties" in d:
170+
for key, value in d["properties"].items():
171+
required = key in required_set
172+
p = property_from_dict(name=key, required=required, data=value)
173+
if required:
174+
required_properties.append(p)
175+
else:
176+
optional_properties.append(p)
177+
if isinstance(p, (ReferenceListProperty, EnumListProperty, RefProperty, EnumProperty)) and p.reference:
178+
# don't add an import for self-referencing schemas
179+
if p.reference.class_name != ref.class_name:
180+
relative_imports.add(import_string_from_reference(p.reference))
173181
schema = Schema(
174-
reference=Reference.from_ref(d["title"]),
182+
reference=ref,
175183
required_properties=required_properties,
176184
optional_properties=optional_properties,
177185
relative_imports=relative_imports,
@@ -183,8 +191,8 @@ def from_dict(d: Dict[str, Any], /) -> Schema:
183191
def dict(d: Dict[str, Dict[str, Any]], /) -> Dict[str, Schema]:
184192
""" Get a list of Schemas from an OpenAPI dict """
185193
result = {}
186-
for data in d.values():
187-
s = Schema.from_dict(data)
194+
for name, data in d.items():
195+
s = Schema.from_dict(data, name=name)
188196
result[s.reference.class_name] = s
189197
return result
190198

openapi_python_client/openapi_parser/properties.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,9 @@ class EnumProperty(Property):
146146
""" A property that should use an enum """
147147

148148
values: Dict[str, str]
149-
reference: Reference = field(init=False)
149+
reference: Reference = field(init=True)
150150

151151
def __post_init__(self) -> None:
152-
self.reference = Reference.from_ref(self.name)
153152
inverse_values = {v: k for k, v in self.values.items()}
154153
if self.default is not None:
155154
self.default = f"{self.reference.class_name}.{inverse_values[self.default]}"
@@ -227,6 +226,7 @@ def property_from_dict(name: str, required: bool, data: Dict[str, Any]) -> Prope
227226
name=name,
228227
required=required,
229228
values=EnumProperty.values_from_list(data["enum"]),
229+
reference=Reference.from_ref(data.get("title", name)),
230230
default=data.get("default"),
231231
)
232232
if "$ref" in data:

tests/test___init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,14 @@ def test__build_models(self, mocker):
274274
"__init__.py": models_init,
275275
f"{schema_1.reference.module_name}.py": schema_1_module_path,
276276
f"{schema_2.reference.module_name}.py": schema_2_module_path,
277-
f"{enum_1.name}.py": enum_1_module_path,
278-
f"{enum_2.name}.py": enum_2_module_path,
277+
f"{enum_1.reference.module_name}.py": enum_1_module_path,
278+
f"{enum_2.reference.module_name}.py": enum_2_module_path,
279279
}
280-
models_dir.__truediv__.side_effect = lambda x: module_paths[x]
280+
281+
def models_dir_get(x):
282+
return module_paths[x]
283+
284+
models_dir.__truediv__.side_effect = models_dir_get
281285
project.package_dir.__truediv__.return_value = models_dir
282286
model_render_1 = mocker.MagicMock()
283287
model_render_2 = mocker.MagicMock()

tests/test_openapi_parser/test_openapi.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import pytest
22

3+
from openapi_python_client.openapi_parser.reference import Reference
4+
35
MODULE_NAME = "openapi_python_client.openapi_parser.openapi"
46

57

@@ -43,7 +45,7 @@ def test__check_enums(self, mocker):
4345
from openapi_python_client.openapi_parser.properties import EnumProperty, StringProperty
4446

4547
def _make_enum():
46-
return EnumProperty(name=mocker.MagicMock(), required=True, default=None, values=mocker.MagicMock(),)
48+
return EnumProperty(name=mocker.MagicMock(), required=True, default=None, values=mocker.MagicMock(), reference=mocker.MagicMock())
4749

4850
# Multiple schemas with both required and optional properties for making sure iteration works correctly
4951
schema_1 = mocker.MagicMock()
@@ -119,7 +121,7 @@ def test__check_enums_bad_duplicate(self, mocker):
119121

120122
schema = mocker.MagicMock()
121123

122-
enum_1 = EnumProperty(name=mocker.MagicMock(), required=True, default=None, values=mocker.MagicMock(),)
124+
enum_1 = EnumProperty(name=mocker.MagicMock(), required=True, default=None, values=mocker.MagicMock(), reference=mocker.MagicMock())
123125
enum_2 = replace(enum_1, values=mocker.MagicMock())
124126
schema.required_properties = [enum_1, enum_2]
125127

@@ -139,7 +141,7 @@ def test_dict(self, mocker):
139141

140142
result = Schema.dict(in_data)
141143

142-
from_dict.assert_has_calls([mocker.call(value) for value in in_data.values()])
144+
from_dict.assert_has_calls([mocker.call(value, name=name) for (name, value) in in_data.items()])
143145
assert result == {
144146
schema_1.reference.class_name: schema_1,
145147
schema_2.reference.class_name: schema_2,
@@ -154,7 +156,7 @@ def test_from_dict(self, mocker):
154156
"required": ["RequiredEnum"],
155157
"properties": {"RequiredEnum": mocker.MagicMock(), "OptionalString": mocker.MagicMock(),},
156158
}
157-
required_property = EnumProperty(name="RequiredEnum", required=True, default=None, values={},)
159+
required_property = EnumProperty(name="RequiredEnum", required=True, default=None, values={}, reference=Reference.from_ref("RequiredEnum"))
158160
optional_property = StringProperty(name="OptionalString", required=False, default=None)
159161
property_from_dict = mocker.patch(
160162
f"{MODULE_NAME}.property_from_dict", side_effect=[required_property, optional_property]
@@ -347,8 +349,8 @@ def test__add_parameters_happy(self, mocker):
347349
tag="tag",
348350
relative_imports={"import_3"},
349351
)
350-
path_prop = EnumProperty(name="path_enum", required=True, default=None, values={})
351-
query_prop = EnumProperty(name="query_enum", required=False, default=None, values={})
352+
path_prop = EnumProperty(name="path_enum", required=True, default=None, values={}, reference=mocker.MagicMock())
353+
query_prop = EnumProperty(name="query_enum", required=False, default=None, values={}, reference=mocker.MagicMock())
352354
propety_from_dict = mocker.patch(f"{MODULE_NAME}.property_from_dict", side_effect=[path_prop, query_prop])
353355
path_schema = mocker.MagicMock()
354356
query_schema = mocker.MagicMock()

tests/test_openapi_parser/test_properties.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,16 @@ def test_get_type_string(self, mocker):
125125
class TestEnumProperty:
126126
def test___post_init__(self, mocker):
127127
name = mocker.MagicMock()
128-
fake_reference = mocker.MagicMock(class_name="MyTestEnum")
129-
from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref", return_value=fake_reference)
130-
131128
from openapi_python_client.openapi_parser.properties import EnumProperty
132129

133130
enum_property = EnumProperty(
134-
name=name, required=True, default="second", values={"FIRST": "first", "SECOND": "second"}
131+
name=name,
132+
required=True,
133+
default="second",
134+
values={"FIRST": "first", "SECOND": "second"},
135+
reference=(mocker.MagicMock(class_name="MyTestEnum")),
135136
)
136137

137-
from_ref.assert_called_once_with(name)
138138
assert enum_property.default == "MyTestEnum.SECOND"
139139

140140
def test_get_type_string(self, mocker):
@@ -143,7 +143,7 @@ def test_get_type_string(self, mocker):
143143

144144
from openapi_python_client.openapi_parser.properties import EnumProperty
145145

146-
enum_property = EnumProperty(name="test", required=True, default=None, values={})
146+
enum_property = EnumProperty(name="test", required=True, default=None, values={}, reference=mocker.MagicMock(class_name="MyTestEnum"))
147147

148148
assert enum_property.get_type_string() == "MyTestEnum"
149149
enum_property.required = False
@@ -155,17 +155,16 @@ def test_transform(self, mocker):
155155

156156
from openapi_python_client.openapi_parser.properties import EnumProperty
157157

158-
enum_property = EnumProperty(name=name, required=True, default=None, values={})
158+
enum_property = EnumProperty(name=name, required=True, default=None, values={}, reference=mocker.MagicMock())
159159

160160
assert enum_property.transform() == f"{name}.value"
161161

162162
def test_constructor_from_dict(self, mocker):
163163
fake_reference = mocker.MagicMock(class_name="MyTestEnum")
164-
mocker.patch(f"{MODULE_NAME}.Reference.from_ref", return_value=fake_reference)
165164

166165
from openapi_python_client.openapi_parser.properties import EnumProperty
167166

168-
enum_property = EnumProperty(name="test_enum", required=True, default=None, values={})
167+
enum_property = EnumProperty(name="test_enum", required=True, default=None, values={}, reference=fake_reference)
169168

170169
assert (
171170
enum_property.constructor_from_dict("my_dict")
@@ -216,14 +215,15 @@ def test_property_from_dict_enum(self, mocker):
216215
"enum": mocker.MagicMock(),
217216
}
218217
EnumProperty = mocker.patch(f"{MODULE_NAME}.EnumProperty")
218+
from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref")
219219

220220
from openapi_python_client.openapi_parser.properties import property_from_dict
221221

222222
p = property_from_dict(name=name, required=required, data=data)
223223

224224
EnumProperty.values_from_list.assert_called_once_with(data["enum"])
225225
EnumProperty.assert_called_once_with(
226-
name=name, required=required, values=EnumProperty.values_from_list(), default=None
226+
name=name, required=required, values=EnumProperty.values_from_list(), default=None, reference=from_ref()
227227
)
228228
assert p == EnumProperty()
229229

@@ -234,7 +234,7 @@ def test_property_from_dict_enum(self, mocker):
234234
name=name, required=required, data=data,
235235
)
236236
EnumProperty.assert_called_once_with(
237-
name=name, required=required, values=EnumProperty.values_from_list(), default=data["default"]
237+
name=name, required=required, values=EnumProperty.values_from_list(), default=data["default"], reference=from_ref()
238238
)
239239

240240
def test_property_from_dict_ref(self, mocker):

0 commit comments

Comments
 (0)