Skip to content

Commit bb24993

Browse files
authored
Merge branch 'dev' into pthummar/add_py311_tests
2 parents 827f09b + 201013e commit bb24993

16 files changed

+275
-124
lines changed

azure_functions_worker/constants.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3-
3+
import os
4+
import pathlib
45
import sys
56

67
# Capabilities
@@ -40,14 +41,19 @@
4041
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_310 = False
4142
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT = False
4243
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT_39 = True
44+
PYTHON_EXTENSIONS_RELOAD_FUNCTIONS = "PYTHON_EXTENSIONS_RELOAD_FUNCTIONS"
4345

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

4749
# new programming model script file name
4850
SCRIPT_FILE_NAME = "function_app.py"
49-
5051
PYTHON_LANGUAGE_RUNTIME = "python"
5152

5253
# Settings for V2 programming model
5354
RETRY_POLICY = "retry_policy"
55+
56+
# Paths
57+
CUSTOMER_PACKAGES_PATH = os.path.join(pathlib.Path.home(),
58+
pathlib.Path(
59+
"site/wwwroot/.python_packages"))

azure_functions_worker/dispatcher.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@
2626
PYTHON_THREADPOOL_THREAD_COUNT_MAX_37,
2727
PYTHON_THREADPOOL_THREAD_COUNT_MIN,
2828
PYTHON_ENABLE_DEBUG_LOGGING, SCRIPT_FILE_NAME,
29-
PYTHON_LANGUAGE_RUNTIME)
29+
PYTHON_LANGUAGE_RUNTIME, CUSTOMER_PACKAGES_PATH)
3030
from .extension import ExtensionManager
3131
from .logging import disable_console_logging, enable_console_logging
32-
from .logging import enable_debug_logging_recommendation
3332
from .logging import (logger, error_logger, is_system_log_category,
3433
CONSOLE_LOG_PREFIX, format_exception)
3534
from .utils.common import get_app_setting, is_envvar_true
@@ -263,9 +262,14 @@ async def _dispatch_grpc_request(self, request):
263262

264263
async def _handle__worker_init_request(self, request):
265264
logger.info('Received WorkerInitRequest, '
266-
'python version %s, worker version %s, request ID %s',
267-
sys.version, VERSION, self.request_id)
268-
enable_debug_logging_recommendation()
265+
'python version %s, '
266+
'worker version %s, '
267+
'request ID %s.'
268+
' To enable debug level logging, please refer to '
269+
'https://aka.ms/python-enable-debug-logging',
270+
sys.version,
271+
VERSION,
272+
self.request_id)
269273

270274
worker_init_request = request.worker_init_request
271275
host_capabilities = worker_init_request.capabilities
@@ -293,6 +297,10 @@ async def _handle__worker_init_request(self, request):
293297
"Importing azure functions in WorkerInitRequest")
294298
import azure.functions # NoQA
295299

300+
if CUSTOMER_PACKAGES_PATH not in sys.path:
301+
logger.warning("Customer packages not in sys path. "
302+
"This should never happen! ")
303+
296304
# loading bindings registry and saving results to a static
297305
# dictionary which will be later used in the invocation request
298306
bindings.load_binding_registry()
@@ -363,6 +371,7 @@ async def _handle__function_load_request(self, request):
363371
'Received WorkerLoadRequest, request ID %s, function_id: %s,'
364372
'function_name: %s,', self.request_id, function_id, function_name)
365373

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

388-
ExtensionManager.function_load_extension(
396+
try:
397+
ExtensionManager.function_load_extension(
398+
function_name,
399+
func_request.metadata.directory
400+
)
401+
except Exception as ex:
402+
logging.error("Failed to load extensions: ", ex)
403+
raise
404+
405+
logger.info('Successfully processed FunctionLoadRequest, '
406+
'request ID: %s, '
407+
'function ID: %s,'
408+
'function Name: %s,'
409+
'programming model: %s',
410+
self.request_id,
411+
function_id,
389412
function_name,
390-
func_request.metadata.directory
391-
)
392-
393-
logger.info('Successfully processed FunctionLoadRequest, '
394-
'request ID: %s, '
395-
'function ID: %s,'
396-
'function Name: %s', self.request_id,
397-
function_id,
398-
function_name)
413+
programming_model)
399414

400415
return protos.StreamingMessage(
401416
request_id=self.request_id,
@@ -532,8 +547,10 @@ async def _handle__function_environment_reload_request(self, request):
532547
"""
533548
try:
534549
logger.info('Received FunctionEnvironmentReloadRequest, '
535-
'request ID: %s', self.request_id)
536-
enable_debug_logging_recommendation()
550+
'request ID: %s,'
551+
' To enable debug level logging, please refer to '
552+
'https://aka.ms/python-enable-debug-logging',
553+
self.request_id)
537554

538555
func_env_reload_request = \
539556
request.function_environment_reload_request

azure_functions_worker/extension.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,8 @@ def _try_get_sdk_with_extension_enabled(cls) -> Optional[ModuleType]:
236236
@classmethod
237237
def _info_extension_is_enabled(cls, sdk):
238238
logger.info(
239-
'Python Worker Extension is enabled in azure.functions (%s).',
240-
get_sdk_version(sdk)
241-
)
239+
'Python Worker Extension is enabled in azure.functions (%s). '
240+
'Sdk path: %s', get_sdk_version(sdk), sdk.__file__)
242241

243242
@classmethod
244243
def _info_discover_extension_list(cls, function_name, sdk):

azure_functions_worker/loader.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,12 @@
1818
from . import protos, functions
1919
from .bindings.retrycontext import RetryPolicy
2020
from .constants import MODULE_NOT_FOUND_TS_URL, SCRIPT_FILE_NAME, \
21-
PYTHON_LANGUAGE_RUNTIME, RETRY_POLICY
21+
PYTHON_LANGUAGE_RUNTIME, RETRY_POLICY, CUSTOMER_PACKAGES_PATH
2222
from .utils.wrappers import attach_message_to_exception
2323

2424
_AZURE_NAMESPACE = '__app__'
2525
_DEFAULT_SCRIPT_FILENAME = '__init__.py'
2626
_DEFAULT_ENTRY_POINT = 'main'
27-
28-
PKGS_PATH = pathlib.Path("site/wwwroot/.python_packages")
29-
home = pathlib.Path.home()
30-
pkgs_path = os.path.join(home, PKGS_PATH)
31-
32-
3327
_submodule_dirs = []
3428

3529

@@ -140,7 +134,8 @@ def process_indexed_function(functions_registry: functions.Registry,
140134
f' guide: {MODULE_NOT_FOUND_TS_URL} ',
141135
debug_logs='Error in load_function. '
142136
f'Sys Path: {sys.path}, Sys Module: {sys.modules},'
143-
f'python-packages Path exists: {os.path.exists(pkgs_path)}')
137+
'python-packages Path exists: '
138+
f'{os.path.exists(CUSTOMER_PACKAGES_PATH)}')
144139
def load_function(name: str, directory: str, script_file: str,
145140
entry_point: Optional[str]):
146141
dir_path = pathlib.Path(directory)
@@ -191,7 +186,8 @@ def load_function(name: str, directory: str, script_file: str,
191186
message=f'Troubleshooting Guide: {MODULE_NOT_FOUND_TS_URL}',
192187
debug_logs='Error in index_function_app. '
193188
f'Sys Path: {sys.path}, Sys Module: {sys.modules},'
194-
f'python-packages Path exists: {os.path.exists(pkgs_path)}')
189+
'python-packages Path exists: '
190+
f'{os.path.exists(CUSTOMER_PACKAGES_PATH)}')
195191
def index_function_app(function_path: str):
196192
module_name = pathlib.Path(function_path).stem
197193
imported_module = importlib.import_module(module_name)

azure_functions_worker/logging.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,6 @@ def enable_console_logging() -> None:
9191
logger.addHandler(handler)
9292

9393

94-
def enable_debug_logging_recommendation():
95-
logging.info("To enable debug level logging, please refer to "
96-
"https://aka.ms/python-enable-debug-logging")
97-
98-
9994
def is_system_log_category(ctg: str) -> bool:
10095
"""Check if the logging namespace belongs to system logs. Category starts
10196
with the following name will be treated as system logs.

azure_functions_worker/main.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# Licensed under the MIT License.
33
"""Main entrypoint."""
44

5-
65
import argparse
76

87

@@ -26,6 +25,18 @@ def parse_args():
2625
'syslog, or a file path')
2726
parser.add_argument('--grpcMaxMessageLength', type=int,
2827
dest='grpc_max_msg_len')
28+
parser.add_argument('--functions-uri', dest='functions_uri', type=str,
29+
help='URI with IP Address and Port used to'
30+
' connect to the Host via gRPC.')
31+
parser.add_argument('--functions-request-id', dest='functions_request_id',
32+
type=str, help='Request ID used for gRPC communication '
33+
'with the Host.')
34+
parser.add_argument('--functions-worker-id',
35+
dest='functions_worker_id', type=str,
36+
help='Worker ID assigned to this language worker.')
37+
parser.add_argument('--functions-grpc-max-message-length', type=int,
38+
dest='functions_grpc_max_msg_len',
39+
help='Max grpc message length for Functions')
2940
return parser.parse_args()
3041

3142

azure_functions_worker/utils/common.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3-
from typing import Optional, Callable
4-
from types import ModuleType
3+
import importlib
54
import os
65
import sys
7-
import importlib
6+
from types import ModuleType
7+
from typing import Optional, Callable
8+
9+
from azure_functions_worker.constants import CUSTOMER_PACKAGES_PATH, \
10+
PYTHON_EXTENSIONS_RELOAD_FUNCTIONS
811

912

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

116-
if 'azure.functions' in sys.modules:
117-
backup_azure_functions = sys.modules.pop('azure.functions')
118-
if 'azure' in sys.modules:
119-
backup_azure = sys.modules.pop('azure')
117+
if is_envvar_true(PYTHON_EXTENSIONS_RELOAD_FUNCTIONS):
118+
backup_azure_functions = None
119+
backup_azure = None
120+
121+
if 'azure.functions' in sys.modules:
122+
backup_azure_functions = sys.modules.pop('azure.functions')
123+
if 'azure' in sys.modules:
124+
backup_azure = sys.modules.pop('azure')
125+
126+
module = importlib.import_module('azure.functions')
127+
128+
if backup_azure:
129+
sys.modules['azure'] = backup_azure
130+
if backup_azure_functions:
131+
sys.modules['azure.functions'] = backup_azure_functions
120132

121-
module = importlib.import_module('azure.functions')
133+
return module
122134

123-
if backup_azure:
124-
sys.modules['azure'] = backup_azure
125-
if backup_azure_functions:
126-
sys.modules['azure.functions'] = backup_azure_functions
135+
if CUSTOMER_PACKAGES_PATH not in sys.path:
136+
sys.path.insert(0, CUSTOMER_PACKAGES_PATH)
127137

128-
return module
138+
return importlib.import_module('azure.functions')

azure_functions_worker/utils/dependency.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3-
from azure_functions_worker.utils.common import is_true_like
4-
from typing import List, Optional
5-
from types import ModuleType
63
import importlib
74
import inspect
85
import os
96
import re
107
import sys
8+
from types import ModuleType
9+
from typing import List, Optional
1110

12-
from ..logging import logger
11+
from azure_functions_worker.utils.common import is_true_like
1312
from ..constants import (
1413
AZURE_WEBJOBS_SCRIPT_ROOT,
1514
CONTAINER_NAME,
1615
PYTHON_ISOLATE_WORKER_DEPENDENCIES,
1716
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT,
1817
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_310
1918
)
19+
from ..logging import logger
2020
from ..utils.common import is_python_version
2121
from ..utils.wrappers import enable_feature_by
2222

@@ -226,7 +226,7 @@ def reload_azure_google_namespace_from_worker_deps(cls):
226226
logger.info('Reloaded azure.functions module now at %s',
227227
inspect.getfile(sys.modules['azure.functions']))
228228
except Exception as ex:
229-
logger.info(
229+
logger.warning(
230230
'Unable to reload azure.functions. Using default. '
231231
'Exception:\n%s', ex)
232232

azure_functions_worker/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4-
VERSION = '4.16.0'
4+
VERSION = '4.18.0'

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
]
107107

108108
INSTALL_REQUIRES = [
109-
"azure-functions==1.16.0",
109+
"azure-functions==1.18.0b3",
110110
"python-dateutil~=2.8.2"
111111
]
112112

tests/consumption_tests/test_linux_consumption.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,37 @@ def test_pinning_functions_to_older_version(self):
217217
"AzureWebJobsStorage": self._storage,
218218
"SCM_RUN_FROM_PACKAGE": self._get_blob_url(
219219
"PinningFunctions"),
220-
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1"
220+
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1",
221221
})
222222
req = Request('GET', f'{ctrl.url}/api/HttpTrigger1')
223223
resp = ctrl.send_request(req)
224224

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

228+
@skipIf(sys.version_info.minor != 10,
229+
"This is testing only for python310")
230+
def test_opencensus_with_extensions_enabled(self):
231+
"""A function app with extensions enabled containing the
232+
following libraries:
233+
234+
azure-functions, azure-eventhub, azure-storage-blob, numpy,
235+
cryptography, pyodbc, requests
236+
237+
should return 200 after importing all libraries.
238+
"""
239+
with LinuxConsumptionWebHostController(_DEFAULT_HOST_VERSION,
240+
self._py_version) as ctrl:
241+
ctrl.assign_container(env={
242+
"AzureWebJobsStorage": self._storage,
243+
"SCM_RUN_FROM_PACKAGE": self._get_blob_url("Opencensus"),
244+
"PYTHON_ENABLE_WORKER_EXTENSIONS": "1",
245+
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing"
246+
})
247+
req = Request('GET', f'{ctrl.url}/api/opencensus')
248+
resp = ctrl.send_request(req)
249+
self.assertEqual(resp.status_code, 200)
250+
228251
def _get_blob_url(self, scenario_name: str) -> str:
229252
return (
230253
f'https://pythonworker{self._py_shortform}sa.blob.core.windows.net/'

0 commit comments

Comments
 (0)