diff --git a/setup.py b/setup.py index 890509a9a..73682784c 100644 --- a/setup.py +++ b/setup.py @@ -138,7 +138,13 @@ "pytest-randomly", "pytest-instafail", "pytest-rerunfailures", - "ptvsd" + "ptvsd", + "python-dotenv", + "plotly", + "scikit-learn", + "opencv-python", + "pandas", + "numpy" ] } diff --git a/tests/endtoend/eventhub_batch_functions/eventhub_batch_functions_stein/function_app.py b/tests/endtoend/eventhub_batch_functions/eventhub_batch_functions_stein/function_app.py index cd210c8bd..3163f126a 100644 --- a/tests/endtoend/eventhub_batch_functions/eventhub_batch_functions_stein/function_app.py +++ b/tests/endtoend/eventhub_batch_functions/eventhub_batch_functions_stein/function_app.py @@ -41,7 +41,6 @@ def eventhub_multiple(events): @app.route(route="eventhub_output_batch", binding_arg_name="out") def eventhub_output_batch(req: func.HttpRequest, out: func.Out[str]) -> str: events = req.get_body().decode('utf-8') - out.set('hello') return events diff --git a/tests/endtoend/http_functions/common_libs_functions/common_libs_functions_stein/function_app.py b/tests/endtoend/http_functions/common_libs_functions/common_libs_functions_stein/function_app.py new file mode 100644 index 000000000..fcc4b2ca7 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/common_libs_functions_stein/function_app.py @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging +import azure.functions as func +import numpy as np +import requests +import cv2 +from pandas import DataFrame +from sklearn.datasets import load_iris +import plotly +import dotenv + + +app = func.FunctionApp() + + +@app.route(route="dotenv_func") +def dotenv_func(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + res = "found" if "load_dotenv" in dotenv.__all__ else "not found" + + return func.HttpResponse(res) + + +@app.route(route="numpy_func") +def numpy_func(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + res = "array: {}".format(np.array([1, 2], dtype=complex)) + + return func.HttpResponse(res) + + +@app.route(route="opencv_func") +def opencv_func(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + res = "opencv version: {}".format(cv2.__version__) + + return func.HttpResponse(res) + + +@app.route(route="pandas_func") +def pandas_func(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + array = np.arange(6).reshape(3, 2) + df = DataFrame(array, columns=['x', 'y'], index=['T1', 'T2', 'T3']) + + res = "two-dimensional DataFrame: \n {}".format(df) + + return func.HttpResponse(res) + + +@app.route(route="plotly_func") +def plotly_func(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + res = "plotly version: {}".format(plotly.__version__) + + return func.HttpResponse(res) + + +@app.route(route="requests_func") +def requests_func(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + req = requests.get('https://github.com') + res = "req status code: {}".format(req.status_code) + + return func.HttpResponse(res) + + +@app.route(route="sklearn_func") +def sklearn_func(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + iris = load_iris() + + res = "First 5 records of array: \n {}".format(iris.data[:5]) + + return func.HttpResponse(res) diff --git a/tests/endtoend/http_functions/common_libs_functions/dotenv_func/__init__.py b/tests/endtoend/http_functions/common_libs_functions/dotenv_func/__init__.py new file mode 100644 index 000000000..aaf3a753c --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/dotenv_func/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging +import dotenv +import azure.functions as func + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + res = "found" if "load_dotenv" in dotenv.__all__ else "not found" + + return func.HttpResponse(res) diff --git a/tests/endtoend/http_functions/common_libs_functions/dotenv_func/function.json b/tests/endtoend/http_functions/common_libs_functions/dotenv_func/function.json new file mode 100644 index 000000000..b8dc650e9 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/dotenv_func/function.json @@ -0,0 +1,20 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/tests/endtoend/http_functions/common_libs_functions/numpy_func/__init__.py b/tests/endtoend/http_functions/common_libs_functions/numpy_func/__init__.py new file mode 100644 index 000000000..57d5d08e2 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/numpy_func/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging +import azure.functions as func +import numpy as np + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + res = "array: {}".format(np.array([1, 2], dtype=complex)) + + return func.HttpResponse(res) diff --git a/tests/endtoend/http_functions/common_libs_functions/numpy_func/function.json b/tests/endtoend/http_functions/common_libs_functions/numpy_func/function.json new file mode 100644 index 000000000..b8dc650e9 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/numpy_func/function.json @@ -0,0 +1,20 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/tests/endtoend/http_functions/common_libs_functions/opencv_func/__init__.py b/tests/endtoend/http_functions/common_libs_functions/opencv_func/__init__.py new file mode 100644 index 000000000..8ba1d4364 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/opencv_func/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging +import cv2 +import azure.functions as func + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + res = "opencv version: {}".format(cv2.__version__) + + return func.HttpResponse(res) diff --git a/tests/endtoend/http_functions/common_libs_functions/opencv_func/function.json b/tests/endtoend/http_functions/common_libs_functions/opencv_func/function.json new file mode 100644 index 000000000..b8dc650e9 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/opencv_func/function.json @@ -0,0 +1,20 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/tests/endtoend/http_functions/common_libs_functions/pandas_func/__init__.py b/tests/endtoend/http_functions/common_libs_functions/pandas_func/__init__.py new file mode 100644 index 000000000..7c36c90f5 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/pandas_func/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging +import azure.functions as func +import numpy as np +from pandas import DataFrame + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + array = np.arange(6).reshape(3, 2) + df = DataFrame(array, columns=['x', 'y'], index=['T1', 'T2', 'T3']) + + res = "two-dimensional DataFrame: \n {}".format(df) + + return func.HttpResponse(res) diff --git a/tests/endtoend/http_functions/common_libs_functions/pandas_func/function.json b/tests/endtoend/http_functions/common_libs_functions/pandas_func/function.json new file mode 100644 index 000000000..b8dc650e9 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/pandas_func/function.json @@ -0,0 +1,20 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/tests/endtoend/http_functions/common_libs_functions/plotly_func/__init__.py b/tests/endtoend/http_functions/common_libs_functions/plotly_func/__init__.py new file mode 100644 index 000000000..570902b40 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/plotly_func/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging +import plotly +import azure.functions as func + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + res = "plotly version: {}".format(plotly.__version__) + + return func.HttpResponse(res) diff --git a/tests/endtoend/http_functions/common_libs_functions/plotly_func/function.json b/tests/endtoend/http_functions/common_libs_functions/plotly_func/function.json new file mode 100644 index 000000000..b8dc650e9 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/plotly_func/function.json @@ -0,0 +1,20 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/tests/endtoend/http_functions/common_libs_functions/requests_func/__init__.py b/tests/endtoend/http_functions/common_libs_functions/requests_func/__init__.py new file mode 100644 index 000000000..48d8715d1 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/requests_func/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging +import azure.functions as func +import requests + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + req = requests.get('https://github.com') + res = "req status code: {}".format(req.status_code) + + return func.HttpResponse(res) diff --git a/tests/endtoend/http_functions/common_libs_functions/requests_func/function.json b/tests/endtoend/http_functions/common_libs_functions/requests_func/function.json new file mode 100644 index 000000000..b8dc650e9 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/requests_func/function.json @@ -0,0 +1,20 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/tests/endtoend/http_functions/common_libs_functions/sklearn_func/__init__.py b/tests/endtoend/http_functions/common_libs_functions/sklearn_func/__init__.py new file mode 100644 index 000000000..4ec8d35f1 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/sklearn_func/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging +import azure.functions as func +from sklearn.datasets import load_iris + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('Python HTTP trigger function processed a request.') + + iris = load_iris() + + res = "First 5 records of array: \n {}".format(iris.data[:5]) + + return func.HttpResponse(res) diff --git a/tests/endtoend/http_functions/common_libs_functions/sklearn_func/function.json b/tests/endtoend/http_functions/common_libs_functions/sklearn_func/function.json new file mode 100644 index 000000000..b8dc650e9 --- /dev/null +++ b/tests/endtoend/http_functions/common_libs_functions/sklearn_func/function.json @@ -0,0 +1,20 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/tests/endtoend/test_http_functions.py b/tests/endtoend/test_http_functions.py index 167aadb4d..952060c58 100644 --- a/tests/endtoend/test_http_functions.py +++ b/tests/endtoend/test_http_functions.py @@ -21,6 +21,7 @@ class TestHttpFunctions(testutils.WebHostTestCase): Compared to the unittests/test_http_functions.py, this file is more focus on testing the E2E flow scenarios. """ + def setUp(self): self._patch_environ = patch.dict('os.environ', os.environ.copy()) self._patch_environ.start() @@ -112,7 +113,7 @@ class TestHttpFunctionsStein(TestHttpFunctions): @classmethod def get_script_dir(cls): - return testutils.E2E_TESTS_FOLDER / 'http_functions' /\ + return testutils.E2E_TESTS_FOLDER / 'http_functions' / \ 'http_functions_stein' @@ -120,6 +121,85 @@ class TestHttpFunctionsSteinGeneric(TestHttpFunctions): @classmethod def get_script_dir(cls): - return testutils.E2E_TESTS_FOLDER / 'http_functions' /\ - 'http_functions_stein' /\ + return testutils.E2E_TESTS_FOLDER / 'http_functions' / \ + 'http_functions_stein' / \ 'generic' + + +class TestCommonLibsHttpFunctions(testutils.WebHostTestCase): + """Test the common libs scenarios in the local webhost. + + This test class will spawn a webhost from your /build/webhost + folder and replace the built-in Python with azure_functions_worker from + your code base. this file is more focus on testing the E2E flow scenarios. + """ + + @classmethod + def get_script_dir(cls): + return testutils.E2E_TESTS_FOLDER / 'http_functions' / \ + 'common_libs_functions' + + @testutils.retryable_test(3, 5) + def test_numpy(self): + r = self.webhost.request('GET', 'numpy_func', + timeout=REQUEST_TIMEOUT_SEC) + + res = "array: [1.+0.j 2.+0.j]" + + self.assertEqual(r.content.decode("UTF-8"), res) + + @testutils.retryable_test(3, 5) + def test_requests(self): + r = self.webhost.request('GET', 'requests_func', + timeout=10) + + self.assertTrue(r.ok) + self.assertEqual(r.content.decode("UTF-8"), 'req status code: 200') + + @testutils.retryable_test(3, 5) + def test_pandas(self): + r = self.webhost.request('GET', 'pandas_func', + timeout=REQUEST_TIMEOUT_SEC) + + self.assertIn("two-dimensional", + r.content.decode("UTF-8")) + + @testutils.retryable_test(3, 5) + def test_sklearn(self): + r = self.webhost.request('GET', 'sklearn_func', + timeout=REQUEST_TIMEOUT_SEC) + + self.assertIn("First 5 records of array:", + r.content.decode("UTF-8")) + + @testutils.retryable_test(3, 5) + def test_opencv(self): + r = self.webhost.request('GET', 'opencv_func', + timeout=REQUEST_TIMEOUT_SEC) + + self.assertIn("opencv version:", + r.content.decode("UTF-8")) + + @testutils.retryable_test(3, 5) + def test_dotenv(self): + r = self.webhost.request('GET', 'dotenv_func', + timeout=REQUEST_TIMEOUT_SEC) + + self.assertEqual(r.content.decode("UTF-8"), "found") + + @testutils.retryable_test(3, 5) + def test_plotly(self): + r = self.webhost.request('GET', 'plotly_func', + timeout=REQUEST_TIMEOUT_SEC) + + self.assertIn("plotly version:", + r.content.decode("UTF-8")) + + +class TestCommonLibsHttpFunctionsStein(TestCommonLibsHttpFunctions): + + @classmethod + def get_script_dir(cls): + return testutils.E2E_TESTS_FOLDER / 'http_functions' / \ + 'common_libs_functions' / \ + 'common_libs_functions_stein'