Skip to content

Commit

Permalink
Merge branch 'autorestv3' of https://github.com/Azure/autorest.python
Browse files Browse the repository at this point in the history
…into generated_code_mypy_fixes

* 'autorestv3' of https://github.com/Azure/autorest.python:
  Update changelog (#539)
  Fix config async token credential (#536)
  Allows enums to have non-string values (#534)
  Fixing AlertsManagement generation (need to check if parent in yaml is of type object) (#533)
  • Loading branch information
iscai-msft committed Apr 2, 2020
2 parents 1836f02 + 68808bf commit 968e889
Show file tree
Hide file tree
Showing 88 changed files with 1,405 additions and 320 deletions.
9 changes: 9 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

### 2020-04-01 - 5.0.0-dev.20200401.1
Modelerfour version: 4.12.276

**Bug Fixes**

- Now the generic models file and python3 models file have the same behavior in regards to required properties and their default values #532
- Can now specify non-string enums #534
- Fixes `TokenCredential` typing #535

### 2020-03-30 - 5.0.0-dev.20200330.1
Modelerfour version: 4.12.276

Expand Down
16 changes: 11 additions & 5 deletions autorest/codegen/models/code_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,26 @@


class CredentialSchema(BaseSchema):
def __init__(self) -> None: # pylint: disable=super-init-not-called
self.type = "azure.core.credentials.TokenCredential"
def __init__(self, async_mode) -> None: # pylint: disable=super-init-not-called
self.async_mode = async_mode
self.async_type = "azure.core.credentials.AsyncTokenCredential"
self.sync_type = "azure.core.credentials.TokenCredential"
self.default_value = None

@property
def serialization_type(self) -> str:
return self.type
if self.async_mode:
return self.async_type
return self.sync_type

@property
def docstring_type(self) -> str:
return self.type
return self.serialization_type

@property
def type_annotation(self) -> str:
if self.async_mode:
return '"AsyncTokenCredential"'
return '"TokenCredential"'

@property
Expand Down Expand Up @@ -168,7 +174,7 @@ def add_credential_global_parameter(self) -> None:
:return: None
:rtype: None
"""
credential_schema = CredentialSchema()
credential_schema = CredentialSchema(async_mode=False)
credential_parameter = Parameter(
yaml_data={},
schema=credential_schema,
Expand Down
31 changes: 21 additions & 10 deletions autorest/codegen/models/enum_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# --------------------------------------------------------------------------
from typing import Any, Dict, List, Optional, Set
from .base_schema import BaseSchema
from .primitive_schemas import PrimitiveSchema, get_primitive_schema, StringSchema
from .imports import FileImport, ImportType


Expand Down Expand Up @@ -44,7 +45,7 @@ class EnumSchema(BaseSchema):
:param yaml_data: the yaml data for this schema
:type yaml_data: dict[str, Any]
:param str description: The description of this enum
:param enum_type: The type of the enum. Currently we're only allowing strings.
:param str name: The name of the enum.
:type element_type: ~autorest.models.PrimitiveSchema
:param values: List of the values for this enum
:type values: list[~autorest.models.EnumValue]
Expand All @@ -55,15 +56,17 @@ def __init__(
namespace: str,
yaml_data: Dict[str, Any],
description: str,
enum_type: str,
name: str,
values: List["EnumValue"],
enum_type: PrimitiveSchema,
enum_file_name: str
) -> None:
super(EnumSchema, self).__init__(namespace=namespace, yaml_data=yaml_data)
self.description = description
self.enum_type = enum_type
self.name = name
self.values = values
self.enum_file_name = enum_file_name
self.enum_type = enum_type

@property
def serialization_type(self) -> str:
Expand All @@ -72,7 +75,7 @@ def serialization_type(self) -> str:
:return: The serialization value for msrest
:rtype: str
"""
return "str"
return self.enum_type.serialization_type

@property
def type_annotation(self) -> str:
Expand All @@ -81,7 +84,7 @@ def type_annotation(self) -> str:
:return: The type annotation for this schema
:rtype: str
"""
return f'Union[str, "{self.enum_type}"]'
return f'Union[{self.enum_type.type_annotation}, "{self.name}"]'

@property
def operation_type_annotation(self) -> str:
Expand All @@ -90,20 +93,20 @@ def operation_type_annotation(self) -> str:
:return: The type annotation for this schema
:rtype: str
"""
return f'Union[str, "models.{self.enum_type}"]'
return f'Union[{self.enum_type.type_annotation}, "models.{self.name}"]'

def get_declaration(self, value: Any) -> str:
return f'"{value}"'

@property
def docstring_text(self) -> str:
return self.enum_type
return self.name

@property
def docstring_type(self) -> str:
"""The python type used for RST syntax input and type annotation.
"""
return f"str or ~{self.namespace}.models.{self.enum_type}"
return f"str or ~{self.namespace}.models.{self.name}"

@staticmethod
def _get_enum_values(yaml_data: List[Dict[str, Any]]) -> List["EnumValue"]:
Expand Down Expand Up @@ -135,22 +138,30 @@ def from_yaml(cls, namespace: str, yaml_data: Dict[str, Any], **kwargs: Any) ->
:return: A created EnumSchema
:rtype: ~autorest.models.EnumSchema
"""
enum_type = yaml_data["language"]["python"]["name"]
name = yaml_data["language"]["python"]["name"]

# choice type doesn't always exist. if there is no choiceType, we default to string
if yaml_data.get("choiceType"):
enum_type = get_primitive_schema(namespace, yaml_data["choiceType"])
else:
enum_type = StringSchema(namespace, {"type": "str"})
values = EnumSchema._get_enum_values(yaml_data["choices"])
code_model = kwargs.pop("code_model")

return cls(
namespace=namespace,
yaml_data=yaml_data,
description=yaml_data["language"]["python"]["description"],
enum_type=enum_type,
name=name,
values=values,
enum_type=enum_type,
enum_file_name=f"_{code_model.module_name}_enums"
)

def imports(self) -> FileImport:
file_import = FileImport()
file_import.add_from_import("typing", "Union", ImportType.STDLIB)
file_import.merge(self.enum_type.imports())
return file_import

def model_file_imports(self) -> FileImport:
Expand Down
9 changes: 7 additions & 2 deletions autorest/codegen/models/object_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ def fill_instance_from_yaml(self, namespace: str, yaml_data: Dict[str, Any], **k
properties = []
base_model = None

name = yaml_data["language"]["python"]["name"]

# checking to see if there is a parent class and / or additional properties
if yaml_data.get("parents"):
immediate_parents = yaml_data["parents"]["immediate"]
Expand All @@ -114,7 +116,10 @@ def fill_instance_from_yaml(self, namespace: str, yaml_data: Dict[str, Any], **k
description="Unmatched properties from the message are deserialized to this collection."
)
)
elif immediate_parent["language"]["default"]["name"] != yaml_data["language"]["default"]["name"]:
elif (
immediate_parent["language"]["default"]["name"] != name and
immediate_parent['type'] == "object"
):
base_model = id(immediate_parent)

# checking to see if this is a polymorphic class
Expand All @@ -132,7 +137,7 @@ def fill_instance_from_yaml(self, namespace: str, yaml_data: Dict[str, Any], **k
]
# this is to ensure that the attribute map type and property type are generated correctly

name = yaml_data["language"]["python"]["name"]


description = yaml_data["language"]["python"]["description"]
is_exception = False
Expand Down
15 changes: 8 additions & 7 deletions autorest/codegen/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def serialize(self, code_model: CodeModel) -> None:
Path(".") if code_model.options["no_namespace_folders"] else Path(*(code_model.namespace.split(".")))
)

if code_model.schemas:
if code_model.schemas or code_model.enums:
self._serialize_and_write_models_folder(code_model=code_model, env=env, namespace_path=namespace_path)

self._serialize_and_write_operations_folder(code_model=code_model, env=env, namespace_path=namespace_path)
Expand All @@ -62,12 +62,13 @@ def serialize(self, code_model: CodeModel) -> None:
def _serialize_and_write_models_folder(self, code_model: CodeModel, env: Environment, namespace_path: Path) -> None:
# Write the models folder
models_path = namespace_path / Path("models")
self._autorestapi.write_file(
models_path / Path("_models.py"), ModelGenericSerializer(code_model=code_model, env=env).serialize()
)
self._autorestapi.write_file(
models_path / Path("_models_py3.py"), ModelPython3Serializer(code_model=code_model, env=env).serialize()
)
if code_model.schemas:
self._autorestapi.write_file(
models_path / Path("_models.py"), ModelGenericSerializer(code_model=code_model, env=env).serialize()
)
self._autorestapi.write_file(
models_path / Path("_models_py3.py"), ModelPython3Serializer(code_model=code_model, env=env).serialize()
)
if code_model.enums:
self._autorestapi.write_file(
models_path / Path(f"_{code_model.module_name}_enums.py"),
Expand Down
23 changes: 17 additions & 6 deletions autorest/codegen/serializers/general_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# --------------------------------------------------------------------------
from jinja2 import Environment
from .import_serializer import FileImportSerializer
from ..models import FileImport, ImportType, CodeModel
from ..models import FileImport, ImportType, CodeModel, CredentialSchema


class GeneralSerializer:
Expand All @@ -20,10 +20,20 @@ def serialize_pkgutil_init_file(self) -> str:

def serialize_init_file(self) -> str:
template = self.env.get_template("init.py.jinja2")
return template.render(code_model=self.code_model, async_mode=self.async_mode,)
return template.render(code_model=self.code_model, async_mode=self.async_mode)

def _correct_credential_parameter(self):
credential_param = [
gp for gp in self.code_model.global_parameters.parameters if isinstance(gp.schema, CredentialSchema)
][0]
credential_param.schema = CredentialSchema(async_mode=self.async_mode)

def serialize_service_client_file(self) -> str:
template = self.env.get_template("service_client.py.jinja2")

if self.code_model.options['credential']:
self._correct_credential_parameter()

return template.render(
code_model=self.code_model,
async_mode=self.async_mode,
Expand All @@ -38,17 +48,18 @@ def _config_imports(async_mode: bool) -> FileImport:
file_import.add_from_import("typing", "Any", ImportType.STDLIB)
if self.code_model.options["package_version"]:
file_import.add_from_import(".._version" if async_mode else "._version", "VERSION", ImportType.LOCAL)
if any(not gp.required for gp in self.code_model.global_parameters):
file_import.add_from_import("typing", "Optional", ImportType.STDLIB)
# if self.code_model.options['credential']:
# file_import.add_from_import("azure.core.credentials", "TokenCredential", ImportType.AZURECORE)
for gp in self.code_model.global_parameters:
file_import.merge(gp.imports())
return file_import

package_name = self.code_model.options['package_name']
if package_name and package_name.startswith("azure-"):
package_name = package_name[len("azure-"):]
sdk_moniker = package_name if package_name else self.code_model.class_name.lower()

if self.code_model.options['credential']:
self._correct_credential_parameter()

template = self.env.get_template("config.py.jinja2")
return template.render(
code_model=self.code_model,
Expand Down
14 changes: 12 additions & 2 deletions autorest/codegen/serializers/metadata_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
# --------------------------------------------------------------------------
from typing import List, Optional, Set, Tuple
from jinja2 import Environment
from ..models import CodeModel, Operation, OperationGroup, LROOperation, PagingOperation
from ..models import CodeModel, Operation, OperationGroup, LROOperation, PagingOperation, CredentialSchema
from ..models.imports import FileImport



class MetadataSerializer:
def __init__(self, code_model: CodeModel, env: Environment) -> None:
self.code_model = code_model
Expand All @@ -36,6 +35,14 @@ def _choose_api_version(self) -> Tuple[str, List[str]]:

return chosen_version, total_api_version_list

def _correct_credential_parameter(self):
# currently this is just copied over from general_serializer.
# this code will be changed when i merge my async multiapi client
# pr, since I need two copies of global parameters (one sync and one async).
credential_param = [
gp for gp in self.code_model.global_parameters.parameters if isinstance(gp.schema, CredentialSchema)
][0]
credential_param.schema = CredentialSchema(async_mode=False)

def serialize(self) -> str:
def _is_lro(operation):
Expand All @@ -60,6 +67,9 @@ def _is_paging(operation):
for parameter in operation.parameters:
parameter_imports.merge(parameter.imports())

if self.code_model.options['credential']:
self._correct_credential_parameter()

template = self.env.get_template("metadata.json.jinja2")
return template.render(
chosen_version=chosen_version,
Expand Down
2 changes: 1 addition & 1 deletion autorest/codegen/serializers/model_base_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def prop_documentation_string(prop: Property) -> str:
if prop.is_discriminator:
description += "Constant filled by server. "
if isinstance(prop.schema, EnumSchema):
values = ["'" + v.value + "'" for v in prop.schema.values]
values = [prop.schema.enum_type.get_declaration(v.value) for v in prop.schema.values]
description += " Possible values include: {}.".format(", ".join(values))
if prop.schema.default_value:
description += f' Default value: "{prop.schema.default_value}".'
Expand Down
2 changes: 1 addition & 1 deletion autorest/codegen/serializers/model_init_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(self, code_model: CodeModel, env: Environment) -> None:
def serialize(self) -> str:
schemas = [s.name for s in self.code_model.sorted_schemas]
schemas.sort()
enums = [e.enum_type for e in self.code_model.enums.values()] if self.code_model.enums else None
enums = [e.name for e in self.code_model.enums.values()] if self.code_model.enums else None

if enums:
enums.sort()
Expand Down
8 changes: 2 additions & 6 deletions autorest/codegen/templates/enum.py.jinja2
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@

class {{ enum.enum_type }}(str, Enum):
class {{ enum.name }}({{ enum.enum_type.type_annotation }}, Enum):
{% if enum.description %}
"""{{ enum.description | wordwrap(width=95, break_long_words=False, wrapstring='\n ') }}
"""
{% endif %}

{% for value in enum.values %}
{% if value.description %}
{{ value.name }} = "{{ value.value }}" {{ "#: " + value.description }}
{% else %}
{{ value.name }} = "{{ value.value }}"
{% endif %}
{{ value.name }} = {{ enum.enum_type.get_declaration(value.value) }}{{ " #: " + value.description if value.description else "" }}
{% endfor %}
Loading

0 comments on commit 968e889

Please sign in to comment.