diff --git a/autorest/codegen/models/operation.py b/autorest/codegen/models/operation.py index 46b6fc49aa6..9259c53121b 100644 --- a/autorest/codegen/models/operation.py +++ b/autorest/codegen/models/operation.py @@ -38,6 +38,16 @@ def _non_binary_schema_media_types(media_types: List[str]) -> List[str]: response_media_types.append(xml_media_types[0]) return response_media_types +def _remove_multiple_content_type_parameters(parameters: List[Parameter]) -> List[Parameter]: + content_type_params = [p for p in parameters if p.serialized_name == "content_type"] + remaining_params = [p for p in parameters if p.serialized_name != "content_type"] + json_content_type_param = [p for p in content_type_params if p.yaml_data["schema"]["type"] == "constant"] + if json_content_type_param: + remaining_params.append(json_content_type_param[0]) + else: + remaining_params.append(content_type_params[0]) + return remaining_params + class Operation(BaseModel): # pylint: disable=too-many-public-methods, too-many-instance-attributes """Represent an operation. """ @@ -262,12 +272,7 @@ def from_yaml(cls, yaml_data: Dict[str, Any]) -> "Operation": for request in yaml_data["requests"]: for yaml in request.get("parameters", []): parameter = Parameter.from_yaml(yaml) - if yaml["language"]["python"]["name"] == "content_type": - if yaml["schema"]["type"] == "sealed-choice": - # for requests with multiple media types - # we get one that's a constant, one that's an enum - continue parameter.is_kwarg = True parameters.append(parameter) elif multiple_requests: @@ -276,6 +281,9 @@ def from_yaml(cls, yaml_data: Dict[str, Any]) -> "Operation": parameters.append(parameter) if multiple_requests: + parameters = _remove_multiple_content_type_parameters(parameters) + chosen_parameter = multiple_media_type_parameters[0] + # binary body parameters are required, while object # ones are not. We default to optional in this case. optional_parameters = [p for p in multiple_media_type_parameters if not p.required] diff --git a/autorest/namer/name_converter.py b/autorest/namer/name_converter.py index 1178247feea..4dd1d2100bf 100644 --- a/autorest/namer/name_converter.py +++ b/autorest/namer/name_converter.py @@ -5,16 +5,7 @@ # -------------------------------------------------------------------------- import re from typing import cast, Any, Dict, List, Match, Optional -from enum import Enum -from .python_mappings import basic_latin_chars, reserved_words - - -class PadType(Enum): - Model = "Model" - Method = "Method" - Parameter = "Parameter" - Enum = "Enum" - Property = "Property" +from .python_mappings import basic_latin_chars, reserved_words, PadType class NameConverter: @@ -198,17 +189,8 @@ def _get_escaped_reserved_name(name: str, pad_string: Optional[PadType] = None) try: # check to see if name is reserved for the type of name we are converting pad_string = cast(PadType, pad_string) - if name.lower() in reserved_words["always_reserved"]: + if pad_string and name.lower() in reserved_words[pad_string]: name += pad_string.value - elif pad_string in [PadType.Method, PadType.Parameter]: - if name.lower() in reserved_words["reserved_for_operations"]: - name += pad_string.value - elif pad_string in [PadType.Model, PadType.Property]: - if name.lower() in reserved_words["reserved_for_models"]: - name += pad_string.value - elif pad_string == PadType.Enum: - if name.lower() in reserved_words["reserved_for_enums"]: - name += pad_string.value return name except AttributeError: raise ValueError(f"The name {name} is a reserved word and you have not specified a pad string for it.") diff --git a/autorest/namer/python_mappings.py b/autorest/namer/python_mappings.py index 87e78f0a5a1..a1efdda0441 100644 --- a/autorest/namer/python_mappings.py +++ b/autorest/namer/python_mappings.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- +from enum import Enum basic_latin_chars = { " ": "Space", @@ -49,48 +50,125 @@ "~": "Tilde", } +class PadType(Enum): + Model = "Model" + Method = "Method" + Parameter = "Parameter" + Enum = "Enum" + Property = "Property" + +_always_reserved = [ + "and", + "as", + "assert", + "break", + "class", + "continue", + "def", + "del", + "elif", + "else", + "except", + "exec", + "finally", + "for", + "from", + "global", + "if", + "import", + "in", + "is", + "lambda", + "not", + "or", + "pass", + "raise", + "return", + "try", + "while", + "with", + "yield", + "async", + "await" +] + reserved_words = { - "always_reserved": [ - "and", - "as", - "assert", - "break", - "class", - "continue", - "def", - "del", - "elif", - "else", - "except", - "exec", - "finally", - "for", - "from", - "global", - "if", - "import", - "in", - "is", - "lambda", - "not", - "or", - "pass", - "raise", - "return", - "try", - "while", - "with", - "yield", - "async", - "await" + PadType.Method: [ + "self", + *_always_reserved + ], + PadType.Parameter: [ + # these are kwargs we've reserved for our autorest generated operations + "content_type", + "cls", + "polling", + # these are transport kwargs + # https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/core/azure-core/CLIENT_LIBRARY_DEVELOPER.md#transport + "connection_timeout", + "connection_verify", + "connection_cert", + "connection_data_block_size", + "use_env_settings", + # the following aren't in the readme, but Xiang said these are also transport kwargs + "read_timeout", + "proxies", + "cookies", + # these are policy kwargs + # https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/core/azure-core/CLIENT_LIBRARY_DEVELOPER.md#available-policies + "base_headers", + "headers", + "request_id", + "auto_request_id", + "base_user_agent", + "user_agent", + "user_agent_overwrite", + "user_agent_use_env", + "user_agent", + "sdk_moniker", + "logging_enable", + "logger", + "response_encoding", + "proxies", + "raw_request_hook", + "raw_response_hook", + "network_span_namer", + "tracing_attributes", + "permit_redirects", + "redirect_max", + "redirect_remove_headers", + "redirect_on_status_codes", + "permit_redirects", + "redirect_max", + "redirect_remove_headers", + "redirect_on_status_codes", + "retry_total", + "retry_connect", + "retry_read", + "retry_status", + "retry_backoff_factor", + "retry_backoff_max", + "retry_mode", + "retry_on_status_codes", + "retry_total", + "retry_connect", + "retry_read", + "retry_status", + "retry_backoff_factor", + "retry_backoff_max", + "retry_mode", + "retry_on_status_codes", + *_always_reserved ], - "reserved_for_operations": [ - "self" + PadType.Model: [ + "self", + *_always_reserved ], - "reserved_for_models": [ - "self" + PadType.Property: [ + "self", + *_always_reserved ], - "reserved_for_enums": [ - "mro" + PadType.Enum: [ + "mro", + *_always_reserved ] } diff --git a/test/unittests/test_name_converter.py b/test/unittests/test_name_converter.py index b39534db480..c0527bd5d0e 100644 --- a/test/unittests/test_name_converter.py +++ b/test/unittests/test_name_converter.py @@ -1,4 +1,5 @@ -from autorest.namer.name_converter import NameConverter, PadType +from autorest.namer.name_converter import NameConverter +from autorest.namer.python_mappings import PadType def test_escaped_reserved_words(): expected_conversion_model = { @@ -11,11 +12,20 @@ def test_escaped_reserved_words(): expected_conversion_method = { "self": "self_method", - "and": "and_method" + "and": "and_method", + "content_type": "content_type" } for name in expected_conversion_method: assert NameConverter._to_valid_python_name(name, pad_string=PadType.Method) == expected_conversion_method[name] + expected_conversion_parameter = { + "content_type": "content_type_parameter", + "request_id": "request_id_parameter", + "elif": "elif_parameter" + } + for name in expected_conversion_parameter: + assert NameConverter._to_valid_python_name(name, pad_string=PadType.Parameter) == expected_conversion_parameter[name] + expected_conversion_enum = { "self": "self", "mro": "mro_enum" diff --git a/test/vanilla/Expected/AcceptanceTests/Header/header/aio/operations_async/_header_operations_async.py b/test/vanilla/Expected/AcceptanceTests/Header/header/aio/operations_async/_header_operations_async.py index d4cf1a11af6..1a9df099441 100644 --- a/test/vanilla/Expected/AcceptanceTests/Header/header/aio/operations_async/_header_operations_async.py +++ b/test/vanilla/Expected/AcceptanceTests/Header/header/aio/operations_async/_header_operations_async.py @@ -44,13 +44,13 @@ def __init__(self, client, config, serializer, deserializer) -> None: @distributed_trace_async async def param_existing_key( self, - user_agent: str, + user_agent_parameter: str, **kwargs ) -> None: """Send a post request with header value "User-Agent": "overwrite". - :param user_agent: Send a post request with header value "User-Agent": "overwrite". - :type user_agent: str + :param user_agent_parameter: Send a post request with header value "User-Agent": "overwrite". + :type user_agent_parameter: str :keyword callable cls: A custom type or function that will be passed the direct response :return: None or the result of cls(response) :rtype: None @@ -67,7 +67,7 @@ async def param_existing_key( # Construct headers header_parameters = {} # type: Dict[str, Any] - header_parameters['User-Agent'] = self._serialize.header("user_agent", user_agent, 'str') + header_parameters['User-Agent'] = self._serialize.header("user_agent_parameter", user_agent_parameter, 'str') # Construct and send request request = self._client.post(url, query_parameters, header_parameters) diff --git a/test/vanilla/Expected/AcceptanceTests/Header/header/operations/_header_operations.py b/test/vanilla/Expected/AcceptanceTests/Header/header/operations/_header_operations.py index f0621e9c2de..00e1225fd72 100644 --- a/test/vanilla/Expected/AcceptanceTests/Header/header/operations/_header_operations.py +++ b/test/vanilla/Expected/AcceptanceTests/Header/header/operations/_header_operations.py @@ -44,14 +44,14 @@ def __init__(self, client, config, serializer, deserializer): @distributed_trace def param_existing_key( self, - user_agent, # type: str + user_agent_parameter, # type: str **kwargs # type: Any ): # type: (...) -> None """Send a post request with header value "User-Agent": "overwrite". - :param user_agent: Send a post request with header value "User-Agent": "overwrite". - :type user_agent: str + :param user_agent_parameter: Send a post request with header value "User-Agent": "overwrite". + :type user_agent_parameter: str :keyword callable cls: A custom type or function that will be passed the direct response :return: None or the result of cls(response) :rtype: None @@ -68,7 +68,7 @@ def param_existing_key( # Construct headers header_parameters = {} # type: Dict[str, Any] - header_parameters['User-Agent'] = self._serialize.header("user_agent", user_agent, 'str') + header_parameters['User-Agent'] = self._serialize.header("user_agent_parameter", user_agent_parameter, 'str') # Construct and send request request = self._client.post(url, query_parameters, header_parameters)