Skip to content

Commit b2c091e

Browse files
committed
Added Enum support
1 parent 13547a0 commit b2c091e

File tree

4 files changed

+63
-24
lines changed

4 files changed

+63
-24
lines changed

openapi_python_client/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,9 @@ def _build_project(openapi: OpenAPI):
5353
module_path = models_dir / f"{stringcase.snakecase(schema.title)}.py"
5454
module_path.write_text(model_template.render(schema=schema))
5555

56+
# Generate enums
57+
enum_template = env.get_template("enum.pyi")
58+
for enum in openapi.enums.values():
59+
module_path = models_dir / f"{enum.name}.py"
60+
module_path.write_text(enum_template.render(enum=enum))
61+

openapi_python_client/models/openapi.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,28 @@
66

77
import stringcase
88

9-
from .properties import Property, property_from_dict, DateTimeProperty, ListProperty, RefProperty, EnumProperty
9+
from .properties import Property, property_from_dict, ListProperty, RefProperty, EnumProperty
1010

1111

1212
class Method(Enum):
1313
""" HTTP Methods """
14+
1415
GET = "get"
1516
POST = "post"
1617
PATCH = "patch"
1718

1819

1920
class ParameterLocation(Enum):
2021
""" The places Parameters can be put when calling an Endpoint """
22+
2123
QUERY = "query"
2224
PATH = "path"
2325

2426

2527
@dataclass
2628
class Parameter:
2729
""" A parameter in an Endpoint """
30+
2831
location: ParameterLocation
2932
property: Property
3033

@@ -33,11 +36,7 @@ def from_dict(d: Dict, /) -> Parameter:
3336
""" Construct a parameter from it's OpenAPI dict form """
3437
return Parameter(
3538
location=ParameterLocation(d["in"]),
36-
property=property_from_dict(
37-
name=d["name"],
38-
required=d["required"],
39-
data=d["schema"],
40-
),
39+
property=property_from_dict(name=d["name"], required=d["required"], data=d["schema"],),
4140
)
4241

4342

@@ -46,6 +45,7 @@ class Endpoint:
4645
"""
4746
Describes a single endpoint on the server
4847
"""
48+
4949
path: str
5050
method: Method
5151
description: Optional[str]
@@ -120,19 +120,25 @@ class OpenAPI:
120120
security_schemes: Dict
121121
schemas: Dict[str, Schema]
122122
endpoints: List[Endpoint]
123-
enums: Dict[str, List[str]]
123+
enums: Dict[str, EnumProperty]
124124

125125
@staticmethod
126126
def from_dict(d: Dict, /) -> OpenAPI:
127127
""" Create an OpenAPI from dict """
128128
schemas = Schema.dict(d["components"]["schemas"])
129-
enums = {}
129+
enums: Dict[str, EnumProperty] = {}
130130
for schema in schemas.values():
131131
for prop in schema.properties:
132-
if isinstance(prop, EnumProperty):
133-
enum_class_name = stringcase.pascalcase(prop.name)
134-
enums[enum_class_name] = prop.values
135-
schema.relative_imports.add(f"from .{prop.name} import {enum_class_name}")
132+
if not isinstance(prop, EnumProperty):
133+
continue
134+
schema.relative_imports.add(f"from .{prop.name} import {prop.class_name}")
135+
if prop.class_name in enums:
136+
# We already have an enum with this name, make sure the values match
137+
assert (
138+
prop.values == enums[prop.class_name].values
139+
), f"Encountered conflicting enum named {prop.class_name}"
140+
141+
enums[prop.class_name] = prop
136142

137143
return OpenAPI(
138144
title=d["info"]["title"],

openapi_python_client/models/properties.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from dataclasses import dataclass
1+
from dataclasses import dataclass, field
22
from typing import Optional, List, Dict, Union, ClassVar
33
import stringcase
44

@@ -40,6 +40,7 @@ def to_string(self) -> str:
4040
@dataclass
4141
class DateTimeProperty(Property):
4242
""" A property of type datetime.datetime """
43+
4344
_type_string: ClassVar[str] = "datetime"
4445

4546
def to_string(self) -> str:
@@ -80,6 +81,7 @@ def to_string(self) -> str:
8081
@dataclass
8182
class BooleanProperty(Property):
8283
""" Property for bool """
84+
8385
_type_string: ClassVar[str] = "bool"
8486

8587
def to_string(self) -> str:
@@ -109,19 +111,38 @@ def to_string(self) -> str:
109111
class EnumProperty(Property):
110112
""" A property that should use an enum """
111113

112-
values: List[str]
114+
values: Dict[str, str]
115+
class_name: str = field(init=False)
116+
117+
def __post_init__(self):
118+
self.class_name = stringcase.pascalcase(self.name)
113119

114120
def get_type_string(self):
115121
""" Get a string representation of type that should be used when declaring this property """
116-
class_name = stringcase.pascalcase(self.name)
122+
117123
if self.required:
118-
return class_name
119-
return f"Optional[{class_name}]"
124+
return self.class_name
125+
return f"Optional[{self.class_name}]"
120126

121127
def to_string(self) -> str:
122128
""" How this should be declared in a dataclass """
123129
return f"{self.name}: {self.get_type_string()}"
124130

131+
@staticmethod
132+
def values_from_list(l: List[str], /) -> Dict[str, str]:
133+
""" Convert a list of values into dict of {name: value} """
134+
output: Dict[str, str] = {}
135+
136+
for i, value in enumerate(l):
137+
if value.isalpha():
138+
key = value.upper()
139+
else:
140+
key = f"VALUE_{i}"
141+
assert key not in output, f"Duplicate key {key} in Enum"
142+
output[key] = value
143+
144+
return output
145+
125146

126147
@dataclass
127148
class RefProperty(Property):
@@ -165,23 +186,23 @@ def property_from_dict(
165186
) -> Property:
166187
""" Generate a Property from the OpenAPI dictionary representation of it """
167188
if "enum" in data:
168-
return EnumProperty(name=name, required=required, values=data["enum"],)
189+
return EnumProperty(name=name, required=required, values=EnumProperty.values_from_list(data["enum"]))
169190
if "$ref" in data:
170191
ref = data["$ref"].split("/")[-1]
171-
return RefProperty(name=name, required=required, ref=ref,)
192+
return RefProperty(name=name, required=required, ref=ref)
172193
if data["type"] == "string":
173194
if "format" not in data:
174195
return StringProperty(
175196
name=name, default=data.get("default"), required=required, pattern=data.get("pattern"),
176197
)
177198
elif data["format"] == "date-time":
178-
return DateTimeProperty(name=name, required=required,)
199+
return DateTimeProperty(name=name, required=required)
179200
elif data["type"] == "number":
180-
return FloatProperty(name=name, default=data.get("default"), required=required,)
201+
return FloatProperty(name=name, default=data.get("default"), required=required)
181202
elif data["type"] == "integer":
182-
return IntProperty(name=name, default=data.get("default"), required=required,)
203+
return IntProperty(name=name, default=data.get("default"), required=required)
183204
elif data["type"] == "boolean":
184-
return BooleanProperty(name=name, required=required,)
205+
return BooleanProperty(name=name, required=required)
185206
elif data["type"] == "array":
186207
ref = None
187208
if "$ref" in data["items"]:
@@ -191,5 +212,5 @@ def property_from_dict(
191212
_type = _openapi_types_to_python_type_strings[data["items"]["type"]]
192213
return ListProperty(name=name, required=required, type=_type, ref=ref)
193214
elif data["type"] == "object":
194-
return DictProperty(name=name, required=required,)
215+
return DictProperty(name=name, required=required)
195216
raise ValueError(f"Did not recognize type of {data}")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from enum import Enum
2+
3+
class {{ enum.class_name }}(Enum):
4+
{% for key, value in enum.values.items() %}
5+
{{ key }} = "{{ value }}"
6+
{% endfor %}

0 commit comments

Comments
 (0)