Skip to content
9 changes: 5 additions & 4 deletions runtimes/v1/tests/unittests/test_handle_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import azure_functions_runtime_v1.handle_event as handle_event
import tests.protos as test_protos

from azure_functions_runtime_v1.handle_event import (worker_init_request,
functions_metadata_request,
function_load_request,
function_environment_reload_request)
from azure_functions_runtime_v1.handle_event import (
worker_init_request,
functions_metadata_request,
function_load_request,
function_environment_reload_request)
from tests.utils import testutils
from tests.utils.constants import UNIT_TESTS_FOLDER
from tests.utils.mock_classes import FunctionRequest, Metadata, Request, WorkerRequest
Expand Down
2 changes: 1 addition & 1 deletion runtimes/v1/tests/unittests/test_opentelemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from azure_functions_runtime_v1.handle_event import otel_manager, worker_init_request
from azure_functions_runtime_v1.otel import (initialize_azure_monitor,
update_opentelemetry_status)
update_opentelemetry_status)
from azure_functions_runtime_v1.logging import logger
from tests.utils.constants import UNIT_TESTS_FOLDER
from tests.utils.mock_classes import FunctionRequest, Request, WorkerRequest
Expand Down
4 changes: 2 additions & 2 deletions runtimes/v1/tests/unittests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from unittest.mock import patch

from azure_functions_runtime_v1.utils import (app_setting_manager,
helpers,
wrappers)
helpers,
wrappers)

TEST_APP_SETTING_NAME = "TEST_APP_SETTING_NAME"
TEST_FEATURE_FLAG = "APP_SETTING_FEATURE_FLAG"
Expand Down
10 changes: 5 additions & 5 deletions runtimes/v2/azure_functions_runtime/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,15 @@ def validate_function_params(params: dict, bound_params: dict,
raise FunctionLoadError(
func_name,
'Function parameter mismatch — the following trigger/input bindings '
'are declared in the function decorators but missing from the '
'Python function signature: ' + repr(set(params) - set(bound_params)))
'are declared in Python but missing from the '
'function decorator: ' + repr(set(params) - set(bound_params)))

if set(bound_params) - set(params):
raise FunctionLoadError(
func_name,
'Extra parameters in function signature — the following parameters '
'are present in the Python function definition but are not declared '
'as bindings: ' + repr(set(params) - set(bound_params)))
'Extra parameters in binding definition — the following parameters '
'are declared as bindings but are not '
'present in Python: ' + repr(set(params) - set(bound_params)))

input_types: typing.Dict[str, ParamTypeInfo] = {}
output_types: typing.Dict[str, ParamTypeInfo] = {}
Expand Down
6 changes: 3 additions & 3 deletions runtimes/v2/tests/unittests/test_handle_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import tests.protos as test_protos

from azure_functions_runtime.handle_event import (worker_init_request,
functions_metadata_request,
function_load_request,
function_environment_reload_request)
functions_metadata_request,
function_load_request,
function_environment_reload_request)
from tests.utils import testutils
from tests.utils.constants import UNIT_TESTS_FOLDER
from tests.utils.mock_classes import FunctionRequest, Request, WorkerRequest
Expand Down
2 changes: 1 addition & 1 deletion runtimes/v2/tests/unittests/test_opentelemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from azure_functions_runtime.handle_event import otel_manager, worker_init_request
from azure_functions_runtime.otel import (initialize_azure_monitor,
update_opentelemetry_status)
update_opentelemetry_status)
from azure_functions_runtime.logging import logger
from tests.utils.constants import UNIT_TESTS_FOLDER
from tests.utils.mock_classes import FunctionRequest, Request, WorkerRequest
Expand Down
6 changes: 3 additions & 3 deletions runtimes/v2/tests/unittests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from unittest.mock import patch

from azure_functions_runtime.utils import (app_setting_manager,
helpers,
validators,
wrappers)
helpers,
validators,
wrappers)

TEST_APP_SETTING_NAME = "TEST_APP_SETTING_NAME"
TEST_FEATURE_FLAG = "APP_SETTING_FEATURE_FLAG"
Expand Down
18 changes: 12 additions & 6 deletions workers/azure_functions_worker/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,15 @@ def validate_function_params(params: dict, bound_params: dict,
if set(params) - set(bound_params):
raise FunctionLoadError(
func_name,
'the following parameters are declared in Python but '
f'not in function.json: {set(params) - set(bound_params)!r}')
'the following parameters are declared in Python '
'but not in the function definition (function.json or '
f'function decorators): {set(params) - set(bound_params)!r}')

if set(bound_params) - set(params):
raise FunctionLoadError(
func_name,
f'the following parameters are declared in function.json but '
'the following parameters are declared in the function '
'definition (function.json or function decorators) but '
f'not in Python: {set(bound_params) - set(params)!r}')

input_types: typing.Dict[str, ParamTypeInfo] = {}
Expand Down Expand Up @@ -221,7 +223,8 @@ def validate_function_params(params: dict, bound_params: dict,
raise FunctionLoadError(
func_name,
f'binding {param.name} is declared to have the "in" '
'direction in function.json, but its annotation '
'direction in the function definition (function.json '
'or function decorators), but its annotation '
'is azure.functions.Out in Python')

if param_has_anno and param_py_type in (str, bytes) and (
Expand All @@ -244,13 +247,16 @@ def validate_function_params(params: dict, bound_params: dict,
func_name,
f'{param.name!r} binding type "{binding.type}" '
f'and dataType "{binding.data_type}" in '
f'function.json do not match the corresponding '
'function definition (function.json or function '
'decorators) do not match the corresponding '
f'function parameter\'s Python type '
f'annotation "{param_py_type.__name__}"')
else:
raise FunctionLoadError(
func_name,
f'type of {param.name} binding in function.json '
f'type of {param.name} binding in function '
'definition (function.json or function '
'decorators) '
f'"{binding.type}" does not match its Python '
f'annotation "{param_py_type.__name__}"')

Expand Down
21 changes: 17 additions & 4 deletions workers/azure_functions_worker/http_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
X_MS_INVOCATION_ID,
)
from azure_functions_worker.logging import logger
from azure_functions_worker.utils.common import is_envvar_false
from azure_functions_worker.utils.common import is_envvar_true


# Http V2 Exceptions
Expand All @@ -28,6 +28,11 @@ class MissingHeaderError(ValueError):
HTTP request."""


class AppSettingDisabledError(Exception):
"""Exception raised when the extension class is included
but PYTHON_ENABLE_INIT_INDEXING is not set or is set to false."""


class BaseContextReference(abc.ABC):
"""
Base class for context references.
Expand Down Expand Up @@ -279,14 +284,22 @@ def ext_base(cls):

@classmethod
def _check_http_v2_enabled(cls):
if sys.version_info.minor < BASE_EXT_SUPPORTED_PY_MINOR_VERSION or \
is_envvar_false(PYTHON_ENABLE_INIT_INDEXING):
init_indexing_enabled = is_envvar_true(PYTHON_ENABLE_INIT_INDEXING)
if sys.version_info.minor < BASE_EXT_SUPPORTED_PY_MINOR_VERSION:
return False

import azurefunctions.extensions.base as ext_base
cls._ext_base = ext_base

return cls._ext_base.HttpV2FeatureChecker.http_v2_enabled()
http_v2_enabled = cls._ext_base.HttpV2FeatureChecker.http_v2_enabled()
if http_v2_enabled and not init_indexing_enabled:
raise AppSettingDisabledError("HTTP Streaming is enabled but "
"PYTHON_ENABLE_INIT_INDEXING "
"is not set or is set to false. "
"See aka.ms/functions-python-streaming "
"for more information")

return http_v2_enabled


http_coordinator = HttpCoordinator()
43 changes: 43 additions & 0 deletions workers/tests/extension_tests/http_v2_tests/test_http_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,46 @@ def send_request():
self.assertTrue(ok)
complete_data = b"".join(data_chunks)
self.assertEqual(content, complete_data)


@unittest.skipIf(is_envvar_true(DEDICATED_DOCKER_TEST)
or is_envvar_true(CONSUMPTION_DOCKER_TEST),
"Tests are flaky when running on Docker")
@unittest.skipIf(sys.version_info.minor < 8, "HTTPv2"
"is only supported for 3.8+.")
@unittest.skipIf(sys.version_info.minor >= 13,
"App Setting is not needed for 3.13+")
class TestHttpFunctionsWithInitIndexingDisabled(testutils.WebHostTestCase):
@classmethod
def setUpClass(cls):
cls.env_variables[PYTHON_ENABLE_INIT_INDEXING] = '0'
os.environ[PYTHON_ENABLE_INIT_INDEXING] = "0"
super().setUpClass()

@classmethod
def tearDownClass(cls):
os.environ.pop(PYTHON_ENABLE_INIT_INDEXING)
super().tearDownClass()

@classmethod
def get_environment_variables(cls):
return cls.env_variables

@classmethod
def get_script_dir(cls):
return testutils.EXTENSION_TESTS_FOLDER / 'http_v2_tests' / \
'http_functions_v2' / \
'fastapi'

@classmethod
def get_libraries_to_install(cls):
return ['azurefunctions-extensions-http-fastapi', 'orjson', 'ujson']

@testutils.retryable_test(3, 5)
def test_return_streaming_disabled(self):
"""Test if the return_streaming function returns an error"""
root_url = self.webhost._addr
streaming_url = f'{root_url}/api/return_streaming'
r = requests.get(
streaming_url, timeout=REQUEST_TIMEOUT_SEC, stream=True)
self.assertFalse(r.ok)
20 changes: 12 additions & 8 deletions workers/tests/unittests/test_broken_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async def test_load_broken__missing_py_param(self):
self.assertRegex(
r.response.result.exception.message,
r".*cannot load the missing_py_param function"
r".*parameters are declared in function.json"
r".*parameters are declared in the function definition"
r".*'req'.*")

async def test_load_broken__missing_json_param(self):
Expand Down Expand Up @@ -218,7 +218,8 @@ async def test_load_broken__invalid_http_trigger_anno(self):
self.assertEqual(
r.response.result.exception.message,
'FunctionLoadError: cannot load the invalid_http_trigger_anno'
' function: type of req binding in function.json "httpTrigger" '
' function: type of req binding in function definition '
'(function.json or function decorators) "httpTrigger" '
'does not match its Python annotation "int"')

async def test_load_broken__invalid_out_anno(self):
Expand All @@ -234,7 +235,8 @@ async def test_load_broken__invalid_out_anno(self):
self.assertEqual(
r.response.result.exception.message,
'FunctionLoadError: cannot load the invalid_out_anno function: '
r'type of ret binding in function.json "http" '
r'type of ret binding in function definition '
r'(function.json or function decorators) "http" '
r'does not match its Python annotation "HttpRequest"')

async def test_load_broken__invalid_in_anno(self):
Expand All @@ -250,7 +252,8 @@ async def test_load_broken__invalid_in_anno(self):
self.assertEqual(
r.response.result.exception.message,
'FunctionLoadError: cannot load the invalid_in_anno function: '
r'type of req binding in function.json "httpTrigger" '
r'type of req binding in function definition '
r'(function.json or function decorators) "httpTrigger" '
r'does not match its Python annotation "HttpResponse"')

async def test_load_broken__invalid_datatype(self):
Expand All @@ -265,10 +268,11 @@ async def test_load_broken__invalid_datatype(self):

self.assertRegex(
r.response.result.exception.message,
r'.*cannot load the invalid_datatype function: '
r'.*binding type "httpTrigger" and dataType "1" in '
r'function.json do not match the corresponding function '
r'parameter.* Python type annotation "HttpResponse"')
r'.*FunctionLoadError: cannot load the invalid_datatype function: '
r'.*binding type "httpTrigger".*dataType "1".*do not match the '
r'corresponding function parameter\'s Python type '
r'annotation "HttpResponse"'
)

async def test_load_broken__invalid_in_anno_non_type(self):
async with testutils.start_mockhost(
Expand Down