From bb4c0e85077b32a1dfcab553bb75d3919fc4e6d3 Mon Sep 17 00:00:00 2001 From: AzureFunctionsPython Date: Thu, 20 Nov 2025 19:20:30 +0000 Subject: [PATCH 1/4] build: update workers version to 4.41.0 --- workers/azure_functions_worker/version.py | 2 +- workers/proxy_worker/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workers/azure_functions_worker/version.py b/workers/azure_functions_worker/version.py index af8be706..30e5c590 100644 --- a/workers/azure_functions_worker/version.py +++ b/workers/azure_functions_worker/version.py @@ -1,4 +1,4 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -VERSION = '4.40.2' +VERSION = '4.41.0' diff --git a/workers/proxy_worker/version.py b/workers/proxy_worker/version.py index af8be706..30e5c590 100644 --- a/workers/proxy_worker/version.py +++ b/workers/proxy_worker/version.py @@ -1,4 +1,4 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -VERSION = '4.40.2' +VERSION = '4.41.0' From b6bf2cbc4218b2e6a72e5ebec8251550156ef61c Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Nov 2025 14:09:33 -0600 Subject: [PATCH 2/4] remove 3.7 & 3.8 from tests --- .../jobs/ci-docker-consumption-tests.yml | 9 - .../jobs/ci-docker-dedicated-tests.yml | 9 - eng/templates/official/jobs/ci-e2e-tests.yml | 18 -- .../test_deferred_bindings_blob_functions.py | 4 - .../test_eventhub_batch_functions.py | 8 - .../emulator_tests/test_eventhub_functions.py | 6 - workers/tests/endtoend/test_eol_log.py | 4 +- workers/tests/endtoend/test_http_v2.py | 2 - .../tests/unittest_proxy/test_dependency.py | 196 +++++++++++++----- workers/tests/unittests/test_datumref.py | 5 +- .../tests/unittests/test_deferred_bindings.py | 11 +- workers/tests/unittests/test_dispatcher.py | 32 --- .../tests/unittests/test_http_functions.py | 14 -- .../tests/unittests/test_http_functions_v2.py | 12 -- workers/tests/unittests/test_http_v2.py | 3 - workers/tests/unittests/test_opentelemetry.py | 4 - .../test_third_party_http_functions.py | 4 - .../unittests/test_utilities_dependency.py | 59 +----- 18 files changed, 150 insertions(+), 250 deletions(-) diff --git a/eng/templates/official/jobs/ci-docker-consumption-tests.yml b/eng/templates/official/jobs/ci-docker-consumption-tests.yml index 0c594142..e5653455 100644 --- a/eng/templates/official/jobs/ci-docker-consumption-tests.yml +++ b/eng/templates/official/jobs/ci-docker-consumption-tests.yml @@ -12,15 +12,6 @@ jobs: strategy: matrix: - Python38: - PYTHON_VERSION: '3.8' - STORAGE_CONNECTION: $(LinuxStorageConnectionString38) - COSMOSDB_CONNECTION: $(LinuxCosmosDBConnectionString38) - EVENTHUB_CONNECTION: $(LinuxEventHubConnectionString38) - SERVICEBUS_CONNECTION: $(LinuxServiceBusConnectionString38) - SQL_CONNECTION: $(LinuxSqlConnectionString38) - EVENTGRID_URI: $(LinuxEventGridTopicUriString38) - EVENTGRID_CONNECTION: $(LinuxEventGridConnectionKeyString38) Python39: PYTHON_VERSION: '3.9' STORAGE_CONNECTION: $(LinuxStorageConnectionString39) diff --git a/eng/templates/official/jobs/ci-docker-dedicated-tests.yml b/eng/templates/official/jobs/ci-docker-dedicated-tests.yml index f2c11534..728caa42 100644 --- a/eng/templates/official/jobs/ci-docker-dedicated-tests.yml +++ b/eng/templates/official/jobs/ci-docker-dedicated-tests.yml @@ -12,15 +12,6 @@ jobs: strategy: matrix: - Python38: - PYTHON_VERSION: '3.8' - STORAGE_CONNECTION: $(LinuxStorageConnectionString38) - COSMOSDB_CONNECTION: $(LinuxCosmosDBConnectionString38) - EVENTHUB_CONNECTION: $(LinuxEventHubConnectionString38) - SERVICEBUS_CONNECTION: $(LinuxServiceBusConnectionString38) - SQL_CONNECTION: $(LinuxSqlConnectionString38) - EVENTGRID_URI: $(LinuxEventGridTopicUriString38) - EVENTGRID_CONNECTION: $(LinuxEventGridConnectionKeyString38) Python39: PYTHON_VERSION: '3.9' STORAGE_CONNECTION: $(LinuxStorageConnectionString39) diff --git a/eng/templates/official/jobs/ci-e2e-tests.yml b/eng/templates/official/jobs/ci-e2e-tests.yml index 681d5aa0..12b57417 100644 --- a/eng/templates/official/jobs/ci-e2e-tests.yml +++ b/eng/templates/official/jobs/ci-e2e-tests.yml @@ -12,24 +12,6 @@ jobs: strategy: matrix: - Python37: - PYTHON_VERSION: '3.7' - STORAGE_CONNECTION: $(LinuxStorageConnectionString37) - COSMOSDB_CONNECTION: $(LinuxCosmosDBConnectionString37) - EVENTHUB_CONNECTION: $(LinuxEventHubConnectionString37) - SERVICEBUS_CONNECTION: $(LinuxServiceBusConnectionString37) - SQL_CONNECTION: $(LinuxSqlConnectionString37) - EVENTGRID_URI: $(LinuxEventGridTopicUriString37) - EVENTGRID_CONNECTION: $(LinuxEventGridConnectionKeyString37) - Python38: - PYTHON_VERSION: '3.8' - STORAGE_CONNECTION: $(LinuxStorageConnectionString38) - COSMOSDB_CONNECTION: $(LinuxCosmosDBConnectionString38) - EVENTHUB_CONNECTION: $(LinuxEventHubConnectionString38) - SERVICEBUS_CONNECTION: $(LinuxServiceBusConnectionString38) - SQL_CONNECTION: $(LinuxSqlConnectionString38) - EVENTGRID_URI: $(LinuxEventGridTopicUriString38) - EVENTGRID_CONNECTION: $(LinuxEventGridConnectionKeyString38) Python39: PYTHON_VERSION: '3.9' STORAGE_CONNECTION: $(LinuxStorageConnectionString39) diff --git a/workers/tests/emulator_tests/test_deferred_bindings_blob_functions.py b/workers/tests/emulator_tests/test_deferred_bindings_blob_functions.py index 2dd124f4..47b3801b 100644 --- a/workers/tests/emulator_tests/test_deferred_bindings_blob_functions.py +++ b/workers/tests/emulator_tests/test_deferred_bindings_blob_functions.py @@ -1,14 +1,10 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -import sys import time -import unittest from tests.utils import testutils -@unittest.skipIf(sys.version_info.minor <= 8, "The base extension" - "is only supported for 3.9+.") class TestDeferredBindingsBlobFunctions(testutils.WebHostTestCase): @classmethod diff --git a/workers/tests/emulator_tests/test_eventhub_batch_functions.py b/workers/tests/emulator_tests/test_eventhub_batch_functions.py index 1a8ae2a9..cb37b446 100644 --- a/workers/tests/emulator_tests/test_eventhub_batch_functions.py +++ b/workers/tests/emulator_tests/test_eventhub_batch_functions.py @@ -1,10 +1,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import json -import sys import time from datetime import datetime -from unittest.case import skipIf from dateutil import parser from tests.utils import testutils @@ -66,9 +64,6 @@ def test_eventhub_multiple(self): self.assertDictEqual(all_row_keys_seen, row_keys_seen) - @skipIf(sys.version_info.minor == 7, - "Using azure-eventhub SDK with the EventHub Emulator" - "requires Python 3.8+") @testutils.retryable_test(3, 5) def test_eventhub_multiple_with_metadata(self): # Generate a unique event body for EventHub event @@ -176,9 +171,6 @@ def test_eventhub_multiple(self): self.assertDictEqual(all_row_keys_seen, row_keys_seen) - @skipIf(sys.version_info.minor == 7, - "Using azure-eventhub SDK with the EventHub Emulator" - "requires Python 3.8+") @testutils.retryable_test(3, 5) def test_eventhub_multiple_with_metadata(self): # Generate a unique event body for EventHub event diff --git a/workers/tests/emulator_tests/test_eventhub_functions.py b/workers/tests/emulator_tests/test_eventhub_functions.py index 03088c73..32a3c937 100644 --- a/workers/tests/emulator_tests/test_eventhub_functions.py +++ b/workers/tests/emulator_tests/test_eventhub_functions.py @@ -1,11 +1,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import json -import sys import time -from unittest import skipIf - from tests.utils import testutils @@ -55,9 +52,6 @@ def test_eventhub_trigger(self): # Check if the event body matches the initial data self.assertEqual(response, doc) - @skipIf(sys.version_info.minor == 7, - "Using azure-eventhub SDK with the EventHub Emulator" - "requires Python 3.8+") @testutils.retryable_test(3, 5) def test_eventhub_trigger_with_metadata(self): # Generate a unique event body for EventHub event diff --git a/workers/tests/endtoend/test_eol_log.py b/workers/tests/endtoend/test_eol_log.py index 084677a6..6215ff80 100644 --- a/workers/tests/endtoend/test_eol_log.py +++ b/workers/tests/endtoend/test_eol_log.py @@ -11,8 +11,8 @@ REQUEST_TIMEOUT_SEC = 5 -@skipIf(sys.version_info.minor >= 9, - '3.9+ is supported.') +@skipIf(sys.version_info.minor >= 10, + '3.10+ is supported.') class TestEOLFunctions(testutils.WebHostTestCase): @classmethod diff --git a/workers/tests/endtoend/test_http_v2.py b/workers/tests/endtoend/test_http_v2.py index da11f3f1..4b2378b9 100644 --- a/workers/tests/endtoend/test_http_v2.py +++ b/workers/tests/endtoend/test_http_v2.py @@ -200,8 +200,6 @@ def send_request(): @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): diff --git a/workers/tests/unittest_proxy/test_dependency.py b/workers/tests/unittest_proxy/test_dependency.py index 27d4227b..888b25ad 100644 --- a/workers/tests/unittest_proxy/test_dependency.py +++ b/workers/tests/unittest_proxy/test_dependency.py @@ -1,59 +1,151 @@ import sys import os +import unittest from unittest.mock import patch from proxy_worker.utils.dependency import DependencyManager +from tests.utils import testutils -@patch("proxy_worker.utils.dependency.DependencyManager._get_cx_deps_path", - return_value="/mock/cx/site-packages") -@patch("proxy_worker.utils.dependency.DependencyManager._get_cx_working_dir", - return_value="/mock/cx") -@patch("proxy_worker.utils.dependency.DependencyManager._get_worker_deps_path", - return_value="/mock/worker") -@patch("proxy_worker.utils.dependency.logger") -def test_use_worker_dependencies(mock_logger, mock_worker, mock_cx_dir, mock_cx_deps): - sys.path = ["/mock/cx/site-packages", "/mock/cx", "/original"] - - DependencyManager.initialize() - DependencyManager.use_worker_dependencies() - - assert sys.path[0] == "/mock/worker" - assert "/mock/cx/site-packages" not in sys.path - assert "/mock/cx" not in sys.path - - mock_logger.info.assert_any_call( - 'Applying use_worker_dependencies:' - ' worker_dependencies: %s,' - ' customer_dependencies: %s,' - ' working_directory: %s', - "/mock/worker", "/mock/cx/site-packages", "/mock/cx" - ) - - -@patch("proxy_worker.utils.dependency.DependencyManager._get_cx_deps_path", - return_value="/mock/cx/site-packages") -@patch("proxy_worker.utils.dependency.DependencyManager._get_worker_deps_path", - return_value="/mock/worker") -@patch("proxy_worker.utils.dependency.DependencyManager._get_cx_working_dir", - return_value="/mock/cx") -@patch("proxy_worker.utils.dependency.DependencyManager.is_in_linux_consumption", - return_value=False) -@patch("proxy_worker.utils.dependency.is_envvar_true", return_value=False) -@patch("proxy_worker.utils.dependency.logger") -def test_prioritize_customer_dependencies(mock_logger, mock_env, mock_linux, - mock_cx_dir, mock_worker, mock_cx_deps): - sys.path = ["/mock/worker", "/some/old/path"] - - DependencyManager.initialize() - DependencyManager.prioritize_customer_dependencies("/override/cx") - - assert sys.path[0] == "/mock/cx/site-packages" - assert sys.path[1] == "/mock/worker" - expected_path = os.path.abspath("/override/cx") - assert expected_path in sys.path - - assert any( - "Finished prioritize_customer_dependencies" in str(call[0][0]) - for call in mock_logger.info.call_args_list - ) +class TestDependency(unittest.TestCase): + + @patch("proxy_worker.utils.dependency.DependencyManager._get_cx_deps_path", + return_value="/mock/cx/site-packages") + @patch("proxy_worker.utils.dependency.DependencyManager._get_cx_working_dir", + return_value="/mock/cx") + @patch("proxy_worker.utils.dependency.DependencyManager._get_worker_deps_path", + return_value="/mock/worker") + @patch("proxy_worker.utils.dependency.logger") + def test_use_worker_dependencies(mock_logger, mock_worker, mock_cx_dir, + mock_cx_deps): + sys.path = ["/mock/cx/site-packages", "/mock/cx", "/original"] + + DependencyManager.initialize() + DependencyManager.use_worker_dependencies() + + assert sys.path[0] == "/mock/worker" + assert "/mock/cx/site-packages" not in sys.path + assert "/mock/cx" not in sys.path + + mock_logger.info.assert_any_call( + 'Applying use_worker_dependencies:' + ' worker_dependencies: %s,' + ' customer_dependencies: %s,' + ' working_directory: %s', + "/mock/worker", "/mock/cx/site-packages", "/mock/cx" + ) + + @patch("proxy_worker.utils.dependency.DependencyManager._get_cx_deps_path", + return_value="/mock/cx/site-packages") + @patch("proxy_worker.utils.dependency.DependencyManager._get_worker_deps_path", + return_value="/mock/worker") + @patch("proxy_worker.utils.dependency.DependencyManager._get_cx_working_dir", + return_value="/mock/cx") + @patch("proxy_worker.utils.dependency.DependencyManager.is_in_linux_consumption", + return_value=False) + @patch("proxy_worker.utils.dependency.is_envvar_true", return_value=False) + @patch("proxy_worker.utils.dependency.logger") + def test_prioritize_customer_dependencies(mock_logger, mock_env, mock_linux, + mock_cx_dir, mock_worker, mock_cx_deps): + sys.path = ["/mock/worker", "/some/old/path"] + + DependencyManager.initialize() + DependencyManager.prioritize_customer_dependencies("/override/cx") + + assert sys.path[0] == "/mock/cx/site-packages" + assert sys.path[1] == "/mock/worker" + expected_path = os.path.abspath("/override/cx") + assert expected_path in sys.path + + assert any( + "Finished prioritize_customer_dependencies" in str(call[0][0]) + for call in mock_logger.info.call_args_list + ) + + +class TestProtobufImports(unittest.TestCase): + def setUp(self): + self._patch_environ = patch.dict('os.environ', os.environ.copy()) + self._patch_sys_path = patch('sys.path', []) + self._patch_importer_cache = patch.dict('sys.path_importer_cache', {}) + self._patch_modules = patch.dict('sys.modules', {}) + self._customer_func_path = os.path.abspath( + os.path.join( + testutils.UNIT_TESTS_ROOT, 'resources', 'customer_func_path' + ) + ) + self._worker_deps_path = os.path.abspath( + os.path.join( + testutils.UNIT_TESTS_ROOT, 'resources', 'worker_deps_path' + ) + ) + self._customer_deps_path = os.path.abspath( + os.path.join( + testutils.UNIT_TESTS_ROOT, 'resources', 'customer_deps_path' + ) + ) + + self._patch_environ.start() + self._patch_sys_path.start() + self._patch_importer_cache.start() + self._patch_modules.start() + + def tearDown(self): + self._patch_environ.stop() + self._patch_sys_path.stop() + self._patch_importer_cache.stop() + self._patch_modules.stop() + DependencyManager.cx_deps_path = '' + DependencyManager.cx_working_dir = '' + DependencyManager.worker_deps_path = '' + + @unittest.skipIf(sys.version_info.minor != 13, + "The worker brings different protobuf versions" + "between 3.13 and 3.14.") + def test_newrelic_protobuf_import_scenario_worker_deps_313(self): + # https://github.com/Azure/azure-functions-python-worker/issues/1339 + # newrelic checks if protobuf has been imported and based on the + # version it finds, imports a specific pb2 file. + + # protobuf is brought through the worker's deps. + # Setup paths + DependencyManager.worker_deps_path = self._worker_deps_path + DependencyManager.cx_deps_path = "" # No customer deps + DependencyManager.cx_working_dir = self._customer_func_path + + DependencyManager.prioritize_customer_dependencies() + + # protobuf v5 is found + from google.protobuf import __version__ + + protobuf_version = tuple(int(v) for v in __version__.split(".")) + self.assertIsNotNone(protobuf_version) + self.assertEqual(protobuf_version[0], 5) + + @unittest.skipIf(sys.version_info.minor != 13, + "The worker brings different protobuf versions" + "between 3.13 and 3.14.") + def test_newrelic_protobuf_import_scenario_user_deps_313(self): + # https://github.com/Azure/azure-functions-python-worker/issues/1339 + # newrelic checks if protobuf has been imported and based on the + # version it finds, imports a specific pb2 file. + + # protobuf is brought through the user's deps. + # Setup paths + DependencyManager.worker_deps_path = self._worker_deps_path + DependencyManager.cx_deps_path = self._customer_deps_path + DependencyManager.cx_working_dir = self._customer_func_path + + DependencyManager.prioritize_customer_dependencies() + + # protobuf is found from worker deps, but newrelic won't find it + from google.protobuf import __version__ + + protobuf_version = tuple(int(v) for v in __version__.split(".")) + self.assertIsNotNone(protobuf_version) + + # newrelic tries to import protobuf v3 + self.assertEqual(protobuf_version[0], 3) + + # newrelic tries to import protobuf 5 + self.assertNotEqual(protobuf_version[0], 5) diff --git a/workers/tests/unittests/test_datumref.py b/workers/tests/unittests/test_datumref.py index 3db94646..78813ef2 100644 --- a/workers/tests/unittests/test_datumref.py +++ b/workers/tests/unittests/test_datumref.py @@ -1,7 +1,6 @@ -import sys + import unittest from http.cookies import SimpleCookie -from unittest import skipIf from dateutil import parser from dateutil.parser import ParserError @@ -67,8 +66,6 @@ def test_parse_cookie_attr_same_site_explicit_none(self): def test_parse_to_rpc_http_cookie_list_none(self): self.assertEqual(parse_to_rpc_http_cookie_list(None), None) - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_parse_to_rpc_http_cookie_list_valid(self): headers = [ 'foo=bar; Path=/some/path; Secure; HttpOnly; Domain=123; ' diff --git a/workers/tests/unittests/test_deferred_bindings.py b/workers/tests/unittests/test_deferred_bindings.py index 7405512f..e43ffc6e 100644 --- a/workers/tests/unittests/test_deferred_bindings.py +++ b/workers/tests/unittests/test_deferred_bindings.py @@ -10,10 +10,7 @@ from azure_functions_worker import protos from azure_functions_worker.bindings import meta -# Even if the tests are skipped for <=3.8, the library is still imported as -# it is used for these tests. -if sys.version_info.minor >= 9: - from azurefunctions.extensions.base import GrpcClientType +from azurefunctions.extensions.base import GrpcClientType DEFERRED_BINDINGS_ENABLED_DIR = testutils.UNIT_TESTS_FOLDER / \ 'deferred_bindings_functions' / \ @@ -35,8 +32,6 @@ def __init__(self, version: str, source: str, self.content = content -@unittest.skipIf(sys.version_info.minor <= 8, "The base extension" - "is only supported for 3.9+.") @unittest.skipIf(sys.version_info.minor >= 13, "For python 3.13+," "this logic is in the" "library worker.") @@ -69,8 +64,6 @@ async def test_deferred_bindings_enabled_log(self): del sys.modules['function_app'] -@unittest.skipIf(sys.version_info.minor <= 8, "The base extension" - "is only supported for 3.9+.") @unittest.skipIf(sys.version_info.minor >= 13, "For python 3.13+," "this logic is in the" "library worker.") @@ -103,8 +96,6 @@ async def test_deferred_bindings_disabled_log(self): del sys.modules['function_app'] -@unittest.skipIf(sys.version_info.minor <= 8, "The base extension" - "is only supported for 3.9+.") @unittest.skipIf(sys.version_info.minor >= 13, "For python 3.13+," "this logic is in the" "library worker.") diff --git a/workers/tests/unittests/test_dispatcher.py b/workers/tests/unittests/test_dispatcher.py index ebaac2ce..8b140a00 100644 --- a/workers/tests/unittests/test_dispatcher.py +++ b/workers/tests/unittests/test_dispatcher.py @@ -515,38 +515,6 @@ async def _check_if_async_function_is_ok(self, host) -> Tuple[str, str]: return func_id, invoke_id, function_name -@unittest.skipIf(sys.version_info.minor != 8, - "Run the tests only for Python 3.8. In other platforms, " - "as the default passed is None, the cpu_count determines the " - "number of max_workers and we cannot mock the os.cpu_count() " - "in the concurrent.futures.ThreadPoolExecutor") -class TestThreadPoolSettingsPython38(TestThreadPoolSettingsPython37): - def setUp(self, version=SysVersionInfo(3, 8, 0, 'final', 0)): - super(TestThreadPoolSettingsPython38, self).setUp(version) - self._allowed_max_workers: int = self._over_max_workers - - def tearDown(self): - super(TestThreadPoolSettingsPython38, self).tearDown() - - async def test_dispatcher_sync_threadpool_in_placeholder_above_max(self): - """Test if the sync threadpool will use any value and there isn't any - artificial max value set. - """ - with patch('azure_functions_worker.dispatcher.logger'): - async with self._ctrl as host: - await self._check_if_function_is_ok(host) - - # Reload environment variable on specialization - await host.reload_environment(environment={ - PYTHON_THREADPOOL_THREAD_COUNT: f'{self._over_max_workers}' - }) - await self._assert_workers_threadpool(self._ctrl, host, - self._allowed_max_workers) - self.assertNotEqual( - self._ctrl._worker.get_sync_tp_workers_set(), - self._default_workers) - - @unittest.skipIf(sys.version_info.minor != 9, "Run the tests only for Python 3.9. In other platforms, " "as the default passed is None, the cpu_count determines the " diff --git a/workers/tests/unittests/test_http_functions.py b/workers/tests/unittests/test_http_functions.py index cfa46be5..b9bac60a 100644 --- a/workers/tests/unittests/test_http_functions.py +++ b/workers/tests/unittests/test_http_functions.py @@ -351,8 +351,6 @@ def test_print_to_console_stdout(self): self.assertEqual(r.status_code, 200) self.assertEqual(r.text, 'OK-print-logging') - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_multiple_cookie_header_in_response(self): r = self.webhost.request('GET', 'multiple_set_cookie_resp_headers') self.assertEqual(r.status_code, 200) @@ -363,15 +361,11 @@ def test_multiple_cookie_header_in_response(self): "foo3=43; expires=Fri, 12 Jan 2018 13:55:08 GMT; " "max-age=10000000; domain=example.com; path=/; secure; httponly") - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_set_cookie_header_in_response_empty_value(self): r = self.webhost.request('GET', 'set_cookie_resp_header_empty') self.assertEqual(r.status_code, 200) self.assertEqual(r.headers.get('Set-Cookie'), None) - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_set_cookie_header_in_response_default_value(self): r = self.webhost.request('GET', 'set_cookie_resp_header_default_values') @@ -379,8 +373,6 @@ def test_set_cookie_header_in_response_default_value(self): self.assertEqual(r.headers.get('Set-Cookie'), 'foo=bar; domain=; path=') - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_response_cookie_header_nullable_timestamp_err(self): r = self.webhost.request( 'GET', @@ -396,8 +388,6 @@ def check_log_response_cookie_header_nullable_timestamp_err(self, "invalid format.", host_out) - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_response_cookie_header_nullable_bool_err(self): r = self.webhost.request( 'GET', @@ -405,8 +395,6 @@ def test_response_cookie_header_nullable_bool_err(self): self.assertEqual(r.status_code, 200) self.assertFalse("Set-Cookie" in r.headers) - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_response_cookie_header_nullable_double_err(self): r = self.webhost.request( 'GET', @@ -418,8 +406,6 @@ def check_log_print_to_console_stdout(self, host_out: typing.List[str]): # System logs stdout should exist in host_out self.assertIn('Secret42', host_out) - @skipIf(sys.version_info < (3, 9, 0), - "Skip the tests for Python 3.8 and below") def test_print_to_console_stderr(self): r = self.webhost.request('GET', 'print_logging?console=true' '&message=Secret42&is_stderr=true') diff --git a/workers/tests/unittests/test_http_functions_v2.py b/workers/tests/unittests/test_http_functions_v2.py index b7e45667..ce0c7f3e 100644 --- a/workers/tests/unittests/test_http_functions_v2.py +++ b/workers/tests/unittests/test_http_functions_v2.py @@ -7,7 +7,6 @@ import sys import typing import unittest -from unittest import skipIf from unittest.mock import patch from tests.utils import testutils @@ -15,7 +14,6 @@ from azure_functions_worker.constants import PYTHON_ENABLE_INIT_INDEXING -@unittest.skipIf(sys.version_info.minor <= 7, "Skipping tests <= Python 3.7") class TestHttpFunctionsV2FastApi(testutils.WebHostTestCase): @classmethod def setUpClass(cls): @@ -356,8 +354,6 @@ def test_print_to_console_stdout(self): self.assertEqual(r.status_code, 200) self.assertEqual(r.text, '"OK-print-logging"') - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_multiple_cookie_header_in_response(self): r = self.webhost.request('GET', 'multiple_set_cookie_resp_headers') self.assertEqual(r.status_code, 200) @@ -368,8 +364,6 @@ def test_multiple_cookie_header_in_response(self): " foo3=43; Domain=example.com; expires=Fri, 12 Jan 2018 13:55:08" " GMT; HttpOnly; Max-Age=10000000; Path=/; SameSite=Lax; Secure") - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_set_cookie_header_in_response_default_value(self): r = self.webhost.request('GET', 'set_cookie_resp_header_default_values') @@ -377,16 +371,12 @@ def test_set_cookie_header_in_response_default_value(self): self.assertEqual(r.headers.get('Set-Cookie'), 'foo3=42; Path=/; SameSite=lax') - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_response_cookie_header_nullable_timestamp_err(self): r = self.webhost.request( 'GET', 'response_cookie_header_nullable_timestamp_err') self.assertEqual(r.status_code, 200) - @skipIf(sys.version_info < (3, 8, 0), - "Skip the tests for Python 3.7 and below") def test_response_cookie_header_nullable_bool_err(self): r = self.webhost.request( 'GET', @@ -394,8 +384,6 @@ def test_response_cookie_header_nullable_bool_err(self): self.assertEqual(r.status_code, 200) self.assertTrue("Set-Cookie" in r.headers) - @skipIf(sys.version_info < (3, 9, 0), - "Skip the tests for Python 3.8 and below") def test_print_to_console_stderr(self): r = self.webhost.request('GET', 'print_logging?console=true' '&message=Secret42&is_stderr=true') diff --git a/workers/tests/unittests/test_http_v2.py b/workers/tests/unittests/test_http_v2.py index b2b1852f..064535f4 100644 --- a/workers/tests/unittests/test_http_v2.py +++ b/workers/tests/unittests/test_http_v2.py @@ -1,6 +1,5 @@ import asyncio import socket -import sys import unittest from unittest.mock import MagicMock, patch @@ -20,7 +19,6 @@ class MockHttpResponse: pass -@unittest.skipIf(sys.version_info <= (3, 7), "Skipping tests if <= Python 3.7") class TestHttpCoordinator(unittest.TestCase): def setUp(self): self.invoc_id = "test_invocation" @@ -129,7 +127,6 @@ def test_await_http_response_async_response_not_set(self): f"No http response found for invocation {invoc_id}") -@unittest.skipIf(sys.version_info <= (3, 7), "Skipping tests if <= Python 3.7") class TestAsyncContextReference(unittest.TestCase): def setUp(self): diff --git a/workers/tests/unittests/test_opentelemetry.py b/workers/tests/unittests/test_opentelemetry.py index add44d85..bdbedc87 100644 --- a/workers/tests/unittests/test_opentelemetry.py +++ b/workers/tests/unittests/test_opentelemetry.py @@ -1,9 +1,7 @@ import asyncio import os -import sys import unittest -from unittest import skipIf from unittest.mock import MagicMock, patch from tests.unittests.test_dispatcher import FUNCTION_APP_DIRECTORY @@ -12,8 +10,6 @@ from azure_functions_worker import protos -@skipIf(sys.version_info.minor == 7, - "Packages are only supported for 3.8+") class TestOpenTelemetry(unittest.TestCase): def setUp(self): diff --git a/workers/tests/unittests/test_third_party_http_functions.py b/workers/tests/unittests/test_third_party_http_functions.py index 7dd57e88..e64c7cb6 100644 --- a/workers/tests/unittests/test_third_party_http_functions.py +++ b/workers/tests/unittests/test_third_party_http_functions.py @@ -6,9 +6,7 @@ import re import typing import base64 -import sys -from unittest import skipIf from unittest.mock import patch from tests.utils import testutils @@ -118,8 +116,6 @@ def check_log_print_to_console_stdout(self, # System logs stdout now exist in host_out self.assertIn('Secret42', host_out) - @skipIf(sys.version_info < (3, 9, 0), - "Skip the tests for Python 3.8 and below") def test_print_to_console_stderr(self): r = self.webhost.request('GET', 'print_logging?console=true' '&message=Secret42&is_stderr=true', diff --git a/workers/tests/unittests/test_utilities_dependency.py b/workers/tests/unittests/test_utilities_dependency.py index 432aee75..acd0954b 100644 --- a/workers/tests/unittests/test_utilities_dependency.py +++ b/workers/tests/unittests/test_utilities_dependency.py @@ -643,34 +643,9 @@ def test_remove_module_cache_with_namespace_remain(self): DependencyManager._remove_module_cache(self._customer_deps_path) self.assertIsNotNone(common_module) - @unittest.skipIf(sys.version_info.minor > 7, + @unittest.skipIf(sys.version_info.minor >= 13, "The worker brings different protobuf versions" - "between 3.7 and 3.8+.") - def test_newrelic_protobuf_import_scenario_worker_deps_37(self): - # https://github.com/Azure/azure-functions-python-worker/issues/1339 - # newrelic checks if protobuf has been imported and based on the - # version it finds, imports a specific pb2 file. - - # PIWD = 0. protobuf is brought through the worker's deps. - os.environ['PYTHON_ISOLATE_WORKER_DEPENDENCIES'] = 'false' - - # Setup paths - DependencyManager.worker_deps_path = self._worker_deps_path - DependencyManager.cx_deps_path = self._customer_deps_path - DependencyManager.cx_working_dir = self._customer_func_path - - DependencyManager.prioritize_customer_dependencies() - - # protobuf v3 is found - from google.protobuf import __version__ - - protobuf_version = tuple(int(v) for v in __version__.split(".")) - self.assertIsNotNone(protobuf_version) - self.assertEqual(protobuf_version[0], 3) - - @unittest.skipIf(sys.version_info.minor <= 7, - "The worker brings different protobuf versions" - "between 3.7 and 3.8+.") + "between 3.12 and 3.13+.") def test_newrelic_protobuf_import_scenario_worker_deps(self): # https://github.com/Azure/azure-functions-python-worker/issues/1339 # newrelic checks if protobuf has been imported and based on the @@ -693,36 +668,6 @@ def test_newrelic_protobuf_import_scenario_worker_deps(self): self.assertIsNotNone(protobuf_version) self.assertEqual(protobuf_version[0], 4) - @unittest.skipIf(sys.version_info.minor > 7, - "The worker brings different protobuf versions" - "between 3.7 and 3.8+.") - def test_newrelic_protobuf_import_scenario_user_deps_37(self): - # https://github.com/Azure/azure-functions-python-worker/issues/1339 - # newrelic checks if protobuf has been imported and based on the - # version it finds, imports a specific pb2 file. - - # PIWD = 1. protobuf is brought through the user's deps. - os.environ['PYTHON_ISOLATE_WORKER_DEPENDENCIES'] = 'true' - - # Setup paths - DependencyManager.worker_deps_path = self._worker_deps_path - DependencyManager.cx_deps_path = self._customer_deps_path - DependencyManager.cx_working_dir = self._customer_func_path - - DependencyManager.prioritize_customer_dependencies() - - # protobuf is found from worker deps, but newrelic won't find it - from google.protobuf import __version__ - - protobuf_version = tuple(int(v) for v in __version__.split(".")) - self.assertIsNotNone(protobuf_version) - - # newrelic tries to import protobuf v3 - self.assertEqual(protobuf_version[0], 3) - - # newrelic tries to import protobuf v4 - self.assertNotEqual(protobuf_version[0], 4) - @unittest.skipIf(sys.version_info.minor <= 7, "The worker brings different protobuf versions" "between 3.7 and 3.8+.") From cc8ee2b78e2189810d9049eab81b9b32ab420eac Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Nov 2025 14:10:41 -0600 Subject: [PATCH 3/4] remove for public tests --- eng/templates/jobs/ci-emulator-tests.yml | 4 ---- eng/templates/jobs/ci-unit-tests.yml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/eng/templates/jobs/ci-emulator-tests.yml b/eng/templates/jobs/ci-emulator-tests.yml index ebe10a34..0df9b005 100644 --- a/eng/templates/jobs/ci-emulator-tests.yml +++ b/eng/templates/jobs/ci-emulator-tests.yml @@ -12,10 +12,6 @@ jobs: strategy: matrix: - Python37: - PYTHON_VERSION: '3.7' - Python38: - PYTHON_VERSION: '3.8' Python39: PYTHON_VERSION: '3.9' Python310: diff --git a/eng/templates/jobs/ci-unit-tests.yml b/eng/templates/jobs/ci-unit-tests.yml index cb44ef65..6e70b833 100644 --- a/eng/templates/jobs/ci-unit-tests.yml +++ b/eng/templates/jobs/ci-unit-tests.yml @@ -12,10 +12,6 @@ jobs: strategy: matrix: - Python37: - PYTHON_VERSION: '3.7' - Python38: - PYTHON_VERSION: '3.8' Python39: PYTHON_VERSION: '3.9' Python310: From ba031f3703b7ef1ef096f0d4f73d53c6b622a002 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Thu, 20 Nov 2025 14:50:25 -0600 Subject: [PATCH 4/4] remove test --- .../tests/unittest_proxy/test_dependency.py | 197 +++++------------- 1 file changed, 53 insertions(+), 144 deletions(-) diff --git a/workers/tests/unittest_proxy/test_dependency.py b/workers/tests/unittest_proxy/test_dependency.py index 888b25ad..6cb4edca 100644 --- a/workers/tests/unittest_proxy/test_dependency.py +++ b/workers/tests/unittest_proxy/test_dependency.py @@ -1,151 +1,60 @@ import sys import os -import unittest from unittest.mock import patch from proxy_worker.utils.dependency import DependencyManager -from tests.utils import testutils -class TestDependency(unittest.TestCase): - - @patch("proxy_worker.utils.dependency.DependencyManager._get_cx_deps_path", - return_value="/mock/cx/site-packages") - @patch("proxy_worker.utils.dependency.DependencyManager._get_cx_working_dir", - return_value="/mock/cx") - @patch("proxy_worker.utils.dependency.DependencyManager._get_worker_deps_path", - return_value="/mock/worker") - @patch("proxy_worker.utils.dependency.logger") - def test_use_worker_dependencies(mock_logger, mock_worker, mock_cx_dir, - mock_cx_deps): - sys.path = ["/mock/cx/site-packages", "/mock/cx", "/original"] - - DependencyManager.initialize() - DependencyManager.use_worker_dependencies() - - assert sys.path[0] == "/mock/worker" - assert "/mock/cx/site-packages" not in sys.path - assert "/mock/cx" not in sys.path - - mock_logger.info.assert_any_call( - 'Applying use_worker_dependencies:' - ' worker_dependencies: %s,' - ' customer_dependencies: %s,' - ' working_directory: %s', - "/mock/worker", "/mock/cx/site-packages", "/mock/cx" - ) - - @patch("proxy_worker.utils.dependency.DependencyManager._get_cx_deps_path", - return_value="/mock/cx/site-packages") - @patch("proxy_worker.utils.dependency.DependencyManager._get_worker_deps_path", - return_value="/mock/worker") - @patch("proxy_worker.utils.dependency.DependencyManager._get_cx_working_dir", - return_value="/mock/cx") - @patch("proxy_worker.utils.dependency.DependencyManager.is_in_linux_consumption", - return_value=False) - @patch("proxy_worker.utils.dependency.is_envvar_true", return_value=False) - @patch("proxy_worker.utils.dependency.logger") - def test_prioritize_customer_dependencies(mock_logger, mock_env, mock_linux, - mock_cx_dir, mock_worker, mock_cx_deps): - sys.path = ["/mock/worker", "/some/old/path"] - - DependencyManager.initialize() - DependencyManager.prioritize_customer_dependencies("/override/cx") - - assert sys.path[0] == "/mock/cx/site-packages" - assert sys.path[1] == "/mock/worker" - expected_path = os.path.abspath("/override/cx") - assert expected_path in sys.path - - assert any( - "Finished prioritize_customer_dependencies" in str(call[0][0]) - for call in mock_logger.info.call_args_list - ) - - -class TestProtobufImports(unittest.TestCase): - def setUp(self): - self._patch_environ = patch.dict('os.environ', os.environ.copy()) - self._patch_sys_path = patch('sys.path', []) - self._patch_importer_cache = patch.dict('sys.path_importer_cache', {}) - self._patch_modules = patch.dict('sys.modules', {}) - self._customer_func_path = os.path.abspath( - os.path.join( - testutils.UNIT_TESTS_ROOT, 'resources', 'customer_func_path' - ) - ) - self._worker_deps_path = os.path.abspath( - os.path.join( - testutils.UNIT_TESTS_ROOT, 'resources', 'worker_deps_path' - ) - ) - self._customer_deps_path = os.path.abspath( - os.path.join( - testutils.UNIT_TESTS_ROOT, 'resources', 'customer_deps_path' - ) - ) - - self._patch_environ.start() - self._patch_sys_path.start() - self._patch_importer_cache.start() - self._patch_modules.start() - - def tearDown(self): - self._patch_environ.stop() - self._patch_sys_path.stop() - self._patch_importer_cache.stop() - self._patch_modules.stop() - DependencyManager.cx_deps_path = '' - DependencyManager.cx_working_dir = '' - DependencyManager.worker_deps_path = '' - - @unittest.skipIf(sys.version_info.minor != 13, - "The worker brings different protobuf versions" - "between 3.13 and 3.14.") - def test_newrelic_protobuf_import_scenario_worker_deps_313(self): - # https://github.com/Azure/azure-functions-python-worker/issues/1339 - # newrelic checks if protobuf has been imported and based on the - # version it finds, imports a specific pb2 file. - - # protobuf is brought through the worker's deps. - # Setup paths - DependencyManager.worker_deps_path = self._worker_deps_path - DependencyManager.cx_deps_path = "" # No customer deps - DependencyManager.cx_working_dir = self._customer_func_path - - DependencyManager.prioritize_customer_dependencies() - - # protobuf v5 is found - from google.protobuf import __version__ - - protobuf_version = tuple(int(v) for v in __version__.split(".")) - self.assertIsNotNone(protobuf_version) - self.assertEqual(protobuf_version[0], 5) - - @unittest.skipIf(sys.version_info.minor != 13, - "The worker brings different protobuf versions" - "between 3.13 and 3.14.") - def test_newrelic_protobuf_import_scenario_user_deps_313(self): - # https://github.com/Azure/azure-functions-python-worker/issues/1339 - # newrelic checks if protobuf has been imported and based on the - # version it finds, imports a specific pb2 file. - - # protobuf is brought through the user's deps. - # Setup paths - DependencyManager.worker_deps_path = self._worker_deps_path - DependencyManager.cx_deps_path = self._customer_deps_path - DependencyManager.cx_working_dir = self._customer_func_path - - DependencyManager.prioritize_customer_dependencies() - - # protobuf is found from worker deps, but newrelic won't find it - from google.protobuf import __version__ - - protobuf_version = tuple(int(v) for v in __version__.split(".")) - self.assertIsNotNone(protobuf_version) - - # newrelic tries to import protobuf v3 - self.assertEqual(protobuf_version[0], 3) - - # newrelic tries to import protobuf 5 - self.assertNotEqual(protobuf_version[0], 5) +@patch("proxy_worker.utils.dependency.DependencyManager._get_cx_deps_path", + return_value="/mock/cx/site-packages") +@patch("proxy_worker.utils.dependency.DependencyManager._get_cx_working_dir", + return_value="/mock/cx") +@patch("proxy_worker.utils.dependency.DependencyManager._get_worker_deps_path", + return_value="/mock/worker") +@patch("proxy_worker.utils.dependency.logger") +def test_use_worker_dependencies(mock_logger, mock_worker, mock_cx_dir, + mock_cx_deps): + sys.path = ["/mock/cx/site-packages", "/mock/cx", "/original"] + + DependencyManager.initialize() + DependencyManager.use_worker_dependencies() + + assert sys.path[0] == "/mock/worker" + assert "/mock/cx/site-packages" not in sys.path + assert "/mock/cx" not in sys.path + + mock_logger.info.assert_any_call( + 'Applying use_worker_dependencies:' + ' worker_dependencies: %s,' + ' customer_dependencies: %s,' + ' working_directory: %s', + "/mock/worker", "/mock/cx/site-packages", "/mock/cx" + ) + + +@patch("proxy_worker.utils.dependency.DependencyManager._get_cx_deps_path", + return_value="/mock/cx/site-packages") +@patch("proxy_worker.utils.dependency.DependencyManager._get_worker_deps_path", + return_value="/mock/worker") +@patch("proxy_worker.utils.dependency.DependencyManager._get_cx_working_dir", + return_value="/mock/cx") +@patch("proxy_worker.utils.dependency.DependencyManager.is_in_linux_consumption", + return_value=False) +@patch("proxy_worker.utils.dependency.is_envvar_true", return_value=False) +@patch("proxy_worker.utils.dependency.logger") +def test_prioritize_customer_dependencies(mock_logger, mock_env, mock_linux, + mock_cx_dir, mock_worker, mock_cx_deps): + sys.path = ["/mock/worker", "/some/old/path"] + + DependencyManager.initialize() + DependencyManager.prioritize_customer_dependencies("/override/cx") + + assert sys.path[0] == "/mock/cx/site-packages" + assert sys.path[1] == "/mock/worker" + expected_path = os.path.abspath("/override/cx") + assert expected_path in sys.path + + assert any( + "Finished prioritize_customer_dependencies" in str(call[0][0]) + for call in mock_logger.info.call_args_list + )