Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 2 additions & 1 deletion azure_functions_worker/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
PYTHON_THREADPOOL_THREAD_COUNT_MAX = sys.maxsize
PYTHON_THREADPOOL_THREAD_COUNT_MAX_37 = 32

PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT = True
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT = False
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_310 = True
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT = False
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT_39 = True

Expand Down
32 changes: 24 additions & 8 deletions azure_functions_worker/utils/dependency.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from azure_functions_worker.utils.common import is_true_like
from typing import List, Optional
from types import ModuleType
import importlib
import inspect
import os
import re
import sys
from types import ModuleType
from typing import List, Optional

from azure_functions_worker.utils.common import is_true_like
from ..logging import logger
from ..constants import (
AZURE_WEBJOBS_SCRIPT_ROOT,
CONTAINER_NAME,
PYTHON_ISOLATE_WORKER_DEPENDENCIES,
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT,
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_310
)
from ..logging import logger
from ..utils.common import is_python_version
from ..utils.wrappers import enable_feature_by


Expand Down Expand Up @@ -75,7 +77,12 @@ def is_in_linux_consumption(cls):
@classmethod
@enable_feature_by(
flag=PYTHON_ISOLATE_WORKER_DEPENDENCIES,
flag_default=PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT)
flag_default=(
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_310 if
is_python_version('3.10') else
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT
)
)
def use_worker_dependencies(cls):
"""Switch the sys.path and ensure the worker imports are loaded from
Worker's dependenciess.
Expand All @@ -101,7 +108,12 @@ def use_worker_dependencies(cls):
@classmethod
@enable_feature_by(
flag=PYTHON_ISOLATE_WORKER_DEPENDENCIES,
flag_default=PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT)
flag_default=(
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_310 if
is_python_version('3.10') else
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT
)
)
def prioritize_customer_dependencies(cls, cx_working_dir=None):
"""Switch the sys.path and ensure the customer's code import are loaded
from CX's deppendencies.
Expand Down Expand Up @@ -170,7 +182,11 @@ def reload_customer_libraries(cls, cx_working_dir: str):
"""
use_new_env = os.getenv(PYTHON_ISOLATE_WORKER_DEPENDENCIES)
if use_new_env is None:
use_new = PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT
use_new = (
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_310 if
is_python_version('3.10') else
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT
)
else:
use_new = is_true_like(use_new_env)

Expand Down
9 changes: 8 additions & 1 deletion tests/unittests/test_rpc_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import unittest

from azure_functions_worker import protos, testutils
from azure_functions_worker.utils.common import is_python_version


class TestGRPC(testutils.AsyncTestCase):
Expand Down Expand Up @@ -126,7 +127,13 @@ def test_failed_azure_namespace_import(self):

@unittest.skipIf(sys.platform == 'win32',
'Linux .sh script only works on Linux')
@unittest.skipIf(
is_python_version('3.10'),
'In Python 3.10, isolate worker dependencies is turned on by default.'
' Reloading all customer dependencies on specialization is a must.'
' This partially reloading namespace feature is no longer needed.'
)
def test_successful_azure_namespace_import(self):
self._verify_azure_namespace_import(
'true',
'module_b fails to import')
'module_b is imported')
95 changes: 67 additions & 28 deletions tests/unittests/test_utilities_dependency.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import importlib.util
import os
import sys
import importlib.util
import unittest
from unittest.mock import patch

from azure_functions_worker import testutils
from azure_functions_worker.utils.common import is_python_version
from azure_functions_worker.utils.dependency import DependencyManager


Expand Down Expand Up @@ -228,7 +229,7 @@ def test_add_to_sys_path_no_duplication(self):

def test_add_to_sys_path_import_module(self):
DependencyManager._add_to_sys_path(self._customer_deps_path, True)
import common_module # NoQA
import common_module # NoQA
self.assertEqual(
common_module.package_location,
os.path.join(self._customer_deps_path, 'common_module')
Expand All @@ -239,7 +240,7 @@ def test_add_to_sys_path_import_namespace_path(self):
into sys.path
"""
DependencyManager._add_to_sys_path(self._customer_deps_path, True)
import common_namespace # NoQA
import common_namespace # NoQA
self.assertEqual(len(common_namespace.__path__), 1)
self.assertEqual(
common_namespace.__path__[0],
Expand Down Expand Up @@ -516,7 +517,7 @@ def test_clear_path_importer_cache_and_modules_retain_namespace(self):
sys.path.insert(0, self._worker_deps_path)

# Ensure new import is from _worker_deps_path
import common_module as worker_mod # NoQA
import common_module as worker_mod # NoQA
self.assertIn('common_module', sys.modules)
self.assertEqual(
worker_mod.package_location,
Expand Down Expand Up @@ -554,6 +555,41 @@ def test_use_worker_dependencies_disable(self):
with self.assertRaises(ImportError):
import common_module # NoQA

@unittest.skipUnless(
sys.version_info.major == 3 and sys.version_info.minor != 10,
'Test only available for Python 3.6, 3.7, 3.8 or 3.9'
)
def test_use_worker_dependencies_default_python_36_37_38_39(self):
# Feature should be disabled in Python 3.6, 3.7, 3.8 and 3.9
# 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

# The common_module cannot be imported since feature is disabled
DependencyManager.use_worker_dependencies()
with self.assertRaises(ImportError):
import common_module # NoQA

@unittest.skipUnless(
sys.version_info.major == 3 and sys.version_info.minor == 10,
'Test only available for Python 3.10'
)
def test_use_worker_dependencies_default_python_310(self):
# Feature should be enabled in Python 3.10 by default
# 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

# Ensure the common_module is imported from _worker_deps_path
DependencyManager.use_worker_dependencies()
import common_module # NoQA
self.assertEqual(
common_module.package_location,
os.path.join(self._worker_deps_path, 'common_module')
)

def test_prioritize_customer_dependencies(self):
# Setup app settings
os.environ['PYTHON_ISOLATE_WORKER_DEPENDENCIES'] = 'true'
Expand Down Expand Up @@ -592,51 +628,54 @@ def test_prioritize_customer_dependencies_disable(self):
with self.assertRaises(ImportError):
import common_module # NoQA

def test_prioritize_customer_dependencies_from_working_directory(self):
self._initialize_scenario()

@unittest.skipIf(is_python_version('3.10'),
'Test not available for python 3.10')
def test_prioritize_customer_dependencies_default_python_36_37_38_39(self):
# Feature should be disabled in Python 3.6, 3.7, 3.8 and 3.9
# 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

# Ensure the func_specific_module is imported from _customer_func_path
# Ensure the common_module is imported from _customer_deps_path
DependencyManager.prioritize_customer_dependencies()
import func_specific_module # NoQA
self.assertEqual(
func_specific_module.package_location,
os.path.join(self._customer_func_path, 'func_specific_module')
)
with self.assertRaises(ImportError):
import common_module # NoQA

def test_reload_customer_libraries_dependency_isolation_true(self):
os.environ['PYTHON_ISOLATE_WORKER_DEPENDENCIES'] = 'true'
@unittest.skipUnless(
sys.version_info.major == 3 and sys.version_info.minor == 10,
'Test only available for Python 3.10'
)
def test_prioritize_customer_dependencies_default_python_310(self):
# Feature should be enabled in Python 3.10 by default
# 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.reload_customer_libraries(self._customer_deps_path)
# Ensure the common_module is imported from _customer_deps_path
DependencyManager.prioritize_customer_dependencies()
import common_module # NoQA
self.assertEqual(
common_module.package_location,
os.path.join(self._customer_deps_path, 'common_module'))
os.path.join(self._customer_deps_path, 'common_module')
)

def test_prioritize_customer_dependencies_from_working_directory(self):
self._initialize_scenario()

def test_reload_customer_libraries_dependency_isolation_false(self):
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._add_to_sys_path(self._worker_deps_path, True)
import azure.functions # NoQA

DependencyManager._add_to_sys_path(self._customer_deps_path, True)
DependencyManager.reload_customer_libraries(self._customer_deps_path)
# Checking if azure.functions gets reloaded
self.assertIn(
os.path.join(self._customer_deps_path, 'azure', 'functions'),
sys.modules['azure.functions'].__path__)
# Ensure the func_specific_module is imported from _customer_func_path
DependencyManager.prioritize_customer_dependencies()
import func_specific_module # NoQA
self.assertEqual(
func_specific_module.package_location,
os.path.join(self._customer_func_path, 'func_specific_module')
)

def test_remove_module_cache(self):
# First import the common_module and create a sys.modules cache
Expand Down