Skip to content

Commit e494aa0

Browse files
committed
Merge branch 'master' into pr/30
2 parents ab9563a + 66970cb commit e494aa0

26 files changed

+340
-61
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Fixes
1212
- Fixed some typing issues in generated clients and incorporate mypy into end to end tests (#32). Thanks @acgray!
13+
- Properly handle camelCase endpoint names and properties (#29, #36). Thanks @acgray!
1314

1415
## 0.2.1 - 2020-03-22
1516
### Fixes

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,17 @@ You can pass a YAML (or JSON) file to openapi-python-client in order to change s
6767
are supported:
6868

6969
### class_overrides
70-
Used to change the name of generated model classes, especially useful if you have a name like ABCModel which, when
71-
converted to snake case for module naming will be a_b_c_model. This param should be a mapping of existing class name
72-
(usually a key in the "schemas" section of your OpenAPI document) to class_name and module_name.
70+
Used to change the name of generated model classes. This param should be a mapping of existing class name
71+
(usually a key in the "schemas" section of your OpenAPI document) to class_name and module_name. As an example, if the
72+
name of the a model in OpenAPI (and therefore the generated class name) was something like "_PrivateInternalLongName"
73+
and you want the generated client's model to be called "ShortName" in a module called "short_name" you could do this:
7374

7475
Example:
7576
```yaml
7677
class_overrides:
77-
ABCModel:
78-
class_name: ABCModel
79-
module_name: abc_model
78+
_PrivateInternalLongName:
79+
class_name: ShortName
80+
module_name: short_name
8081
```
8182
8283
The easiest way to find what needs to be overridden is probably to generate your client and go look at everything in the

openapi_python_client/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import yaml
1313
from jinja2 import Environment, PackageLoader
1414

15+
from openapi_python_client import utils
16+
1517
from .openapi_parser import OpenAPI, import_string_from_reference
1618

1719
__version__ = version(__package__)
@@ -61,6 +63,8 @@ def _get_json(*, url: Optional[str], path: Optional[Path]) -> Dict[str, Any]:
6163

6264

6365
class _Project:
66+
TEMPLATE_FILTERS = {"snakecase": utils.snake_case}
67+
6468
def __init__(self, *, openapi: OpenAPI) -> None:
6569
self.openapi: OpenAPI = openapi
6670
self.env: Environment = Environment(loader=PackageLoader(__package__), trim_blocks=True, lstrip_blocks=True)
@@ -72,6 +76,8 @@ def __init__(self, *, openapi: OpenAPI) -> None:
7276
self.package_dir: Path = self.project_dir / self.package_name
7377
self.package_description = f"A client library for accessing {self.openapi.title}"
7478

79+
self.env.filters.update(self.TEMPLATE_FILTERS)
80+
7581
def build(self) -> None:
7682
""" Create the project from templates """
7783

openapi_python_client/openapi_parser/properties.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from dataclasses import dataclass, field
22
from typing import Any, ClassVar, Dict, List, Optional
33

4+
from openapi_python_client import utils
5+
46
from .reference import Reference
57

68

@@ -15,6 +17,11 @@ class Property:
1517
constructor_template: ClassVar[Optional[str]] = None
1618
_type_string: ClassVar[str]
1719

20+
python_name: str = field(init=False)
21+
22+
def __post_init__(self) -> None:
23+
self.python_name = utils.snake_case(self.name)
24+
1825
def get_type_string(self) -> str:
1926
""" Get a string representation of type that should be used when declaring this property """
2027
if self.required:
@@ -31,13 +38,13 @@ def to_string(self) -> str:
3138
default = None
3239

3340
if default is not None:
34-
return f"{self.name}: {self.get_type_string()} = {self.default}"
41+
return f"{self.python_name}: {self.get_type_string()} = {self.default}"
3542
else:
36-
return f"{self.name}: {self.get_type_string()}"
43+
return f"{self.python_name}: {self.get_type_string()}"
3744

3845
def transform(self) -> str:
3946
""" What it takes to turn this object into a native python type """
40-
return self.name
47+
return self.python_name
4148

4249
def constructor_from_dict(self, dict_name: str) -> str:
4350
""" How to load this property from a dict (used in generated model from_dict function """
@@ -57,6 +64,7 @@ class StringProperty(Property):
5764
_type_string: ClassVar[str] = "str"
5865

5966
def __post_init__(self) -> None:
67+
super().__post_init__()
6068
if self.default is not None:
6169
self.default = f'"{self.default}"'
6270

@@ -146,6 +154,7 @@ class EnumListProperty(Property):
146154
constructor_template: ClassVar[str] = "enum_list_property.pyi"
147155

148156
def __post_init__(self) -> None:
157+
super().__post_init__()
149158
self.reference = Reference.from_ref(self.name)
150159

151160
def get_type_string(self) -> str:
@@ -163,6 +172,7 @@ class EnumProperty(Property):
163172
reference: Reference = field(init=False)
164173

165174
def __post_init__(self) -> None:
175+
super().__post_init__()
166176
self.reference = Reference.from_ref(self.name)
167177
inverse_values = {v: k for k, v in self.values.items()}
168178
if self.default is not None:
@@ -177,7 +187,7 @@ def get_type_string(self) -> str:
177187

178188
def transform(self) -> str:
179189
""" Output to the template, convert this Enum into a JSONable value """
180-
return f"{self.name}.value"
190+
return f"{self.python_name}.value"
181191

182192
def constructor_from_dict(self, dict_name: str) -> str:
183193
""" How to load this property from a dict (used in generated model from_dict function """
@@ -218,7 +228,7 @@ def get_type_string(self) -> str:
218228

219229
def transform(self) -> str:
220230
""" Convert this into a JSONable value """
221-
return f"{self.name}.to_dict()"
231+
return f"{self.python_name}.to_dict()"
222232

223233

224234
@dataclass

openapi_python_client/openapi_parser/reference.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dataclasses import dataclass
66
from typing import Dict
77

8-
import stringcase
8+
from .. import utils
99

1010
class_overrides: Dict[str, Reference] = {}
1111

@@ -21,9 +21,9 @@ class Reference:
2121
def from_ref(ref: str) -> Reference:
2222
""" Get a Reference from the openapi #/schemas/blahblah string """
2323
ref_value = ref.split("/")[-1]
24-
class_name = stringcase.pascalcase(ref_value)
24+
class_name = utils.pascal_case(ref_value)
2525

2626
if class_name in class_overrides:
2727
return class_overrides[class_name]
2828

29-
return Reference(class_name=class_name, module_name=stringcase.snakecase(ref_value),)
29+
return Reference(class_name=class_name, module_name=utils.snake_case(ref_value),)

openapi_python_client/templates/async_endpoint_module.pyi

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ from ..errors import ApiResponseError
1212
{% for endpoint in collection.endpoints %}
1313

1414

15-
async def {{ endpoint.name }}(
15+
async def {{ endpoint.name | snakecase }}(
1616
*,
1717
{# Proper client based on whether or not the endpoint requires authentication #}
1818
{% if endpoint.requires_security %}
@@ -42,7 +42,12 @@ async def {{ endpoint.name }}(
4242
{% endfor %}
4343
]:
4444
""" {{ endpoint.description }} """
45-
url = f"{client.base_url}{{ endpoint.path }}"
45+
url = "{}{{ endpoint.path }}".format(
46+
client.base_url
47+
{%- for parameter in endpoint.path_parameters -%}
48+
,{{parameter.name}}={{parameter.python_name}}
49+
{%- endfor -%}
50+
)
4651

4752
{% if endpoint.query_parameters %}
4853
params = {
@@ -54,8 +59,8 @@ async def {{ endpoint.name }}(
5459
}
5560
{% for parameter in endpoint.query_parameters %}
5661
{% if not parameter.required %}
57-
if {{ parameter.name }} is not None:
58-
params["{{ parameter.name }}"] = {{ parameter.transform() }}
62+
if {{ parameter.python_name }} is not None:
63+
params["{{ parameter.name }}"] = str({{ parameter.transform() }})
5964
{% endif %}
6065
{% endfor %}
6166
{% endif %}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% if property.required %}
2-
{{ property.name }} = datetime.fromisoformat(d["{{ property.name }}"])
2+
{{ property.python_name }} = datetime.fromisoformat(d["{{ property.name }}"])
33
{% else %}
4-
{{ property.name }} = None
5-
if ({{ property.name }}_string := d.get("{{ property.name }}")) is not None:
6-
{{ property.name }} = datetime.fromisoformat(cast(str, {{ property.name }}_string))
4+
{{ property.python_name }} = None
5+
if ({{ property.python_name }}_string := d.get("{{ property.name }}")) is not None:
6+
{{ property.python_name }} = datetime.fromisoformat(cast(str, {{ property.python_name }}_string))
77
{% endif %}

openapi_python_client/templates/endpoint_module.pyi

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ from ..errors import ApiResponseError
1212
{% for endpoint in collection.endpoints %}
1313

1414

15-
def {{ endpoint.name }}(
15+
def {{ endpoint.name | snakecase }}(
1616
*,
1717
{# Proper client based on whether or not the endpoint requires authentication #}
1818
{% if endpoint.requires_security %}
@@ -42,7 +42,12 @@ def {{ endpoint.name }}(
4242
{% endfor %}
4343
]:
4444
""" {{ endpoint.description }} """
45-
url = f"{client.base_url}{{ endpoint.path }}"
45+
url = "{}{{ endpoint.path }}".format(
46+
client.base_url
47+
{%- for parameter in endpoint.path_parameters -%}
48+
,{{parameter.name}}={{parameter.python_name}}
49+
{%- endfor -%}
50+
)
4651

4752
{% if endpoint.query_parameters %}
4853
params = {
@@ -54,8 +59,8 @@ def {{ endpoint.name }}(
5459
}
5560
{% for parameter in endpoint.query_parameters %}
5661
{% if not parameter.required %}
57-
if {{ parameter.name }} is not None:
58-
params["{{ parameter.name }}"] = {{ parameter.transform() }}
62+
if {{ parameter.python_name }} is not None:
63+
params["{{ parameter.name }}"] = str({{ parameter.transform() }})
5964
{% endif %}
6065
{% endfor %}
6166
{% endif %}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
{{ property.name }} = []
2-
for {{ property.name }}_item in d.get("{{ property.name }}", []):
3-
{{ property.name }}.append({{ property.reference.class_name }}({{ property.name }}_item))
1+
{{ property.python_name }} = []
2+
for {{ property.python_name }}_item in d.get("{{ property.name }}", []):
3+
{{ property.python_name }}.append({{ property.reference.class_name }}({{ property.python_name }}_item))

openapi_python_client/templates/model.pyi

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ class {{ schema.reference.class_name }}:
2121
"{{ property.name }}": self.{{ property.transform() }},
2222
{% endfor %}
2323
{% for property in schema.optional_properties %}
24-
"{{ property.name }}": self.{{ property.transform() }} if self.{{ property.name }} is not None else None,
25-
{% endfor %}
24+
"{{ property.name }}": self.{{ property.transform() }} if self.{{ property.python_name }} is not None else None,
25+
{% endfor %}
2626
}
2727

2828
@staticmethod
@@ -32,12 +32,12 @@ class {{ schema.reference.class_name }}:
3232
{% if property.constructor_template %}
3333
{% include property.constructor_template %}
3434
{% else %}
35-
{{ property.name }} = {{ property.constructor_from_dict("d") }}
35+
{{ property.python_name }} = {{ property.constructor_from_dict("d") }}
3636
{% endif %}
3737

3838
{% endfor %}
3939
return {{ schema.reference.class_name }}(
4040
{% for property in schema.required_properties + schema.optional_properties %}
41-
{{ property.name }}={{ property.name }},
41+
{{ property.python_name }}={{ property.python_name }},
4242
{% endfor %}
4343
)

0 commit comments

Comments
 (0)