Skip to content

Path parameters as function positional arguments #429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Ensure path parameters are ordered by appearance in path
  • Loading branch information
tsotnikov committed May 18, 2021
commit b9e8057c3dc96057ee609f7a61311a0f832b7b65
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@


def _get_kwargs(
param_1: str,
param_4: str,
param_2: int,
param_1: str,
param_3: int,
*,
client: Client,
) -> Dict[str, Any]:
url = "{}/multiple-path-parameters/{param1}/{param2}".format(client.base_url, param1=param_1, param2=param_2)
url = "{}/multiple-path-parameters/{param4}/{param2}/{param1}/{param3}".format(
client.base_url, param4=param_4, param2=param_2, param1=param_1, param3=param_3
)

headers: Dict[str, Any] = client.get_headers()
cookies: Dict[str, Any] = client.get_cookies()
Expand All @@ -35,14 +39,18 @@ def _build_response(*, response: httpx.Response) -> Response[None]:


def sync_detailed(
param_1: str,
param_4: str,
param_2: int,
param_1: str,
param_3: int,
*,
client: Client,
) -> Response[None]:
kwargs = _get_kwargs(
param_1=param_1,
param_4=param_4,
param_2=param_2,
param_1=param_1,
param_3=param_3,
client=client,
)

Expand All @@ -54,14 +62,18 @@ def sync_detailed(


async def asyncio_detailed(
param_1: str,
param_4: str,
param_2: int,
param_1: str,
param_3: int,
*,
client: Client,
) -> Response[None]:
kwargs = _get_kwargs(
param_1=param_1,
param_4=param_4,
param_2=param_2,
param_1=param_1,
param_3=param_3,
client=client,
)

Expand Down
24 changes: 21 additions & 3 deletions end_to_end_tests/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -795,8 +795,8 @@
}
}
},
"/multiple-path-parameters/{param1}/{param2}": {
"description": "Test with multiple path parameters",
"/multiple-path-parameters/{param4}/{param2}/{param1}/{param3}": {
"description": "Test that multiple path parameters are ordered by appearance in path",
"get": {
"tags": [
"parameters"
Expand Down Expand Up @@ -825,7 +825,25 @@
"description": "Success"
}
}
}
},
"parameters": [
{
"name": "param4",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "param3",
"in": "path",
"required": true,
"schema": {
"type": "integer"
}
}
]
}
},
"components": {
Expand Down
19 changes: 19 additions & 0 deletions openapi_python_client/parser/openapi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import itertools
import re
from copy import deepcopy
from dataclasses import dataclass, field
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
Expand All @@ -12,6 +13,8 @@
from .properties import Class, EnumProperty, ModelProperty, Property, Schemas, build_schemas, property_from_data
from .responses import Response, response_from_data

_PATH_PARAM_REGEX = re.compile("{([a-zA-Z_][a-zA-Z0-9_]*)}")


def import_string_from_class(class_: Class, prefix: str = "") -> str:
"""Create a string which is used to import a reference"""
Expand Down Expand Up @@ -49,6 +52,8 @@ def from_data(
endpoint, schemas = Endpoint._add_parameters(
endpoint=endpoint, data=path_data, schemas=schemas, config=config
)
if not isinstance(endpoint, ParseError):
endpoint = Endpoint._sort_parameters(endpoint=endpoint, path=path)
if isinstance(endpoint, ParseError):
endpoint.header = (
f"ERROR parsing {method.upper()} {path} within {tag}. Endpoint will not be generated."
Expand Down Expand Up @@ -268,6 +273,20 @@ def _add_parameters(

return endpoint, schemas

@staticmethod
def _sort_parameters(*, endpoint: "Endpoint", path: str) -> Union["Endpoint", ParseError]:
endpoint = deepcopy(endpoint)
parameters_form_path = re.findall(_PATH_PARAM_REGEX, path)
path_parameter_names = [p.name for p in endpoint.path_parameters]
if sorted(parameters_form_path) != sorted(path_parameter_names):
return ParseError(
data=endpoint.path_parameters,
detail="Incorrect path templating (Path parameters do not match with path)",
)

endpoint.path_parameters.sort(key=lambda p: parameters_form_path.index(p.name))
return endpoint

@staticmethod
def from_data(
*, data: oai.Operation, path: str, method: str, tag: str, schemas: Schemas, config: Config
Expand Down
34 changes: 34 additions & 0 deletions tests/test_parser/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,40 @@ def test__add_parameters_duplicate_properties_different_location(self):
assert result.query_parameters[0].python_name == "test_query"
assert result.query_parameters[0].name == "test"

def test__sort_parameters(self, mocker):
from openapi_python_client.parser.openapi import Endpoint

endpoint = self.make_endpoint()
path = "/multiple-path-parameters/{param4}/{param2}/{param1}/{param3}"

for i in range(1, 5):
param = oai.Parameter.construct(
name=f"param{i}", required=True, param_schema=mocker.MagicMock(), param_in=oai.ParameterLocation.PATH
)
endpoint.path_parameters.append(param)

result = Endpoint._sort_parameters(endpoint=endpoint, path=path)
result_names = [p.name for p in result.path_parameters]
expected_names = [f"param{i}" for i in (4, 2, 1, 3)]

assert result_names == expected_names

def test__sort_parameters_invalid_path_templating(self, mocker):
from openapi_python_client.parser.openapi import Endpoint

endpoint = self.make_endpoint()
path = "/multiple-path-parameters/{param1}/{param2}"
param = oai.Parameter.construct(
name=f"param1", required=True, param_schema=mocker.MagicMock(), param_in=oai.ParameterLocation.PATH
)
endpoint.path_parameters.append(param)

result = Endpoint._sort_parameters(endpoint=endpoint, path=path)

assert isinstance(result, ParseError)
assert result.data == [param]
assert "Incorrect path templating" in result.detail

def test_from_data_bad_params(self, mocker):
from openapi_python_client.parser.openapi import Endpoint

Expand Down