Skip to content

fix: enforcing unique function names #216

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

Merged
merged 21 commits into from
Jul 22, 2024
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
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