diff --git a/CHANGELOG.md b/CHANGELOG.md index b72777e30..3924cb1b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixes +- Endpoint tags are now sanitized during parsing to fix an issue where `My Tag` and `MyTag` are seen as two different tags but are then later unified, causing errors when creating directories. Thanks @p1-ra! (#328) - Parser will softly ignore value error during schema responses' status code convertion from string to integer (not a number). Errors will be reported to the end user and parsing will continue to proceed (#327). - The generated `from_dict` and `to_dict` methods of models will now properly handle `nullable` and `not required` properties that are themselves generated models (#315). Thanks @forest-benchling! - Fix deserialization of `None` and `Unset` properties for all types by unifying the checks (#334). Thanks @forest-benchling! diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index 74de04520..8c97d45e9 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -235,7 +235,6 @@ def _build_api(self) -> None: endpoint_template = self.env.get_template("endpoint_module.py.jinja") for tag, collection in self.openapi.endpoint_collections_by_tag.items(): - tag = utils.snake_case(tag) tag_dir = api_dir / tag tag_dir.mkdir() (tag_dir / "__init__.py").touch() diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 8aa97422d..65acceb24 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -48,7 +48,7 @@ def from_data( operation: Optional[oai.Operation] = getattr(path_data, method) if operation is None: continue - tag = (operation.tags or ["default"])[0] + tag = utils.snake_case((operation.tags or ["default"])[0]) collection = endpoints_by_tag.setdefault(tag, EndpointCollection(tag=tag)) endpoint, schemas = Endpoint.from_data( data=operation, path=path, method=method, tag=tag, schemas=schemas diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index cf1c9ba14..1336c6c39 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -829,3 +829,51 @@ def test_from_data_errors(self, mocker): assert result["default"].parse_errors[1].data == "3" assert result["tag_2"].parse_errors[0].data == "2" assert result_schemas == schemas_3 + + def test_from_data_tags_snake_case_sanitizer(self, mocker): + from openapi_python_client.parser.openapi import Endpoint, EndpointCollection + + path_1_put = oai.Operation.construct() + path_1_post = oai.Operation.construct(tags=["AMF Subscription Info (Document)", "tag_3"]) + path_2_get = oai.Operation.construct() + data = { + "path_1": oai.PathItem.construct(post=path_1_post, put=path_1_put), + "path_2": oai.PathItem.construct(get=path_2_get), + } + endpoint_1 = mocker.MagicMock(autospec=Endpoint, tag="default", relative_imports={"1", "2"}) + endpoint_2 = mocker.MagicMock(autospec=Endpoint, tag="AMFSubscriptionInfo (Document)", relative_imports={"2"}) + endpoint_3 = mocker.MagicMock(autospec=Endpoint, tag="default", relative_imports={"2", "3"}) + schemas_1 = mocker.MagicMock() + schemas_2 = mocker.MagicMock() + schemas_3 = mocker.MagicMock() + endpoint_from_data = mocker.patch.object( + Endpoint, + "from_data", + side_effect=[(endpoint_1, schemas_1), (endpoint_2, schemas_2), (endpoint_3, schemas_3)], + ) + schemas = mocker.MagicMock() + + result = EndpointCollection.from_data(data=data, schemas=schemas) + + endpoint_from_data.assert_has_calls( + [ + mocker.call(data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas), + mocker.call( + data=path_1_post, + path="path_1", + method="post", + tag="amf_subscription_info_document", + schemas=schemas_1, + ), + mocker.call(data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2), + ], + ) + assert result == ( + { + "default": EndpointCollection("default", endpoints=[endpoint_1, endpoint_3]), + "amf_subscription_info_document": EndpointCollection( + "amf_subscription_info_document", endpoints=[endpoint_2] + ), + }, + schemas_3, + )