-
Notifications
You must be signed in to change notification settings - Fork 68
Retry policy support for v2 #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
512b613
4d4de06
274ea2b
f5cd2e6
c942289
b14b1c8
9880a04
e2ff9c8
e727a6a
27c56fe
24797d2
f89b31e
d6915b4
3feaca2
c639451
72d353f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ | |
|
||
from azure.functions.decorators.blob import BlobTrigger, BlobInput, BlobOutput | ||
from azure.functions.decorators.core import Binding, Trigger, DataType, \ | ||
AuthLevel, SCRIPT_FILE_NAME, Cardinality, AccessRights | ||
AuthLevel, SCRIPT_FILE_NAME, Cardinality, AccessRights, Setting | ||
from azure.functions.decorators.cosmosdb import CosmosDBTrigger, \ | ||
CosmosDBOutput, CosmosDBInput, CosmosDBTriggerV3, CosmosDBInputV3, \ | ||
CosmosDBOutputV3 | ||
|
@@ -29,6 +29,8 @@ | |
parse_iterable_param_to_enums, StringifyEnumJsonEncoder | ||
from azure.functions.http import HttpRequest | ||
from .generic import GenericInputBinding, GenericTrigger, GenericOutputBinding | ||
from .retry_policy import RetryPolicy | ||
from .function_name import FunctionName | ||
from .warmup import WarmUpTrigger | ||
from .._http_asgi import AsgiMiddleware | ||
from .._http_wsgi import WsgiMiddleware, Context | ||
|
@@ -45,11 +47,17 @@ def __init__(self, func: Callable[..., Any], script_file: str): | |
|
||
:param func: User defined python function instance. | ||
:param script_file: File name indexed by worker to find function. | ||
:param trigger: The trigger object of the function. | ||
:param bindings: The list of binding objects of a function. | ||
:param settings: The list of setting objects of a function. | ||
:param http_type: Http function type. | ||
:param is_http_function: Whether the function is a http function. | ||
""" | ||
self._name: str = func.__name__ | ||
self._func = func | ||
self._trigger: Optional[Trigger] = None | ||
vrdmr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._bindings: List[Binding] = [] | ||
self._settings: List[Setting] = [] | ||
gavin-aguiar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.function_script_file = script_file | ||
self.http_type = 'function' | ||
self._is_http_function = False | ||
|
@@ -83,14 +91,12 @@ def add_trigger(self, trigger: Trigger) -> None: | |
# function.json is complete | ||
self._bindings.append(trigger) | ||
|
||
def set_function_name(self, function_name: Optional[str] = None) -> None: | ||
"""Set or update the name for the function if :param:`function_name` | ||
is not None. If not set, function name will default to python | ||
function name. | ||
:param function_name: Name the function set to. | ||
def add_setting(self, setting: Setting) -> None: | ||
"""Add a setting instance to the function. | ||
|
||
:param setting: The setting object to add | ||
""" | ||
if function_name: | ||
self._name = function_name | ||
self._settings.append(setting) | ||
|
||
def set_http_type(self, http_type: str) -> None: | ||
"""Set or update the http type for the function if :param:`http_type` | ||
|
@@ -116,6 +122,36 @@ def get_bindings(self) -> List[Binding]: | |
""" | ||
return self._bindings | ||
|
||
def get_setting(self, setting_name: str) -> Optional[Setting]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where do we get the list of all the settings registered to the Function? where you can get the setting_name itself from? I don't see it within a Function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. class Functions has a List[Settings] which should have all the settings for a function |
||
"""Get a specific setting attached to the function. | ||
|
||
:param setting_name: The name of the setting to search for. | ||
:return: The setting attached to the function (or None if not found). | ||
""" | ||
for setting in self._settings: | ||
if setting.setting_name == setting_name: | ||
return setting | ||
return None | ||
|
||
def get_settings_dict(self, setting_name) -> Optional[Dict]: | ||
"""Get a dictionary representation of a setting. | ||
|
||
:param: setting_name: The name of the setting to search for. | ||
:return: The dictionary representation of the setting (or None if not | ||
found). | ||
""" | ||
setting = self.get_setting(setting_name) | ||
return setting.get_dict_repr() if setting else None | ||
|
||
def get_function_name(self) -> Optional[str]: | ||
"""Get the name of the function. | ||
:return: The name of the function. | ||
""" | ||
function_name_setting = \ | ||
self.get_setting("function_name") | ||
return function_name_setting.get_settings_value("function_name") \ | ||
if function_name_setting else self._name | ||
|
||
def get_raw_bindings(self) -> List[str]: | ||
return [json.dumps(b.get_dict_repr(), cls=StringifyEnumJsonEncoder) | ||
for b in self._bindings] | ||
|
@@ -145,13 +181,6 @@ def get_user_function(self) -> Callable[..., Any]: | |
""" | ||
return self._func | ||
|
||
def get_function_name(self) -> str: | ||
"""Get the function name. | ||
|
||
:return: Function name. | ||
""" | ||
return self._name | ||
|
||
def get_function_json(self) -> str: | ||
"""Get the json stringified form of function. | ||
|
||
|
@@ -170,11 +199,6 @@ def __init__(self, func, function_script_file): | |
def __call__(self, *args, **kwargs): | ||
pass | ||
|
||
def configure_function_name(self, function_name: str) -> 'FunctionBuilder': | ||
self._function.set_function_name(function_name) | ||
|
||
return self | ||
|
||
def configure_http_type(self, http_type: str) -> 'FunctionBuilder': | ||
self._function.set_http_type(http_type) | ||
|
||
|
@@ -188,6 +212,10 @@ def add_binding(self, binding: Binding) -> 'FunctionBuilder': | |
self._function.add_binding(binding=binding) | ||
return self | ||
|
||
def add_setting(self, setting: Setting) -> 'FunctionBuilder': | ||
vrdmr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._function.add_setting(setting=setting) | ||
return self | ||
|
||
def _validate_function(self, | ||
auth_level: Optional[AuthLevel] = None) -> None: | ||
""" | ||
|
@@ -288,23 +316,6 @@ def decorator(func): | |
|
||
return decorator | ||
|
||
def function_name(self, name: str) -> Callable[..., Any]: | ||
"""Set name of the :class:`Function` object. | ||
|
||
:param name: Name of the function. | ||
:return: Decorator function. | ||
""" | ||
|
||
@self._configure_function_builder | ||
def wrap(fb): | ||
def decorator(): | ||
fb.configure_function_name(name) | ||
return fb | ||
|
||
return decorator() | ||
|
||
return wrap | ||
|
||
def http_type(self, http_type: str) -> Callable[..., Any]: | ||
"""Set http type of the :class:`Function` object. | ||
|
||
|
@@ -1938,6 +1949,80 @@ def decorator(): | |
return wrap | ||
|
||
|
||
class SettingsApi(DecoratorApi, ABC): | ||
gavin-aguiar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Interface to extend for using existing settings decorator in | ||
functions.""" | ||
|
||
def function_name(self, name: str, | ||
setting_extra_fields: Dict[str, Any] = {}, | ||
) -> Callable[..., Any]: | ||
"""Optional: Sets name of the :class:`Function` object. If not set, | ||
it will default to the name of the method name. | ||
|
||
:param name: Name of the function. | ||
:param setting_extra_fields: Keyword arguments for specifying | ||
additional setting fields | ||
:return: Decorator function. | ||
""" | ||
|
||
@self._configure_function_builder | ||
def wrap(fb): | ||
def decorator(): | ||
fb.add_setting(setting=FunctionName( | ||
function_name=name, | ||
**setting_extra_fields)) | ||
return fb | ||
|
||
return decorator() | ||
|
||
return wrap | ||
|
||
def retry(self, | ||
strategy: str, | ||
max_retry_count: str, | ||
delay_interval: Optional[str] = None, | ||
minimum_interval: Optional[str] = None, | ||
maximum_interval: Optional[str] = None, | ||
setting_extra_fields: Dict[str, Any] = {}, | ||
) -> Callable[..., Any]: | ||
"""The retry decorator adds :class:`RetryPolicy` to the function | ||
settings object for building :class:`Function` object used in worker | ||
function indexing model. This is equivalent to defining RetryPolicy | ||
in the function.json which enables function to retry on failure. | ||
All optional fields will be given default value by function host when | ||
they are parsed by function host. | ||
|
||
Ref: https://aka.ms/azure_functions_retries | ||
|
||
:param strategy: The retry strategy to use. | ||
:param max_retry_count: The maximum number of retry attempts. | ||
:param delay_interval: The delay interval between retry attempts. | ||
:param minimum_interval: The minimum delay interval between retry | ||
attempts. | ||
:param maximum_interval: The maximum delay interval between retry | ||
attempts. | ||
:param setting_extra_fields: Keyword arguments for specifying | ||
additional setting fields. | ||
:return: Decorator function. | ||
""" | ||
|
||
@self._configure_function_builder | ||
def wrap(fb): | ||
def decorator(): | ||
fb.add_setting(setting=RetryPolicy( | ||
strategy=strategy, | ||
max_retry_count=max_retry_count, | ||
minimum_interval=minimum_interval, | ||
maximum_interval=maximum_interval, | ||
delay_interval=delay_interval, | ||
**setting_extra_fields)) | ||
return fb | ||
|
||
return decorator() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we planning to add validation to ensure basic combinations are not missed? Better to raise it here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. Validations will be in the host. If we add a validation here it will hide the host error and only show no functions found in the logs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should at least send a warning - better wording of the validation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed this offline can validations across the library coming later. |
||
|
||
return wrap | ||
|
||
|
||
class FunctionRegister(DecoratorApi, HttpFunctionsAuthLevelMixin, ABC): | ||
def __init__(self, auth_level: Union[AuthLevel, str], *args, **kwargs): | ||
"""Interface for declaring top level function app class which will | ||
|
@@ -1987,7 +2072,7 @@ def register_functions(self, function_container: DecoratorApi) -> None: | |
register_blueprint = register_functions | ||
|
||
|
||
class FunctionApp(FunctionRegister, TriggerApi, BindingApi): | ||
class FunctionApp(FunctionRegister, TriggerApi, BindingApi, SettingsApi): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is becoming more complicated day by day |
||
"""FunctionApp object used by worker function indexing model captures | ||
user defined functions and metadata. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. | ||
|
||
from azure.functions.decorators.core import Setting | ||
|
||
FUNCTION_NAME = "function_name" | ||
|
||
|
||
class FunctionName(Setting): | ||
|
||
def __init__(self, function_name: str, | ||
**kwargs): | ||
self.function_name = function_name | ||
super().__init__(setting_name=FUNCTION_NAME) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. | ||
from typing import Optional | ||
|
||
from azure.functions.decorators.core import Setting | ||
|
||
RETRY_POLICY = "retry_policy" | ||
|
||
|
||
class RetryPolicy(Setting): | ||
|
||
def __init__(self, | ||
strategy: str, | ||
max_retry_count: str, | ||
delay_interval: Optional[str] = None, | ||
minimum_interval: Optional[str] = None, | ||
maximum_interval: Optional[str] = None, | ||
**kwargs): | ||
self.strategy = strategy | ||
self.max_retry_count = max_retry_count | ||
self.delay_interval = delay_interval | ||
self.minimum_interval = minimum_interval | ||
self.maximum_interval = maximum_interval | ||
super().__init__(setting_name=RETRY_POLICY) |
Uh oh!
There was an error while loading. Please reload this page.