Skip to content

Commit

Permalink
fix: enforcing unique function names (#216)
Browse files Browse the repository at this point in the history
* unique function names

* added blueprint tests

* changing test function names

* lint

* lint

* fixed test_decorator tests

* fixed function_app tests

* configurable fx name for wsgi / asgi

* lint

* last test

* dapr test fx names

* feedback

* missed refs

* incorrect check

* missed reg

* type hint

---------

Co-authored-by: Victoria Hall <[email protected]>
  • Loading branch information
hallvictoria and Victoria Hall authored Jul 22, 2024
1 parent de37877 commit b2e31ec
Show file tree
Hide file tree
Showing 4 changed files with 526 additions and 142 deletions.
46 changes: 37 additions & 9 deletions azure/functions/decorators/function_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ def __str__(self):


class FunctionBuilder(object):
function_bindings: dict = {}

def __init__(self, func, function_script_file):
self._function = Function(func, function_script_file)

Expand Down Expand Up @@ -232,6 +234,12 @@ def _validate_function(self,
"""
Validates the function information before building the function.
Functions with the same function name are not supported and should
fail indexing. If a function name is not defined, the default is the
method name. This also means that two functions with the same
method name will also fail indexing.
https://github.com/Azure/azure-functions-python-worker/issues/1489
:param auth_level: Http auth level that will be set if http
trigger function auth level is None.
"""
Expand Down Expand Up @@ -262,6 +270,16 @@ def _validate_function(self,
parse_singular_param_to_enum(auth_level, AuthLevel))
self._function._is_http_function = True

# This dict contains the function name and its bindings for all
# functions in an app. If a previous function has the same name,
# indexing will fail here.
if self.function_bindings.get(function_name, None):
raise ValueError(
f"Function {function_name} does not have a unique"
f" function name. Please change @app.function_name() or"
f" the function method name to be unique.")
self.function_bindings[function_name] = bindings

def build(self, auth_level: Optional[AuthLevel] = None) -> Function:
"""
Validates and builds the function object.
Expand Down Expand Up @@ -3333,11 +3351,13 @@ class ExternalHttpFunctionApp(
@abc.abstractmethod
def _add_http_app(self,
http_middleware: Union[
AsgiMiddleware, WsgiMiddleware]) -> None:
AsgiMiddleware, WsgiMiddleware],
function_name: str = 'http_app_func') -> None:
"""Add a Wsgi or Asgi app integrated http function.
:param http_middleware: :class:`WsgiMiddleware`
or class:`AsgiMiddleware` instance.
:param function_name: name for the function
:return: None
"""
Expand All @@ -3346,17 +3366,18 @@ def _add_http_app(self,

class AsgiFunctionApp(ExternalHttpFunctionApp):
def __init__(self, app,
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION):
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION,
function_name: str = 'http_app_func'):
"""Constructor of :class:`AsgiFunctionApp` object.
:param app: asgi app object.
:param http_auth_level: Determines what keys, if any, need to be
present
on the request in order to invoke the function.
present on the request in order to invoke the function.
:param function_name: function name
"""
super().__init__(auth_level=http_auth_level)
self.middleware = AsgiMiddleware(app)
self._add_http_app(self.middleware)
self._add_http_app(self.middleware, function_name)
self.startup_task_done = False

def __del__(self):
Expand All @@ -3365,7 +3386,8 @@ def __del__(self):

def _add_http_app(self,
http_middleware: Union[
AsgiMiddleware, WsgiMiddleware]) -> None:
AsgiMiddleware, WsgiMiddleware],
function_name: str = 'http_app_func') -> None:
"""Add an Asgi app integrated http function.
:param http_middleware: :class:`WsgiMiddleware`
Expand All @@ -3379,6 +3401,7 @@ def _add_http_app(self,

asgi_middleware: AsgiMiddleware = http_middleware

@self.function_name(name=function_name)
@self.http_type(http_type='asgi')
@self.route(methods=(method for method in HttpMethod),
auth_level=self.auth_level,
Expand All @@ -3395,21 +3418,25 @@ async def http_app_func(req: HttpRequest, context: Context):

class WsgiFunctionApp(ExternalHttpFunctionApp):
def __init__(self, app,
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION):
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION,
function_name: str = 'http_app_func'):
"""Constructor of :class:`WsgiFunctionApp` object.
:param app: wsgi app object.
:param function_name: function name
"""
super().__init__(auth_level=http_auth_level)
self._add_http_app(WsgiMiddleware(app))
self._add_http_app(WsgiMiddleware(app), function_name)

def _add_http_app(self,
http_middleware: Union[
AsgiMiddleware, WsgiMiddleware]) -> None:
AsgiMiddleware, WsgiMiddleware],
function_name: str = 'http_app_func') -> None:
"""Add a Wsgi app integrated http function.
:param http_middleware: :class:`WsgiMiddleware`
or class:`AsgiMiddleware` instance.
:param function_name: name for the function
:return: None
"""
Expand All @@ -3419,6 +3446,7 @@ def _add_http_app(self,

wsgi_middleware: WsgiMiddleware = http_middleware

@self.function_name(function_name)
@self.http_type(http_type='wsgi')
@self.route(methods=(method for method in HttpMethod),
auth_level=self.auth_level,
Expand Down
18 changes: 9 additions & 9 deletions tests/decorators/test_dapr.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_dapr_service_invocation_trigger_default_args(self):

@app.dapr_service_invocation_trigger(arg_name="req",
method_name="dummy_method_name")
def dummy():
def test_dapr_service_invocation_trigger_default_args():
pass

func = self._get_user_function(app)
Expand All @@ -50,7 +50,7 @@ def test_dapr_binding_trigger_default_args(self):

@app.dapr_binding_trigger(arg_name="req",
binding_name="dummy_binding_name")
def dummy():
def test_dapr_binding_trigger_default_args():
pass

func = self._get_user_function(app)
Expand All @@ -73,7 +73,7 @@ def test_dapr_topic_trigger_default_args(self):
pub_sub_name="dummy_pub_sub_name",
topic="dummy_topic",
route="/dummy_route")
def dummy():
def test_dapr_topic_trigger_default_args():
pass

func = self._get_user_function(app)
Expand All @@ -99,7 +99,7 @@ def test_dapr_state_input_binding(self):
@app.dapr_state_input(arg_name="in",
state_store="dummy_state_store",
key="dummy_key")
def dummy():
def test_dapr_state_input_binding():
pass

func = self._get_user_function(app)
Expand All @@ -125,7 +125,7 @@ def test_dapr_secret_input_binding(self):
secret_store_name="dummy_secret_store_name",
key="dummy_key",
metadata="dummy_metadata")
def dummy():
def test_dapr_secret_input_binding():
pass

func = self._get_user_function(app)
Expand All @@ -151,7 +151,7 @@ def test_dapr_state_output_binding(self):
@app.dapr_state_output(arg_name="out",
state_store="dummy_state_store",
key="dummy_key")
def dummy():
def test_dapr_state_output_binding():
pass

func = self._get_user_function(app)
Expand All @@ -177,7 +177,7 @@ def test_dapr_invoke_output_binding(self):
app_id="dummy_app_id",
method_name="dummy_method_name",
http_verb="dummy_http_verb")
def dummy():
def test_dapr_invoke_output_binding():
pass

func = self._get_user_function(app)
Expand All @@ -203,7 +203,7 @@ def test_dapr_publish_output_binding(self):
@app.dapr_publish_output(arg_name="out",
pub_sub_name="dummy_pub_sub_name",
topic="dummy_topic")
def dummy():
def test_dapr_publish_output_binding():
pass

func = self._get_user_function(app)
Expand All @@ -228,7 +228,7 @@ def test_dapr_binding_output_binding(self):
@app.dapr_binding_output(arg_name="out",
binding_name="dummy_binding_name",
operation="dummy_operation")
def dummy():
def test_dapr_binding_output_binding():
pass

func = self._get_user_function(app)
Expand Down
Loading

0 comments on commit b2e31ec

Please sign in to comment.