Skip to content

Commit

Permalink
Merge branch 'dev' into pthummar/add_py311_tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gavin-aguiar authored Oct 3, 2023
2 parents 827f09b + 201013e commit bb24993
Show file tree
Hide file tree
Showing 16 changed files with 275 additions and 124 deletions.
10 changes: 8 additions & 2 deletions azure_functions_worker/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import os
import pathlib
import sys

# Capabilities
Expand Down Expand Up @@ -40,14 +41,19 @@
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_310 = False
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT = False
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT_39 = True
PYTHON_EXTENSIONS_RELOAD_FUNCTIONS = "PYTHON_EXTENSIONS_RELOAD_FUNCTIONS"

# External Site URLs
MODULE_NOT_FOUND_TS_URL = "https://aka.ms/functions-modulenotfound"

# new programming model script file name
SCRIPT_FILE_NAME = "function_app.py"

PYTHON_LANGUAGE_RUNTIME = "python"

# Settings for V2 programming model
RETRY_POLICY = "retry_policy"

# Paths
CUSTOMER_PACKAGES_PATH = os.path.join(pathlib.Path.home(),
pathlib.Path(
"site/wwwroot/.python_packages"))
55 changes: 36 additions & 19 deletions azure_functions_worker/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
PYTHON_THREADPOOL_THREAD_COUNT_MAX_37,
PYTHON_THREADPOOL_THREAD_COUNT_MIN,
PYTHON_ENABLE_DEBUG_LOGGING, SCRIPT_FILE_NAME,
PYTHON_LANGUAGE_RUNTIME)
PYTHON_LANGUAGE_RUNTIME, CUSTOMER_PACKAGES_PATH)
from .extension import ExtensionManager
from .logging import disable_console_logging, enable_console_logging
from .logging import enable_debug_logging_recommendation
from .logging import (logger, error_logger, is_system_log_category,
CONSOLE_LOG_PREFIX, format_exception)
from .utils.common import get_app_setting, is_envvar_true
Expand Down Expand Up @@ -263,9 +262,14 @@ async def _dispatch_grpc_request(self, request):

async def _handle__worker_init_request(self, request):
logger.info('Received WorkerInitRequest, '
'python version %s, worker version %s, request ID %s',
sys.version, VERSION, self.request_id)
enable_debug_logging_recommendation()
'python version %s, '
'worker version %s, '
'request ID %s.'
' To enable debug level logging, please refer to '
'https://aka.ms/python-enable-debug-logging',
sys.version,
VERSION,
self.request_id)

worker_init_request = request.worker_init_request
host_capabilities = worker_init_request.capabilities
Expand Down Expand Up @@ -293,6 +297,10 @@ async def _handle__worker_init_request(self, request):
"Importing azure functions in WorkerInitRequest")
import azure.functions # NoQA

if CUSTOMER_PACKAGES_PATH not in sys.path:
logger.warning("Customer packages not in sys path. "
"This should never happen! ")

# loading bindings registry and saving results to a static
# dictionary which will be later used in the invocation request
bindings.load_binding_registry()
Expand Down Expand Up @@ -363,6 +371,7 @@ async def _handle__function_load_request(self, request):
'Received WorkerLoadRequest, request ID %s, function_id: %s,'
'function_name: %s,', self.request_id, function_id, function_name)

programming_model = "V1"
try:
if not self._functions.get_function(function_id):
if function_metadata.properties.get("worker_indexed", False) \
Expand All @@ -371,8 +380,7 @@ async def _handle__function_load_request(self, request):
# indexing is enabled and load request is called without
# calling the metadata request. In this case we index the
# function and update the workers registry
logger.info(f"Indexing function {function_name} in the "
f"load request")
programming_model = "V2"
_ = self.index_functions(function_path)
else:
# legacy function
Expand All @@ -385,17 +393,24 @@ async def _handle__function_load_request(self, request):
self._functions.add_function(
function_id, func, func_request.metadata)

ExtensionManager.function_load_extension(
try:
ExtensionManager.function_load_extension(
function_name,
func_request.metadata.directory
)
except Exception as ex:
logging.error("Failed to load extensions: ", ex)
raise

logger.info('Successfully processed FunctionLoadRequest, '
'request ID: %s, '
'function ID: %s,'
'function Name: %s,'
'programming model: %s',
self.request_id,
function_id,
function_name,
func_request.metadata.directory
)

logger.info('Successfully processed FunctionLoadRequest, '
'request ID: %s, '
'function ID: %s,'
'function Name: %s', self.request_id,
function_id,
function_name)
programming_model)

return protos.StreamingMessage(
request_id=self.request_id,
Expand Down Expand Up @@ -532,8 +547,10 @@ async def _handle__function_environment_reload_request(self, request):
"""
try:
logger.info('Received FunctionEnvironmentReloadRequest, '
'request ID: %s', self.request_id)
enable_debug_logging_recommendation()
'request ID: %s,'
' To enable debug level logging, please refer to '
'https://aka.ms/python-enable-debug-logging',
self.request_id)

func_env_reload_request = \
request.function_environment_reload_request
Expand Down
5 changes: 2 additions & 3 deletions azure_functions_worker/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,8 @@ def _try_get_sdk_with_extension_enabled(cls) -> Optional[ModuleType]:
@classmethod
def _info_extension_is_enabled(cls, sdk):
logger.info(
'Python Worker Extension is enabled in azure.functions (%s).',
get_sdk_version(sdk)
)
'Python Worker Extension is enabled in azure.functions (%s). '
'Sdk path: %s', get_sdk_version(sdk), sdk.__file__)

@classmethod
def _info_discover_extension_list(cls, function_name, sdk):
Expand Down
14 changes: 5 additions & 9 deletions azure_functions_worker/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,12 @@
from . import protos, functions
from .bindings.retrycontext import RetryPolicy
from .constants import MODULE_NOT_FOUND_TS_URL, SCRIPT_FILE_NAME, \
PYTHON_LANGUAGE_RUNTIME, RETRY_POLICY
PYTHON_LANGUAGE_RUNTIME, RETRY_POLICY, CUSTOMER_PACKAGES_PATH
from .utils.wrappers import attach_message_to_exception

_AZURE_NAMESPACE = '__app__'
_DEFAULT_SCRIPT_FILENAME = '__init__.py'
_DEFAULT_ENTRY_POINT = 'main'

PKGS_PATH = pathlib.Path("site/wwwroot/.python_packages")
home = pathlib.Path.home()
pkgs_path = os.path.join(home, PKGS_PATH)


_submodule_dirs = []


Expand Down Expand Up @@ -140,7 +134,8 @@ def process_indexed_function(functions_registry: functions.Registry,
f' guide: {MODULE_NOT_FOUND_TS_URL} ',
debug_logs='Error in load_function. '
f'Sys Path: {sys.path}, Sys Module: {sys.modules},'
f'python-packages Path exists: {os.path.exists(pkgs_path)}')
'python-packages Path exists: '
f'{os.path.exists(CUSTOMER_PACKAGES_PATH)}')
def load_function(name: str, directory: str, script_file: str,
entry_point: Optional[str]):
dir_path = pathlib.Path(directory)
Expand Down Expand Up @@ -191,7 +186,8 @@ def load_function(name: str, directory: str, script_file: str,
message=f'Troubleshooting Guide: {MODULE_NOT_FOUND_TS_URL}',
debug_logs='Error in index_function_app. '
f'Sys Path: {sys.path}, Sys Module: {sys.modules},'
f'python-packages Path exists: {os.path.exists(pkgs_path)}')
'python-packages Path exists: '
f'{os.path.exists(CUSTOMER_PACKAGES_PATH)}')
def index_function_app(function_path: str):
module_name = pathlib.Path(function_path).stem
imported_module = importlib.import_module(module_name)
Expand Down
5 changes: 0 additions & 5 deletions azure_functions_worker/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,6 @@ def enable_console_logging() -> None:
logger.addHandler(handler)


def enable_debug_logging_recommendation():
logging.info("To enable debug level logging, please refer to "
"https://aka.ms/python-enable-debug-logging")


def is_system_log_category(ctg: str) -> bool:
"""Check if the logging namespace belongs to system logs. Category starts
with the following name will be treated as system logs.
Expand Down
13 changes: 12 additions & 1 deletion azure_functions_worker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# Licensed under the MIT License.
"""Main entrypoint."""


import argparse


Expand All @@ -26,6 +25,18 @@ def parse_args():
'syslog, or a file path')
parser.add_argument('--grpcMaxMessageLength', type=int,
dest='grpc_max_msg_len')
parser.add_argument('--functions-uri', dest='functions_uri', type=str,
help='URI with IP Address and Port used to'
' connect to the Host via gRPC.')
parser.add_argument('--functions-request-id', dest='functions_request_id',
type=str, help='Request ID used for gRPC communication '
'with the Host.')
parser.add_argument('--functions-worker-id',
dest='functions_worker_id', type=str,
help='Worker ID assigned to this language worker.')
parser.add_argument('--functions-grpc-max-message-length', type=int,
dest='functions_grpc_max_msg_len',
help='Max grpc message length for Functions')
return parser.parse_args()


Expand Down
40 changes: 25 additions & 15 deletions azure_functions_worker/utils/common.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from typing import Optional, Callable
from types import ModuleType
import importlib
import os
import sys
import importlib
from types import ModuleType
from typing import Optional, Callable

from azure_functions_worker.constants import CUSTOMER_PACKAGES_PATH, \
PYTHON_EXTENSIONS_RELOAD_FUNCTIONS


def is_true_like(setting: str) -> bool:
Expand Down Expand Up @@ -110,19 +113,26 @@ def get_sdk_from_sys_path() -> ModuleType:
ModuleType
The azure.functions that is loaded from the first sys.path entry
"""
backup_azure_functions = None
backup_azure = None

if 'azure.functions' in sys.modules:
backup_azure_functions = sys.modules.pop('azure.functions')
if 'azure' in sys.modules:
backup_azure = sys.modules.pop('azure')
if is_envvar_true(PYTHON_EXTENSIONS_RELOAD_FUNCTIONS):
backup_azure_functions = None
backup_azure = None

if 'azure.functions' in sys.modules:
backup_azure_functions = sys.modules.pop('azure.functions')
if 'azure' in sys.modules:
backup_azure = sys.modules.pop('azure')

module = importlib.import_module('azure.functions')

if backup_azure:
sys.modules['azure'] = backup_azure
if backup_azure_functions:
sys.modules['azure.functions'] = backup_azure_functions

module = importlib.import_module('azure.functions')
return module

if backup_azure:
sys.modules['azure'] = backup_azure
if backup_azure_functions:
sys.modules['azure.functions'] = backup_azure_functions
if CUSTOMER_PACKAGES_PATH not in sys.path:
sys.path.insert(0, CUSTOMER_PACKAGES_PATH)

return module
return importlib.import_module('azure.functions')
10 changes: 5 additions & 5 deletions azure_functions_worker/utils/dependency.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# 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 ..logging import logger
from azure_functions_worker.utils.common import is_true_like
from ..constants import (
AZURE_WEBJOBS_SCRIPT_ROOT,
CONTAINER_NAME,
PYTHON_ISOLATE_WORKER_DEPENDENCIES,
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 @@ -226,7 +226,7 @@ def reload_azure_google_namespace_from_worker_deps(cls):
logger.info('Reloaded azure.functions module now at %s',
inspect.getfile(sys.modules['azure.functions']))
except Exception as ex:
logger.info(
logger.warning(
'Unable to reload azure.functions. Using default. '
'Exception:\n%s', ex)

Expand Down
2 changes: 1 addition & 1 deletion azure_functions_worker/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

VERSION = '4.16.0'
VERSION = '4.18.0'
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
]

INSTALL_REQUIRES = [
"azure-functions==1.16.0",
"azure-functions==1.18.0b3",
"python-dateutil~=2.8.2"
]

Expand Down
25 changes: 24 additions & 1 deletion tests/consumption_tests/test_linux_consumption.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,37 @@ def test_pinning_functions_to_older_version(self):
"AzureWebJobsStorage": self._storage,
"SCM_RUN_FROM_PACKAGE": self._get_blob_url(
"PinningFunctions"),
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1"
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1",
})
req = Request('GET', f'{ctrl.url}/api/HttpTrigger1')
resp = ctrl.send_request(req)

self.assertEqual(resp.status_code, 200)
self.assertIn("Func Version: 1.11.1", resp.text)

@skipIf(sys.version_info.minor != 10,
"This is testing only for python310")
def test_opencensus_with_extensions_enabled(self):
"""A function app with extensions enabled containing the
following libraries:
azure-functions, azure-eventhub, azure-storage-blob, numpy,
cryptography, pyodbc, requests
should return 200 after importing all libraries.
"""
with LinuxConsumptionWebHostController(_DEFAULT_HOST_VERSION,
self._py_version) as ctrl:
ctrl.assign_container(env={
"AzureWebJobsStorage": self._storage,
"SCM_RUN_FROM_PACKAGE": self._get_blob_url("Opencensus"),
"PYTHON_ENABLE_WORKER_EXTENSIONS": "1",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing"
})
req = Request('GET', f'{ctrl.url}/api/opencensus')
resp = ctrl.send_request(req)
self.assertEqual(resp.status_code, 200)

def _get_blob_url(self, scenario_name: str) -> str:
return (
f'https://pythonworker{self._py_shortform}sa.blob.core.windows.net/'
Expand Down
Loading

0 comments on commit bb24993

Please sign in to comment.