diff --git a/azure_functions_worker/loader.py b/azure_functions_worker/loader.py index 36787e65..a5ec98f3 100644 --- a/azure_functions_worker/loader.py +++ b/azure_functions_worker/loader.py @@ -143,18 +143,19 @@ def index_function_app(function_path: str): module_name = pathlib.Path(function_path).stem imported_module = importlib.import_module(module_name) - from azure.functions import FunctionApp - app: Optional[FunctionApp] = None + from azure.functions import FunctionRegister + app: Optional[FunctionRegister] = None for i in imported_module.__dir__(): - if isinstance(getattr(imported_module, i, None), FunctionApp): + if isinstance(getattr(imported_module, i, None), FunctionRegister): if not app: app = getattr(imported_module, i, None) else: raise ValueError( - "Multiple instances of FunctionApp are defined") + f"More than one {app.__class__.__name__} or other top " + f"level function app instances are defined.") if not app: - raise ValueError("Could not find instance of FunctionApp in " + raise ValueError("Could not find top level function app instances in " f"{SCRIPT_FILE_NAME}.") return app.get_functions() diff --git a/setup.py b/setup.py index ab16cd39..0ffe06ec 100644 --- a/setup.py +++ b/setup.py @@ -105,7 +105,7 @@ INSTALL_REQUIRES = [ "grpcio~=1.43.0", "grpcio-tools~=1.43.0", "protobuf~=3.19.3", - 'azure-functions==1.11.3b2', + "azure-functions==1.11.3b2", "python-dateutil~=2.8.2" ] diff --git a/tests/endtoend/blueprint_functions/functions_in_blueprint_only/blueprint.py b/tests/endtoend/blueprint_functions/functions_in_blueprint_only/blueprint.py new file mode 100644 index 00000000..78504939 --- /dev/null +++ b/tests/endtoend/blueprint_functions/functions_in_blueprint_only/blueprint.py @@ -0,0 +1,31 @@ +import logging + +import azure.functions as func + +bp = func.Blueprint() + + +@bp.route(route="default_template") +def default_template(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + name = req.params.get('name') + if not name: + try: + req_body = req.get_json() + except ValueError: + pass + else: + name = req_body.get('name') + + if name: + return func.HttpResponse( + f"Hello, {name}. This HTTP triggered function " + f"executed successfully.") + else: + return func.HttpResponse( + "This HTTP triggered function executed successfully. " + "Pass a name in the query string or in the request body for a" + " personalized response.", + status_code=200 + ) diff --git a/tests/endtoend/blueprint_functions/functions_in_blueprint_only/function_app.py b/tests/endtoend/blueprint_functions/functions_in_blueprint_only/function_app.py new file mode 100644 index 00000000..fb045265 --- /dev/null +++ b/tests/endtoend/blueprint_functions/functions_in_blueprint_only/function_app.py @@ -0,0 +1,6 @@ +import azure.functions as func +from blueprint import bp + +app = func.FunctionApp() + +app.register_functions(bp) diff --git a/tests/endtoend/blueprint_functions/functions_in_both_blueprint_functionapp/blueprint.py b/tests/endtoend/blueprint_functions/functions_in_both_blueprint_functionapp/blueprint.py new file mode 100644 index 00000000..78504939 --- /dev/null +++ b/tests/endtoend/blueprint_functions/functions_in_both_blueprint_functionapp/blueprint.py @@ -0,0 +1,31 @@ +import logging + +import azure.functions as func + +bp = func.Blueprint() + + +@bp.route(route="default_template") +def default_template(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + name = req.params.get('name') + if not name: + try: + req_body = req.get_json() + except ValueError: + pass + else: + name = req_body.get('name') + + if name: + return func.HttpResponse( + f"Hello, {name}. This HTTP triggered function " + f"executed successfully.") + else: + return func.HttpResponse( + "This HTTP triggered function executed successfully. " + "Pass a name in the query string or in the request body for a" + " personalized response.", + status_code=200 + ) diff --git a/tests/endtoend/blueprint_functions/functions_in_both_blueprint_functionapp/function_app.py b/tests/endtoend/blueprint_functions/functions_in_both_blueprint_functionapp/function_app.py new file mode 100644 index 00000000..4347b0c7 --- /dev/null +++ b/tests/endtoend/blueprint_functions/functions_in_both_blueprint_functionapp/function_app.py @@ -0,0 +1,13 @@ +import azure.functions as func + +from blueprint import bp + +app = func.FunctionApp() + +app.register_blueprint(bp) + + +@app.route(route="return_http") +def return_http(req: func.HttpRequest): + return func.HttpResponse('

Hello World™

', + mimetype='text/html') diff --git a/tests/endtoend/blueprint_functions/multiple_function_registers/function_app.py b/tests/endtoend/blueprint_functions/multiple_function_registers/function_app.py new file mode 100644 index 00000000..4277df8d --- /dev/null +++ b/tests/endtoend/blueprint_functions/multiple_function_registers/function_app.py @@ -0,0 +1,12 @@ +import azure.functions as func + +app = func.FunctionApp() + + +@app.route(route="return_http") +def return_http(req: func.HttpRequest): + return func.HttpResponse('

Hello World™

', + mimetype='text/html') + + +asgi_app = func.AsgiFunctionApp() diff --git a/tests/endtoend/blueprint_functions/only_blueprint/function_app.py b/tests/endtoend/blueprint_functions/only_blueprint/function_app.py new file mode 100644 index 00000000..78504939 --- /dev/null +++ b/tests/endtoend/blueprint_functions/only_blueprint/function_app.py @@ -0,0 +1,31 @@ +import logging + +import azure.functions as func + +bp = func.Blueprint() + + +@bp.route(route="default_template") +def default_template(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + name = req.params.get('name') + if not name: + try: + req_body = req.get_json() + except ValueError: + pass + else: + name = req_body.get('name') + + if name: + return func.HttpResponse( + f"Hello, {name}. This HTTP triggered function " + f"executed successfully.") + else: + return func.HttpResponse( + "This HTTP triggered function executed successfully. " + "Pass a name in the query string or in the request body for a" + " personalized response.", + status_code=200 + ) diff --git a/tests/endtoend/test_blueprint_functions.py b/tests/endtoend/test_blueprint_functions.py new file mode 100644 index 00000000..bb73fd91 --- /dev/null +++ b/tests/endtoend/test_blueprint_functions.py @@ -0,0 +1,59 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from azure_functions_worker import testutils + + +class TestFunctionInBluePrintOnly(testutils.WebHostTestCase): + @classmethod + def get_script_dir(cls): + return testutils.E2E_TESTS_FOLDER / 'blueprint_functions' / \ + 'functions_in_blueprint_only' + + @testutils.retryable_test(3, 5) + def test_function_in_blueprint_only(self): + r = self.webhost.request('GET', 'default_template') + self.assertTrue(r.ok) + + +class TestFunctionsInBothBlueprintAndFuncApp(testutils.WebHostTestCase): + @classmethod + def get_script_dir(cls): + return testutils.E2E_TESTS_FOLDER / 'blueprint_functions' / \ + 'functions_in_both_blueprint_functionapp' + + @testutils.retryable_test(3, 5) + def test_functions_in_both_blueprint_functionapp(self): + r = self.webhost.request('GET', 'default_template') + self.assertTrue(r.ok) + + r = self.webhost.request('GET', 'return_http') + self.assertTrue(r.ok) + + +class TestMultipleFunctionRegisters(testutils.WebHostTestCase): + @classmethod + def get_script_dir(cls): + return testutils.E2E_TESTS_FOLDER / 'blueprint_functions' / \ + 'multiple_function_registers' + + @testutils.retryable_test(3, 5) + def test_function_in_blueprint_only(self): + r = self.webhost.request('GET', 'return_http') + self.assertEqual(r.status_code, 404) + + +class TestOnlyBlueprint(testutils.WebHostTestCase): + @classmethod + def get_script_dir(cls): + return testutils.E2E_TESTS_FOLDER / 'blueprint_functions' / \ + 'only_blueprint' + + @testutils.retryable_test(3, 5) + def test_only_blueprint(self): + """Test if the default template of Http trigger in Python + Function app + will return OK + """ + r = self.webhost.request('GET', 'default_template') + self.assertEqual(r.status_code, 404) diff --git a/tests/endtoend/third_party_http_functions/stein/asgi_function/function_app.py b/tests/endtoend/third_party_http_functions/stein/asgi_function/function_app.py index 0654b349..c5570be6 100644 --- a/tests/endtoend/third_party_http_functions/stein/asgi_function/function_app.py +++ b/tests/endtoend/third_party_http_functions/stein/asgi_function/function_app.py @@ -37,5 +37,5 @@ async def raise_http_exception(): raise HTTPException(status_code=404, detail="Item not found") -app = func.FunctionApp(asgi_app=fast_app, - http_auth_level=func.AuthLevel.ANONYMOUS) +app = func.AsgiFunctionApp(app=fast_app, + http_auth_level=func.AuthLevel.ANONYMOUS) diff --git a/tests/endtoend/third_party_http_functions/stein/wsgi_function/function_app.py b/tests/endtoend/third_party_http_functions/stein/wsgi_function/function_app.py index 267fb85e..264a67a0 100644 --- a/tests/endtoend/third_party_http_functions/stein/wsgi_function/function_app.py +++ b/tests/endtoend/third_party_http_functions/stein/wsgi_function/function_app.py @@ -32,5 +32,5 @@ def raise_http_exception(): return {"detail": "Item not found"}, 404 -app = func.FunctionApp(wsgi_app=flask_app.wsgi_app, - http_auth_level=func.AuthLevel.ANONYMOUS) +app = func.WsgiFunctionApp(app=flask_app.wsgi_app, + http_auth_level=func.AuthLevel.ANONYMOUS) diff --git a/tests/unittests/third_party_http_functions/stein/asgi_function/function_app.py b/tests/unittests/third_party_http_functions/stein/asgi_function/function_app.py index 7ac32678..3248f25f 100644 --- a/tests/unittests/third_party_http_functions/stein/asgi_function/function_app.py +++ b/tests/unittests/third_party_http_functions/stein/asgi_function/function_app.py @@ -172,5 +172,5 @@ async def unhandled_unserializable_error(): raise UnserializableException('foo') -app = func.FunctionApp(asgi_app=fast_app, - http_auth_level=func.AuthLevel.ANONYMOUS) +app = func.AsgiFunctionApp(app=fast_app, + http_auth_level=func.AuthLevel.ANONYMOUS) diff --git a/tests/unittests/third_party_http_functions/stein/wsgi_function/function_app.py b/tests/unittests/third_party_http_functions/stein/wsgi_function/function_app.py index dbd2c311..d08b3dc6 100644 --- a/tests/unittests/third_party_http_functions/stein/wsgi_function/function_app.py +++ b/tests/unittests/third_party_http_functions/stein/wsgi_function/function_app.py @@ -99,5 +99,5 @@ def unhandled_unserializable_error(): raise UnserializableException('foo') -app = func.FunctionApp(wsgi_app=flask_app.wsgi_app, - http_auth_level=func.AuthLevel.ANONYMOUS) +app = func.WsgiFunctionApp(app=flask_app.wsgi_app, + http_auth_level=func.AuthLevel.ANONYMOUS)