Skip to content

Commit eeb1326

Browse files
authored
Fix mixed case properties & params (#972)
Also fixes some Ruff stuff, since it was getting in the way of debugging e2e tests --------- Co-authored-by: Dylan Anthony <[email protected]>
1 parent f44e67f commit eeb1326

27 files changed

+589
-266
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
default: patch
3+
---
4+
5+
# Allow parameters with names differing only by case
6+
7+
If you have two parameters to an endpoint named `mixedCase` and `mixed_case`, previously, this was a conflict and the endpoint would not be generated.
8+
Now, the generator will skip snake-casing the parameters and use the names as-is. Note that this means if neither of the parameters _was_ snake case, neither _will be_ in the generated code.
9+
10+
Fixes #922 reported by @macmoritz & @benedikt-bartscher.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
default: patch
3+
---
4+
5+
# Fix naming conflicts with properties in models with mixed casing
6+
7+
If you had an object with two properties, where the names differed only by case, conflicting properties would be generated in the model, which then failed the linting step (when using default config). For example, this:
8+
9+
```yaml
10+
type: "object"
11+
properties:
12+
MixedCase:
13+
type: "string"
14+
mixedCase:
15+
type: "string"
16+
```
17+
18+
Would generate a class like this:
19+
20+
```python
21+
class MyModel:
22+
mixed_case: str
23+
mixed_case: str
24+
```
25+
26+
Now, neither of the properties will be forced into snake case, and the generated code will look like this:
27+
28+
```python
29+
class MyModel:
30+
MixedCase: str
31+
mixedCase: str
32+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
default: major
3+
---
4+
5+
# For custom templates, changed type of endpoint parameters
6+
7+
**This does not affect projects that are not using `--custom-template-path`**
8+
9+
The type of these properties on `Endpoint` has been changed from `Dict[str, Property]` to `List[Property]`:
10+
11+
- `path_parameters`
12+
- `query_parameters`
13+
- `header_parameters`
14+
- `cookie_parameters`
15+
16+
If your templates are very close to the default templates, you can probably just remove `.values()` anywhere it appears.
17+
18+
The type of `iter_all_parameters()` is also different, you probably want `list_all_parameters()` instead.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
default: major
3+
---
4+
5+
# Updated generated config for Ruff v0.2
6+
7+
This only affects projects using the `generate` command, not the `update` command. The `pyproject.toml` file generated which configures Ruff for linting and formatting has been updated to the 0.2 syntax, which means it will no longer work with Ruff 0.1.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
default: major
3+
---
4+
5+
# Updated naming strategy for conflicting properties
6+
7+
While fixing #922, some naming strategies were updated. These should mostly be backwards compatible, but there may be
8+
some small differences in generated code. Make sure to check your diffs before pushing updates to consumers!

end_to_end_tests/baseline_openapi_3.0.json

+44
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,50 @@
14131413
}
14141414
}
14151415
},
1416+
"/naming/mixed-case": {
1417+
"get": {
1418+
"tags": ["naming"],
1419+
"operationId": "mixed_case",
1420+
"parameters": [
1421+
{
1422+
"name": "mixed_case",
1423+
"in": "query",
1424+
"required": true,
1425+
"schema": {
1426+
"type": "string"
1427+
}
1428+
},
1429+
{
1430+
"name": "mixedCase",
1431+
"in": "query",
1432+
"required": true,
1433+
"schema": {
1434+
"type": "string"
1435+
}
1436+
}
1437+
],
1438+
"responses": {
1439+
"200": {
1440+
"description": "Successful response",
1441+
"content": {
1442+
"application/json": {
1443+
"schema": {
1444+
"type": "object",
1445+
"properties": {
1446+
"mixed_case": {
1447+
"type": "string"
1448+
},
1449+
"mixedCase": {
1450+
"type": "string"
1451+
}
1452+
}
1453+
}
1454+
}
1455+
}
1456+
}
1457+
}
1458+
}
1459+
},
14161460
"/parameter-references/{path_param}": {
14171461
"get": {
14181462
"tags": [

end_to_end_tests/baseline_openapi_3.1.yaml

+44
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,50 @@ info:
14071407
}
14081408
}
14091409
},
1410+
"/naming/mixed-case": {
1411+
"get": {
1412+
"tags": [ "naming" ],
1413+
"operationId": "mixed_case",
1414+
"parameters": [
1415+
{
1416+
"name": "mixed_case",
1417+
"in": "query",
1418+
"required": true,
1419+
"schema": {
1420+
"type": "string"
1421+
}
1422+
},
1423+
{
1424+
"name": "mixedCase",
1425+
"in": "query",
1426+
"required": true,
1427+
"schema": {
1428+
"type": "string"
1429+
}
1430+
}
1431+
],
1432+
"responses": {
1433+
"200": {
1434+
"description": "Successful response",
1435+
"content": {
1436+
"application/json": {
1437+
"schema": {
1438+
"type": "object",
1439+
"properties": {
1440+
"mixed_case": {
1441+
"type": "string"
1442+
},
1443+
"mixedCase": {
1444+
"type": "string"
1445+
}
1446+
}
1447+
}
1448+
}
1449+
}
1450+
}
1451+
}
1452+
}
1453+
},
14101454
"/parameter-references/{path_param}": {
14111455
"get": {
14121456
"tags": [

end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/naming/__init__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import types
44

5-
from . import post_naming_property_conflict_with_import
5+
from . import mixed_case, post_naming_property_conflict_with_import
66

77

88
class NamingEndpoints:
99
@classmethod
1010
def post_naming_property_conflict_with_import(cls) -> types.ModuleType:
1111
return post_naming_property_conflict_with_import
12+
13+
@classmethod
14+
def mixed_case(cls) -> types.ModuleType:
15+
return mixed_case
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
from http import HTTPStatus
2+
from typing import Any, Dict, Optional, Union
3+
4+
import httpx
5+
6+
from ... import errors
7+
from ...client import AuthenticatedClient, Client
8+
from ...models.mixed_case_response_200 import MixedCaseResponse200
9+
from ...types import UNSET, Response
10+
11+
12+
def _get_kwargs(
13+
*,
14+
mixed_case: str,
15+
mixedCase: str,
16+
) -> Dict[str, Any]:
17+
params: Dict[str, Any] = {}
18+
19+
params["mixed_case"] = mixed_case
20+
21+
params["mixedCase"] = mixedCase
22+
23+
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
24+
25+
_kwargs: Dict[str, Any] = {
26+
"method": "get",
27+
"url": "/naming/mixed-case",
28+
"params": params,
29+
}
30+
31+
return _kwargs
32+
33+
34+
def _parse_response(
35+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
36+
) -> Optional[MixedCaseResponse200]:
37+
if response.status_code == HTTPStatus.OK:
38+
response_200 = MixedCaseResponse200.from_dict(response.json())
39+
40+
return response_200
41+
if client.raise_on_unexpected_status:
42+
raise errors.UnexpectedStatus(response.status_code, response.content)
43+
else:
44+
return None
45+
46+
47+
def _build_response(
48+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
49+
) -> Response[MixedCaseResponse200]:
50+
return Response(
51+
status_code=HTTPStatus(response.status_code),
52+
content=response.content,
53+
headers=response.headers,
54+
parsed=_parse_response(client=client, response=response),
55+
)
56+
57+
58+
def sync_detailed(
59+
*,
60+
client: Union[AuthenticatedClient, Client],
61+
mixed_case: str,
62+
mixedCase: str,
63+
) -> Response[MixedCaseResponse200]:
64+
"""
65+
Args:
66+
mixed_case (str):
67+
mixedCase (str):
68+
69+
Raises:
70+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
71+
httpx.TimeoutException: If the request takes longer than Client.timeout.
72+
73+
Returns:
74+
Response[MixedCaseResponse200]
75+
"""
76+
77+
kwargs = _get_kwargs(
78+
mixed_case=mixed_case,
79+
mixedCase=mixedCase,
80+
)
81+
82+
response = client.get_httpx_client().request(
83+
**kwargs,
84+
)
85+
86+
return _build_response(client=client, response=response)
87+
88+
89+
def sync(
90+
*,
91+
client: Union[AuthenticatedClient, Client],
92+
mixed_case: str,
93+
mixedCase: str,
94+
) -> Optional[MixedCaseResponse200]:
95+
"""
96+
Args:
97+
mixed_case (str):
98+
mixedCase (str):
99+
100+
Raises:
101+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
102+
httpx.TimeoutException: If the request takes longer than Client.timeout.
103+
104+
Returns:
105+
MixedCaseResponse200
106+
"""
107+
108+
return sync_detailed(
109+
client=client,
110+
mixed_case=mixed_case,
111+
mixedCase=mixedCase,
112+
).parsed
113+
114+
115+
async def asyncio_detailed(
116+
*,
117+
client: Union[AuthenticatedClient, Client],
118+
mixed_case: str,
119+
mixedCase: str,
120+
) -> Response[MixedCaseResponse200]:
121+
"""
122+
Args:
123+
mixed_case (str):
124+
mixedCase (str):
125+
126+
Raises:
127+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
128+
httpx.TimeoutException: If the request takes longer than Client.timeout.
129+
130+
Returns:
131+
Response[MixedCaseResponse200]
132+
"""
133+
134+
kwargs = _get_kwargs(
135+
mixed_case=mixed_case,
136+
mixedCase=mixedCase,
137+
)
138+
139+
response = await client.get_async_httpx_client().request(**kwargs)
140+
141+
return _build_response(client=client, response=response)
142+
143+
144+
async def asyncio(
145+
*,
146+
client: Union[AuthenticatedClient, Client],
147+
mixed_case: str,
148+
mixedCase: str,
149+
) -> Optional[MixedCaseResponse200]:
150+
"""
151+
Args:
152+
mixed_case (str):
153+
mixedCase (str):
154+
155+
Raises:
156+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
157+
httpx.TimeoutException: If the request takes longer than Client.timeout.
158+
159+
Returns:
160+
MixedCaseResponse200
161+
"""
162+
163+
return (
164+
await asyncio_detailed(
165+
client=client,
166+
mixed_case=mixed_case,
167+
mixedCase=mixedCase,
168+
)
169+
).parsed

end_to_end_tests/golden-record/my_test_api_client/models/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from .http_validation_error import HTTPValidationError
4141
from .import_ import Import
4242
from .json_like_body import JsonLikeBody
43+
from .mixed_case_response_200 import MixedCaseResponse200
4344
from .model_from_all_of import ModelFromAllOf
4445
from .model_name import ModelName
4546
from .model_reference_with_periods import ModelReferenceWithPeriods
@@ -116,6 +117,7 @@
116117
"HTTPValidationError",
117118
"Import",
118119
"JsonLikeBody",
120+
"MixedCaseResponse200",
119121
"ModelFromAllOf",
120122
"ModelName",
121123
"ModelReferenceWithPeriods",

0 commit comments

Comments
 (0)