Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Modelerfour version: 4.13.351

**New Features**

- Supports a function that is both LRO and paging #689
- We have added a `--credential-default-policy-type` flag. Its default value is `BearerTokenCredentialPolicy`, but it can also accept
`AzureKeyCredentialPolicy`. The value passed in will be the default authentication policy in the client's config, so users using the
generated library will use that auth policy unless they pass in a separate one through kwargs #686
Expand Down
2 changes: 1 addition & 1 deletion autorest/codegen/models/lro_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(
responses: Optional[List[SchemaResponse]] = None,
exceptions: Optional[List[SchemaResponse]] = None,
want_description_docstring: bool = True,
want_tracing: bool = True,
want_tracing: bool = True
) -> None:
super(LROOperation, self).__init__(
yaml_data,
Expand Down
58 changes: 58 additions & 0 deletions autorest/codegen/models/lro_paging_operation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------

from typing import Any, Dict, List, Set, Optional
from .lro_operation import LROOperation
from .paging_operation import PagingOperation
from .imports import FileImport
from .schema_request import SchemaRequest
from .parameter import Parameter
from .schema_response import SchemaResponse

class LROPagingOperation(PagingOperation, LROOperation):
def __init__(
self,
yaml_data: Dict[str, Any],
name: str,
description: str,
url: str,
method: str,
api_versions: Set[str],
requests: List[SchemaRequest],
summary: Optional[str] = None,
parameters: Optional[List[Parameter]] = None,
multiple_media_type_parameters: Optional[List[Parameter]] = None,
responses: Optional[List[SchemaResponse]] = None,
exceptions: Optional[List[SchemaResponse]] = None,
want_description_docstring: bool = True,
want_tracing: bool = True
) -> None:
super(LROPagingOperation, self).__init__(
yaml_data,
name,
description,
url,
method,
api_versions,
requests,
summary,
parameters,
multiple_media_type_parameters,
responses,
exceptions,
want_description_docstring,
want_tracing,
override_success_response_to_200=True
)

def imports(self, code_model, async_mode: bool) -> FileImport:
lro_imports = LROOperation.imports(self, code_model, async_mode)
paging_imports = PagingOperation.imports(self, code_model, async_mode)

file_import = lro_imports
file_import.merge(paging_imports)
return file_import

2 changes: 1 addition & 1 deletion autorest/codegen/models/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def __init__(
responses: Optional[List[SchemaResponse]] = None,
exceptions: Optional[List[SchemaResponse]] = None,
want_description_docstring: bool = True,
want_tracing: bool = True,
want_tracing: bool = True
) -> None:
super().__init__(yaml_data)
self.name = name
Expand Down
9 changes: 7 additions & 2 deletions autorest/codegen/models/operation_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .operation import Operation
from .lro_operation import LROOperation
from .paging_operation import PagingOperation
from .lro_paging_operation import LROPagingOperation
from .imports import FileImport, ImportType


Expand Down Expand Up @@ -82,9 +83,13 @@ def from_yaml(cls, code_model, yaml_data: Dict[str, Any]) -> "OperationGroup":
operations = []
api_versions: Set[str] = set()
for operation_yaml in yaml_data["operations"]:
if operation_yaml.get("extensions", {}).get("x-ms-long-running-operation"):
lro_operation = operation_yaml.get("extensions", {}).get("x-ms-long-running-operation")
paging_operation = operation_yaml.get("extensions", {}).get("x-ms-pageable")
if lro_operation and paging_operation:
operation = LROPagingOperation.from_yaml(operation_yaml)
elif lro_operation:
operation = LROOperation.from_yaml(operation_yaml)
elif operation_yaml.get("extensions", {}).get("x-ms-pageable"):
elif paging_operation:
operation = PagingOperation.from_yaml(operation_yaml)
else:
operation = Operation.from_yaml(operation_yaml)
Expand Down
19 changes: 17 additions & 2 deletions autorest/codegen/models/paging_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# license information.
# --------------------------------------------------------------------------
import logging
from typing import cast, Dict, List, Any, Optional, Set
from typing import cast, Dict, List, Any, Optional, Set, Union

from .operation import Operation
from .parameter import Parameter
Expand All @@ -31,6 +31,10 @@ def __init__(
multiple_media_type_parameters: Optional[List[Parameter]] = None,
responses: Optional[List[SchemaResponse]] = None,
exceptions: Optional[List[SchemaResponse]] = None,
want_description_docstring: bool = True,
want_tracing: bool = True,
*,
override_success_response_to_200: bool = False
) -> None:
super(PagingOperation, self).__init__(
yaml_data,
Expand All @@ -44,12 +48,15 @@ def __init__(
parameters,
multiple_media_type_parameters,
responses,
exceptions
exceptions,
want_description_docstring,
want_tracing
)
self._item_name: str = yaml_data["extensions"]["x-ms-pageable"].get("itemName")
self._next_link_name: str = yaml_data["extensions"]["x-ms-pageable"].get("nextLinkName")
self.operation_name: str = yaml_data["extensions"]["x-ms-pageable"].get("operationName")
self.next_operation: Optional[Operation] = None
self.override_success_response_to_200 = override_success_response_to_200

def _get_response(self) -> SchemaResponse:
response = self.responses[0]
Expand Down Expand Up @@ -98,6 +105,14 @@ def has_optional_return_type(self) -> bool:
"""A paging will never have an optional return type, we will always return ItemPaged[return type]"""
return False

@property
def success_status_code(self) -> List[Union[str, int]]:
"""The list of all successfull status code.
"""
if self.override_success_response_to_200:
return [200]
return super(PagingOperation, self).success_status_code

def imports(self, code_model, async_mode: bool) -> FileImport:
file_import = super(PagingOperation, self).imports(code_model, async_mode)

Expand Down
69 changes: 5 additions & 64 deletions autorest/codegen/templates/lro_operation.py.jinja2
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
{% import 'keywords.jinja2' as keywords with context %}
{% import 'operation_tools.jinja2' as op_tools %}
{% import 'lro_operation_helper.jinja2' as helper %}
{% set trace_decorator = "@distributed_trace_async" if async_mode else "@distributed_trace" %}
{% set async_prefix = "Async" if async_mode else "" %}
{% set poller = "AsyncLROPoller" if async_mode else "LROPoller" %}
{% set operation_name = "begin_"+operation.python_name %}
{% macro return_docstring(async_mode) %}
:return: An instance of {{ "Async" if async_mode }}LROPoller that returns either {{ operation.responses[0].schema.docstring_text if operation.responses[0].has_body else "None"}} or the result of cls(response)
:rtype: ~azure.core.polling.{{ "Async" if async_mode }}LROPoller[{{ operation.responses[0].schema.docstring_type if operation.responses[0].has_body else "None" }}]{% endmacro %}
{% macro param_documentation_string(parameter) %}:param {{ parameter.serialized_name }}: {{ parameter.description }}{% endmacro %}
{% macro response_headers(response) %}
response_headers = {
{% for response_header in response.headers %}
Expand All @@ -16,61 +13,21 @@ response_headers = {
}
{% endmacro %}
{% macro operation_docstring(async_mode) %}
"""{{ operation.summary if operation.summary else operation.description | wordwrap(width=95, break_long_words=False, wrapstring='\n') }}
{% if operation.summary and operation.description %}

{{ operation.description | wordwrap(width=95, break_long_words=False, wrapstring='\n') }}
{% endif %}

{% if operation.deprecated -%}
.. warning::
This method is deprecated

{% endif -%}
{% for parameter in operation.parameters.method %}
{%- for doc_string in param_documentation_string(parameter).replace('\n', '\n ').split('\n') %}
{{ doc_string | wordwrap(width=95, break_long_words=False, wrapstring='\n ')}}
{% endfor %}
:type {{ parameter.serialized_name }}: {{ parameter.schema.docstring_type }}
{% endfor %}
:keyword callable cls: A custom type or function that will be passed the direct response
:keyword str continuation_token: A continuation token to restart a poller from a saved state.
:keyword polling: True for ARMPolling, False for no polling, or a
polling object for personal polling strategy
:paramtype polling: bool or ~azure.core.polling.{{ "Async" if async_mode else "" }}PollingMethod
:keyword int polling_interval: Default waiting time between two polls for LRO operations if no Retry-After header is present.
{{ helper.operation_docstring_helper(operation, async_mode) }}
{{ return_docstring(async_mode) }}
:raises ~azure.core.exceptions.HttpResponseError:
"""{% endmacro %}
{% set lro_options = (", lro_options={'final-state-via': '"+ operation.lro_options['final-state-via'] + "'}") if operation.lro_options else "" %}
{# actual template starts here #}
{% if code_model.options['tracing'] %}
{{ trace_decorator }}
{% endif %}
{% set return_type_wrapper = "AsyncLROPoller" if async_mode else "LROPoller" %}
{% set return_type_wrapper = ["AsyncLROPoller" if async_mode else "LROPoller"] %}
{{ op_tools.method_signature(operation, operation_name, async_mode=async_mode, coroutine=async_mode, return_type_wrapper=return_type_wrapper) }}
{%- if not async_mode %}
{{ op_tools.sync_return_type_annotation(operation, return_type_wrapper) }}
{% endif %}
{{ operation_docstring(async_mode) | indent }}
polling = kwargs.pop('polling', {{ "True" if code_model.options['azure_arm'] else "False" }}) # type: Union[bool, {{ "Async" if async_mode else "" }}PollingMethod]
cls = kwargs.pop('cls', None) # type: ClsType[{{ op_tools.return_type_annotation(operation) }}]
lro_delay = kwargs.pop(
'polling_interval',
self._config.polling_interval
)
cont_token = kwargs.pop('continuation_token', None) # type: Optional[str]
if cont_token is None:
raw_result = {{ keywords.await }}self._{{ operation.name }}_initial(
{% for parameter in operation.parameters.method %}
{{ parameter.serialized_name }}={{ parameter.serialized_name }},
{% endfor %}
cls=lambda x,y,z: x,
**kwargs
)

kwargs.pop('error_map', None)
kwargs.pop('content_type', None)
{{ helper.lro_operation(code_model, operation, async_mode) }}

def get_long_running_output(pipeline_response):
{% if operation.lro_response.has_headers %}
Expand All @@ -86,20 +43,4 @@ response_headers = {
return deserialized
{% endif %}

{% if code_model.options['azure_arm'] %}
if polling is True: polling_method = {{ async_prefix }}ARMPolling(lro_delay{{ lro_options }}, **kwargs)
{% else %}
if polling is True: polling_method = {{ async_prefix }}LROBasePolling(lro_delay{{ lro_options }}, **kwargs)
{% endif %}
elif polling is False: polling_method = {{ async_prefix }}NoPolling()
else: polling_method = polling
if cont_token:
return {{ poller }}.from_continuation_token(
polling_method=polling_method,
continuation_token=cont_token,
client=self._client,
deserialization_callback=get_long_running_output
)
else:
return {{ poller }}(self._client, raw_result, get_long_running_output, polling_method)
{{ operation_name }}.metadata = {'url': '{{ operation.url }}'} # type: ignore
{{ helper.lro_operation_return(code_model, operation, async_mode) }}
76 changes: 76 additions & 0 deletions autorest/codegen/templates/lro_operation_helper.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{% import 'operation_tools.jinja2' as op_tools %}

{% macro param_documentation_string(parameter) %}:param {{ parameter.serialized_name }}: {{ parameter.description }}{% endmacro %}

{% macro operation_docstring_helper(operation, async_mode) %}
"""{{ operation.summary if operation.summary else operation.description | wordwrap(width=95, break_long_words=False, wrapstring='\n') }}
{% if operation.summary and operation.description %}

{{ operation.description | wordwrap(width=95, break_long_words=False, wrapstring='\n') }}
{% endif %}

{% if operation.deprecated -%}
.. warning::
This method is deprecated

{% endif -%}
{% for parameter in operation.parameters.method %}
{%- for doc_string in param_documentation_string(parameter).replace('\n', '\n ').split('\n') %}
{{ doc_string | wordwrap(width=95, break_long_words=False, wrapstring='\n ')}}
{% endfor %}
:type {{ parameter.serialized_name }}: {{ parameter.schema.docstring_type }}
{% endfor %}
:keyword callable cls: A custom type or function that will be passed the direct response
:keyword str continuation_token: A continuation token to restart a poller from a saved state.
:keyword polling: True for ARMPolling, False for no polling, or a
polling object for personal polling strategy
:paramtype polling: bool or ~azure.core.polling.{{ "Async" if async_mode else "" }}PollingMethod
:keyword int polling_interval: Default waiting time between two polls for LRO operations if no Retry-After header is present.
{%- endmacro -%}


{% macro lro_operation(code_model, operation, async_mode) %}
{% import 'keywords.jinja2' as keywords with context %}
polling = kwargs.pop('polling', {{ "True" if code_model.options['azure_arm'] else "False" }}) # type: Union[bool, {{ "Async" if async_mode else "" }}PollingMethod]
cls = kwargs.pop('cls', None) # type: ClsType[{{ op_tools.return_type_annotation(operation) }}]
lro_delay = kwargs.pop(
'polling_interval',
self._config.polling_interval
)
cont_token = kwargs.pop('continuation_token', None) # type: Optional[str]
if cont_token is None:
raw_result = {{ keywords.await }}self._{{ operation.name }}_initial(
{% for parameter in operation.parameters.method %}
{{ parameter.serialized_name }}={{ parameter.serialized_name }},
{% endfor %}
cls=lambda x,y,z: x,
**kwargs
)

kwargs.pop('error_map', None)
kwargs.pop('content_type', None)
{%- endmacro -%}

{% macro lro_operation_return(code_model, operation, async_mode) %}
{% set async_prefix = "Async" if async_mode else "" %}
{% set lro_options = (", lro_options={'final-state-via': '"+ operation.lro_options['final-state-via'] + "'}") if operation.lro_options else "" %}
{% set poller = "AsyncLROPoller" if async_mode else "LROPoller" %}
{% set operation_name = "begin_"+operation.python_name %}
{% if code_model.options['azure_arm'] %}
if polling is True: polling_method = {{ async_prefix }}ARMPolling(lro_delay{{ lro_options }}, **kwargs)
{% else %}
if polling is True: polling_method = {{ async_prefix }}LROBasePolling(lro_delay{{ lro_options }}, **kwargs)
{% endif %}
elif polling is False: polling_method = {{ async_prefix }}NoPolling()
else: polling_method = polling
if cont_token:
return {{ poller }}.from_continuation_token(
polling_method=polling_method,
continuation_token=cont_token,
client=self._client,
deserialization_callback=get_long_running_output
)
else:
return {{ poller }}(self._client, raw_result, get_long_running_output, polling_method)
{{ operation_name }}.metadata = {'url': '{{ operation.url }}'} # type: ignore
{%- endmacro -%}
38 changes: 38 additions & 0 deletions autorest/codegen/templates/lro_paging_operation.py.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{% import 'operation_tools.jinja2' as op_tools %}
{% import 'keywords.jinja2' as keywords with context %}
{% import 'lro_operation_helper.jinja2' as lro_helper %}
{% import 'paging_operation_helper.jinja2' as paging_helper %}
{% set trace_decorator = "@distributed_trace_async" if async_mode else "@distributed_trace" %}
{% set operation_name = "begin_"+operation.python_name %}
{% macro return_docstring(async_mode) %}
:return: An instance of {{ "Async" if async_mode }}LROPoller that returns an iterator like instance of either {{ operation.responses[0].schema.docstring_text if operation.responses[0].has_body else "None"}} or the result of cls(response)
:rtype: ~azure.core.polling.{{ "Async" if async_mode }}LROPoller[~azure.core.{{ "async_" if async_mode else "" }}paging.{{ "Async" if async_mode }}ItemPaged[{{ operation.responses[0].schema.docstring_type if operation.responses[0].has_body else "None" }}]]{% endmacro %}
{% macro operation_docstring(async_mode) %}
{{ lro_helper.operation_docstring_helper(operation, async_mode) }}
{{ return_docstring(async_mode) }}
:raises ~azure.core.exceptions.HttpResponseError:
"""{% endmacro %}
{# actual template starts here #}
{% if code_model.options['tracing'] %}
{{ trace_decorator }}
{% endif %}
{% set return_type_wrapper = [("Async" if async_mode else "") ~ "LROPoller", ("Async" if async_mode else "") ~ "ItemPaged"] %}
{{ op_tools.method_signature(operation, operation_name, async_mode=async_mode, coroutine=async_mode, return_type_wrapper=return_type_wrapper) }}
{%- if not async_mode %}
{{ op_tools.sync_return_type_annotation(operation, return_type_wrapper) }}
{% endif %}
{{ operation_docstring(async_mode) | indent }}
{{ paging_helper.paging_operation(code_model, operation, async_mode) }}

{{ lro_helper.lro_operation(code_model, operation, async_mode) }}
def get_long_running_output(pipeline_response):
{{ keywords.def }} internal_get_next(next_link=None):
if next_link is None:
return pipeline_response
else:
return {{ keywords.await }}get_next(next_link)

return {{ "Async" if async_mode }}ItemPaged(
internal_get_next, extract_data
)
{{ lro_helper.lro_operation_return(code_model, operation, async_mode) }}
Loading