Skip to content

Commit 59364b1

Browse files
committed
refactor: Use location prefix for all duplicate params
1 parent 2b79f9f commit 59364b1

File tree

7 files changed

+53
-36
lines changed

7 files changed

+53
-36
lines changed

end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ def _get_kwargs(
1010
*,
1111
client: Client,
1212
param_path: Union[Unset, str] = UNSET,
13-
param: Union[Unset, str] = UNSET,
13+
param_query: Union[Unset, str] = UNSET,
1414
) -> Dict[str, Any]:
1515
url = "{}/same-name-multiple-locations/{param}".format(client.base_url, param=param_path)
1616

1717
headers: Dict[str, Any] = client.get_headers()
1818
cookies: Dict[str, Any] = client.get_cookies()
1919

2020
params: Dict[str, Any] = {
21-
"param": param,
21+
"param": param_query,
2222
}
2323
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
2424

@@ -44,12 +44,12 @@ def sync_detailed(
4444
*,
4545
client: Client,
4646
param_path: Union[Unset, str] = UNSET,
47-
param: Union[Unset, str] = UNSET,
47+
param_query: Union[Unset, str] = UNSET,
4848
) -> Response[None]:
4949
kwargs = _get_kwargs(
5050
client=client,
5151
param_path=param_path,
52-
param=param,
52+
param_query=param_query,
5353
)
5454

5555
response = httpx.get(
@@ -63,12 +63,12 @@ async def asyncio_detailed(
6363
*,
6464
client: Client,
6565
param_path: Union[Unset, str] = UNSET,
66-
param: Union[Unset, str] = UNSET,
66+
param_query: Union[Unset, str] = UNSET,
6767
) -> Response[None]:
6868
kwargs = _get_kwargs(
6969
client=client,
7070
param_path=param_path,
71-
param=param,
71+
param_query=param_query,
7272
)
7373

7474
async with httpx.AsyncClient() as _client:

openapi_python_client/parser/openapi.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import itertools
12
from copy import deepcopy
23
from dataclasses import dataclass, field
3-
from enum import Enum
44
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
55

66
from pydantic import ValidationError
@@ -13,15 +13,6 @@
1313
from .responses import Response, response_from_data
1414

1515

16-
class ParameterLocation(str, Enum):
17-
"""The places Parameters can be put when calling an Endpoint"""
18-
19-
QUERY = "query"
20-
PATH = "path"
21-
HEADER = "header"
22-
COOKIE = "cookie"
23-
24-
2516
def import_string_from_class(class_: Class, prefix: str = "") -> str:
2617
"""Create a string which is used to import a reference"""
2718
return f"from {prefix}.{class_.module_name} import {class_.name}"
@@ -217,7 +208,7 @@ def _add_parameters(
217208
*, endpoint: "Endpoint", data: Union[oai.Operation, oai.PathItem], schemas: Schemas, config: Config
218209
) -> Tuple[Union["Endpoint", ParseError], Schemas]:
219210
endpoint = deepcopy(endpoint)
220-
used_python_names = set()
211+
used_python_names: Dict[str, Tuple[Property, oai.ParameterLocation]] = {}
221212
if data.parameters is None:
222213
return endpoint, schemas
223214
for param in data.parameters:
@@ -235,26 +226,37 @@ def _add_parameters(
235226
return ParseError(detail=f"cannot parse parameter of endpoint {endpoint.name}", data=prop.data), schemas
236227

237228
if prop.python_name in used_python_names:
229+
duplicate, duplicate_location = used_python_names[prop.python_name]
230+
if duplicate.python_name == prop.python_name: # Existing should be converted too for consistency
231+
duplicate.set_python_name(f"{duplicate.python_name}_{duplicate_location}")
238232
prop.set_python_name(f"{prop.python_name}_{param.param_in}")
233+
else:
234+
used_python_names[prop.python_name] = (prop, param.param_in)
239235

240-
if prop.python_name in used_python_names:
241-
return (
242-
ParseError(detail=f"Encountered duplicate properties named {prop.python_name}", data=data),
243-
schemas,
244-
)
245-
used_python_names.add(prop.python_name)
246236
endpoint.relative_imports.update(prop.get_imports(prefix="..."))
247237

248-
if param.param_in == ParameterLocation.QUERY:
238+
if param.param_in == oai.ParameterLocation.QUERY:
249239
endpoint.query_parameters.append(prop)
250-
elif param.param_in == ParameterLocation.PATH:
240+
elif param.param_in == oai.ParameterLocation.PATH:
251241
endpoint.path_parameters.append(prop)
252-
elif param.param_in == ParameterLocation.HEADER:
242+
elif param.param_in == oai.ParameterLocation.HEADER:
253243
endpoint.header_parameters.append(prop)
254-
elif param.param_in == ParameterLocation.COOKIE:
244+
elif param.param_in == oai.ParameterLocation.COOKIE:
255245
endpoint.cookie_parameters.append(prop)
256246
else:
257247
return ParseError(data=param, detail="Parameter must be declared in path or query"), schemas
248+
249+
name_check = set()
250+
for prop in itertools.chain(
251+
endpoint.query_parameters, endpoint.path_parameters, endpoint.header_parameters, endpoint.cookie_parameters
252+
):
253+
if prop.python_name in name_check:
254+
return (
255+
ParseError(data=data, detail=f"Could not reconcile duplicate parameters named {prop.python_name}"),
256+
schemas,
257+
)
258+
name_check.add(prop.python_name)
259+
258260
return endpoint, schemas
259261

260262
@staticmethod

openapi_python_client/schema/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"OpenAPI",
44
"Operation",
55
"Parameter",
6+
"ParameterLocation",
67
"PathItem",
78
"Reference",
89
"RequestBody",
@@ -18,6 +19,7 @@
1819
from .openapi_schema_pydantic import MediaType
1920
from .openapi_schema_pydantic import OpenAPI as _OpenAPI
2021
from .openapi_schema_pydantic import Operation, Parameter, PathItem, Reference, RequestBody, Response, Responses, Schema
22+
from .parameter_location import ParameterLocation
2123

2224
regex = re.compile(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)")
2325

openapi_python_client/schema/openapi_schema_pydantic/header.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pydantic import Field
22

3+
from ..parameter_location import ParameterLocation
34
from .parameter import Parameter
45

56

@@ -14,7 +15,7 @@ class Header(Parameter):
1415
"""
1516

1617
name = Field(default="", const=True)
17-
param_in = Field(default="header", const=True, alias="in")
18+
param_in = Field(default=ParameterLocation.HEADER, const=True, alias="in")
1819

1920
class Config:
2021
allow_population_by_field_name = True

openapi_python_client/schema/openapi_schema_pydantic/parameter.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pydantic import BaseModel, Field
44

5+
from ..parameter_location import ParameterLocation
56
from .example import Example
67
from .media_type import MediaType
78
from .reference import Reference
@@ -30,7 +31,7 @@ class Parameter(BaseModel):
3031
- For all other cases, the `name` corresponds to the parameter name used by the [`in`](#parameterIn) property.
3132
"""
3233

33-
param_in: str = Field(alias="in")
34+
param_in: ParameterLocation = Field(alias="in")
3435
"""
3536
**REQUIRED**. The location of the parameter. Possible values are `"query"`, `"header"`, `"path"` or `"cookie"`.
3637
"""
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from enum import Enum
2+
3+
4+
class ParameterLocation(str, Enum):
5+
"""The places Parameters can be put when calling an Endpoint"""
6+
7+
QUERY = "query"
8+
PATH = "path"
9+
HEADER = "header"
10+
COOKIE = "cookie"

tests/test_parser/test_openapi.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -547,17 +547,18 @@ def test__add_parameters_duplicate_properties(self, mocker):
547547
from openapi_python_client.parser.openapi import Endpoint, Schemas
548548

549549
endpoint = self.make_endpoint()
550-
parsed_schemas = mocker.MagicMock()
551-
mocker.patch(
552-
f"{MODULE_NAME}.property_from_data", return_value=(mocker.MagicMock(python_name="test"), parsed_schemas)
550+
param = oai.Parameter.construct(
551+
name="test", required=True, param_schema=oai.Schema.construct(type="string"), param_in="path"
553552
)
554-
param = oai.Parameter.construct(name="test", required=True, param_schema=mocker.MagicMock(), param_in="path")
555-
data = oai.Operation.construct(parameters=[param, param, param])
553+
data = oai.Operation.construct(parameters=[param, param])
556554
schemas = Schemas()
557555
config = MagicMock()
558556

559557
result = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=schemas, config=config)
560-
assert result == (ParseError(data=data, detail="Encountered duplicate properties named test"), parsed_schemas)
558+
assert result == (
559+
ParseError(data=data, detail="Could not reconcile duplicate parameters named test_path"),
560+
schemas,
561+
)
561562

562563
def test__add_parameters_duplicate_properties_different_location(self):
563564
from openapi_python_client.parser.openapi import Endpoint, Schemas
@@ -579,7 +580,7 @@ def test__add_parameters_duplicate_properties_different_location(self):
579580
config=config,
580581
)[0]
581582
assert isinstance(result, Endpoint)
582-
assert result.path_parameters[0].python_name == "test"
583+
assert result.path_parameters[0].python_name == "test_path"
583584
assert result.path_parameters[0].name == "test"
584585
assert result.query_parameters[0].python_name == "test_query"
585586
assert result.query_parameters[0].name == "test"

0 commit comments

Comments
 (0)