From 381f178ec50707de53ca087f980f115c97666803 Mon Sep 17 00:00:00 2001 From: harabat Date: Sun, 3 Mar 2024 12:46:12 +0100 Subject: [PATCH 1/6] fix: allow hyphens as delimiter in PATH_PARAM_REGEX --- openapi_python_client/parser/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index c04cd1e8b..453e9c77a 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -26,7 +26,7 @@ from .properties.schemas import parameter_from_reference from .responses import Response, response_from_data -_PATH_PARAM_REGEX = re.compile("{([a-zA-Z_][a-zA-Z0-9_]*)}") +_PATH_PARAM_REGEX = re.compile("{([a-zA-Z_-][a-zA-Z0-9_-]*)}") def import_string_from_class(class_: Class, prefix: str = "") -> str: From bd28cf7be305fac3915f27b82c8f675bc74344dc Mon Sep 17 00:00:00 2001 From: harabat Date: Sun, 3 Mar 2024 13:20:33 +0100 Subject: [PATCH 2/6] fix: add path with python names variable to template --- openapi_python_client/__init__.py | 7 +++++++ openapi_python_client/templates/endpoint_module.py.jinja | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index 23d972eac..c88ef0997 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -277,9 +277,16 @@ def _build_api(self) -> None: for endpoint in collection.endpoints: module_path = tag_dir / f"{utils.PythonIdentifier(endpoint.name, self.config.field_prefix)}.py" + endpoint_path_python_names = endpoint.path + for parameter in endpoint.path_parameters: + endpoint_path_python_names = endpoint_path_python_names.replace( + f'{{{parameter.name}}}', + f'{{{parameter.python_name}}}' + ) module_path.write_text( endpoint_template.render( endpoint=endpoint, + endpoint_path_python_names=endpoint_path_python_names, ), encoding=self.config.file_encoding, ) diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 46f4eb365..0f0d1279e 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -29,9 +29,9 @@ def _get_kwargs( _kwargs: Dict[str, Any] = { "method": "{{ endpoint.method }}", {% if endpoint.path_parameters %} - "url": "{{ endpoint.path }}".format( + "url": "{{ endpoint_path_python_names }}".format( {%- for parameter in endpoint.path_parameters -%} - {{parameter.name}}={{parameter.python_name}}, + {{parameter.python_name}}={{parameter.python_name}}, {%- endfor -%} ), {% else %} From 724f533125cb5771895176e48e1793dc373192ff Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Tue, 5 Mar 2024 18:53:47 -0700 Subject: [PATCH 3/6] chore: reformat --- openapi_python_client/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index c88ef0997..feb5ec53c 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -280,8 +280,7 @@ def _build_api(self) -> None: endpoint_path_python_names = endpoint.path for parameter in endpoint.path_parameters: endpoint_path_python_names = endpoint_path_python_names.replace( - f'{{{parameter.name}}}', - f'{{{parameter.python_name}}}' + f"{{{parameter.name}}}", f"{{{parameter.python_name}}}" ) module_path.write_text( endpoint_template.render( From a8b3e9027eb6c47cac68393478340d6f02c91020 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Tue, 5 Mar 2024 18:58:31 -0700 Subject: [PATCH 4/6] test: Add e2e test --- end_to_end_tests/baseline_openapi_3.0.json | 21 +++++ end_to_end_tests/baseline_openapi_3.1.yaml | 21 +++++ .../my_test_api_client/api/naming/__init__.py | 6 +- .../api/naming/hyphen_in_path.py | 91 +++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/naming/hyphen_in_path.py diff --git a/end_to_end_tests/baseline_openapi_3.0.json b/end_to_end_tests/baseline_openapi_3.0.json index 2ebc52322..e70de4c99 100644 --- a/end_to_end_tests/baseline_openapi_3.0.json +++ b/end_to_end_tests/baseline_openapi_3.0.json @@ -1457,6 +1457,27 @@ } } }, + "/naming/{hyphen-in-path}": { + "get": { + "tags": ["naming"], + "operationId": "hyphen_in_path", + "parameters": [ + { + "name": "hyphen-in-path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, "/parameter-references/{path_param}": { "get": { "tags": [ diff --git a/end_to_end_tests/baseline_openapi_3.1.yaml b/end_to_end_tests/baseline_openapi_3.1.yaml index 4c715082c..1b5664e77 100644 --- a/end_to_end_tests/baseline_openapi_3.1.yaml +++ b/end_to_end_tests/baseline_openapi_3.1.yaml @@ -1451,6 +1451,27 @@ info: } } }, + "/naming/{hyphen-in-path}": { + "get": { + "tags": [ "naming" ], + "operationId": "hyphen_in_path", + "parameters": [ + { + "name": "hyphen-in-path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, "/parameter-references/{path_param}": { "get": { "tags": [ diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/naming/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/naming/__init__.py index 864802814..d446ab5ab 100644 --- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/naming/__init__.py +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/naming/__init__.py @@ -2,7 +2,7 @@ import types -from . import mixed_case, post_naming_property_conflict_with_import +from . import hyphen_in_path, mixed_case, post_naming_property_conflict_with_import class NamingEndpoints: @@ -13,3 +13,7 @@ def post_naming_property_conflict_with_import(cls) -> types.ModuleType: @classmethod def mixed_case(cls) -> types.ModuleType: return mixed_case + + @classmethod + def hyphen_in_path(cls) -> types.ModuleType: + return hyphen_in_path diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/naming/hyphen_in_path.py b/end_to_end_tests/golden-record/my_test_api_client/api/naming/hyphen_in_path.py new file mode 100644 index 000000000..8bab45991 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/naming/hyphen_in_path.py @@ -0,0 +1,91 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional, Union + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...types import Response + + +def _get_kwargs( + hyphen_in_path: str, +) -> Dict[str, Any]: + _kwargs: Dict[str, Any] = { + "method": "get", + "url": f"/naming/{hyphen_in_path}", + } + + return _kwargs + + +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: + if response.status_code == HTTPStatus.OK: + return None + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + hyphen_in_path: str, + *, + client: Union[AuthenticatedClient, Client], +) -> Response[Any]: + """ + Args: + hyphen_in_path (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + hyphen_in_path=hyphen_in_path, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +async def asyncio_detailed( + hyphen_in_path: str, + *, + client: Union[AuthenticatedClient, Client], +) -> Response[Any]: + """ + Args: + hyphen_in_path (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + hyphen_in_path=hyphen_in_path, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) From 544f289a12d06116a0d3b0936463303704f4f234 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Tue, 5 Mar 2024 19:02:29 -0700 Subject: [PATCH 5/6] chore: Reorganize path templating --- openapi_python_client/__init__.py | 6 ------ openapi_python_client/parser/openapi.py | 2 ++ openapi_python_client/templates/endpoint_module.py.jinja | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index feb5ec53c..23d972eac 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -277,15 +277,9 @@ def _build_api(self) -> None: for endpoint in collection.endpoints: module_path = tag_dir / f"{utils.PythonIdentifier(endpoint.name, self.config.field_prefix)}.py" - endpoint_path_python_names = endpoint.path - for parameter in endpoint.path_parameters: - endpoint_path_python_names = endpoint_path_python_names.replace( - f"{{{parameter.name}}}", f"{{{parameter.python_name}}}" - ) module_path.write_text( endpoint_template.render( endpoint=endpoint, - endpoint_path_python_names=endpoint_path_python_names, ), encoding=self.config.file_encoding, ) diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 453e9c77a..015966224 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -379,6 +379,8 @@ def sort_parameters(*, endpoint: "Endpoint") -> Union["Endpoint", ParseError]: return ParseError( detail=f"Incorrect path templating for {endpoint.path} (Path parameters do not match with path)", ) + for parameter in endpoint.path_parameters: + endpoint.path = endpoint.path.replace(f"{{{parameter.name}}}", f"{{{parameter.python_name}}}") return endpoint @staticmethod diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 0f0d1279e..4db1c3546 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -29,7 +29,7 @@ def _get_kwargs( _kwargs: Dict[str, Any] = { "method": "{{ endpoint.method }}", {% if endpoint.path_parameters %} - "url": "{{ endpoint_path_python_names }}".format( + "url": "{{ endpoint.path }}".format( {%- for parameter in endpoint.path_parameters -%} {{parameter.python_name}}={{parameter.python_name}}, {%- endfor -%} From ca202a7239a76b626b6159c93831dda53aac73e6 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Tue, 5 Mar 2024 19:10:38 -0700 Subject: [PATCH 6/6] chore: document with changeset --- .changeset/allow_hyphens_in_path_parameters.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changeset/allow_hyphens_in_path_parameters.md diff --git a/.changeset/allow_hyphens_in_path_parameters.md b/.changeset/allow_hyphens_in_path_parameters.md new file mode 100644 index 000000000..4c4c1659d --- /dev/null +++ b/.changeset/allow_hyphens_in_path_parameters.md @@ -0,0 +1,15 @@ +--- +default: patch +--- + +# Allow hyphens in path parameters + +Before now, path parameters which were invalid Python identifiers were not allowed, and would fail generation with an +"Incorrect path templating" error. In particular, this meant that path parameters with hyphens were not allowed. +This has now been fixed! + +PR #986 fixed issue #976. Thanks @harabat! + +> [!WARNING] +> This change may break custom templates, see [this diff](https://github.com/openapi-generators/openapi-python-client/pull/986/files#diff-0de8437b26075d8fe8454cf47d8d95d4835c7f827fa87328e03f690412be803e) +> if you have trouble upgrading.