Skip to content

Commit ff38583

Browse files
authored
Append /home/site/wwwroot to sys.path (#726)
* Add sys.path reload in Linux Consumption and add /home/site/wwwroot in Linux Dedicated/Premium
1 parent 2d71726 commit ff38583

File tree

17 files changed

+239
-24
lines changed

17 files changed

+239
-24
lines changed

azure_functions_worker/dispatcher.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ async def _handle__invocation_request(self, req):
399399
exception=self._serialize_exception(ex))))
400400

401401
async def _handle__function_environment_reload_request(self, req):
402+
'''Only runs on Linux Consumption placeholder specialization.'''
402403
try:
403404
logger.info('Received FunctionEnvironmentReloadRequest, '
404405
'request ID: %s', self.request_id)
@@ -410,12 +411,16 @@ async def _handle__function_environment_reload_request(self, req):
410411
# customer use
411412
import azure.functions # NoQA
412413

414+
# Append function project root to module finding sys.path
415+
if func_env_reload_request.function_app_directory:
416+
sys.path.append(func_env_reload_request.function_app_directory)
417+
418+
# Clear sys.path import cache, reload all module from new sys.path
413419
sys.path_importer_cache.clear()
414420

421+
# Reload environment variables
415422
os.environ.clear()
416-
417423
env_vars = func_env_reload_request.environment_variables
418-
419424
for var in env_vars:
420425
os.environ[var] = env_vars[var]
421426

python/prodV2/worker.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,33 @@
1313
# Azure environment variables
1414
AZURE_WEBSITE_INSTANCE_ID = "WEBSITE_INSTANCE_ID"
1515
AZURE_CONTAINER_NAME = "CONTAINER_NAME"
16+
AZURE_WEBJOBS_SCRIPT_ROOT = "AzureWebJobsScriptRoot"
1617

1718

1819
def is_azure_environment():
20+
'''Check if the function app is running on the cloud'''
1921
return (AZURE_CONTAINER_NAME in os.environ
2022
or AZURE_WEBSITE_INSTANCE_ID in os.environ)
2123

2224

25+
def add_script_root_to_sys_path():
26+
'''Append function project root to module finding sys.path'''
27+
functions_script_root = os.getenv(AZURE_WEBJOBS_SCRIPT_ROOT)
28+
if functions_script_root is not None:
29+
sys.path.append(functions_script_root)
30+
31+
2332
def determine_user_pkg_paths():
33+
'''This finds the user packages when function apps are running on the cloud
34+
35+
For Python 3.6 app, the third-party packages can live in any of the paths:
36+
/home/site/wwwroot/.python_packages/lib/site-packages
37+
/home/site/wwwroot/.python_packages/lib/python3.6/site-packages
38+
/home/site/wwwroot/worker_venv/lib/python3.6/site-packages
39+
40+
For Python 3.7, we only accept:
41+
/home/site/wwwroot/.python_packages/lib/site-packages
42+
'''
2443
minor_version = sys.version_info[1]
2544

2645
home = Path.home()
@@ -49,13 +68,19 @@ def determine_user_pkg_paths():
4968
user_pkg_paths = determine_user_pkg_paths()
5069

5170
joined_pkg_paths = os.pathsep.join(user_pkg_paths)
71+
72+
# On cloud, we prioritize third-party user packages
73+
# over worker packages in PYTHONPATH
5274
env['PYTHONPATH'] = f'{joined_pkg_paths}:{func_worker_dir}'
5375
os.execve(sys.executable,
5476
[sys.executable, '-m', 'azure_functions_worker']
5577
+ sys.argv[1:],
5678
env)
5779
else:
80+
# On local development, we prioritize worker packages over
81+
# third-party user packages (in .venv)
5882
sys.path.insert(1, func_worker_dir)
83+
add_script_root_to_sys_path()
5984
from azure_functions_worker import main
6085

6186
main.main()

python/prodV3/worker.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,33 @@
1313
# Azure environment variables
1414
AZURE_WEBSITE_INSTANCE_ID = "WEBSITE_INSTANCE_ID"
1515
AZURE_CONTAINER_NAME = "CONTAINER_NAME"
16+
AZURE_WEBJOBS_SCRIPT_ROOT = "AzureWebJobsScriptRoot"
1617

1718

1819
def is_azure_environment():
20+
'''Check if the function app is running on the cloud'''
1921
return (AZURE_CONTAINER_NAME in os.environ
2022
or AZURE_WEBSITE_INSTANCE_ID in os.environ)
2123

2224

25+
def add_script_root_to_sys_path():
26+
'''Append function project root to module finding sys.path'''
27+
functions_script_root = os.getenv(AZURE_WEBJOBS_SCRIPT_ROOT)
28+
if functions_script_root is not None:
29+
sys.path.append(functions_script_root)
30+
31+
2332
def determine_user_pkg_paths():
33+
'''This finds the user packages when function apps are running on the cloud
34+
35+
For Python 3.6 app, the third-party packages can live in any of the paths:
36+
/home/site/wwwroot/.python_packages/lib/site-packages
37+
/home/site/wwwroot/.python_packages/lib/python3.6/site-packages
38+
/home/site/wwwroot/worker_venv/lib/python3.6/site-packages
39+
40+
For Python 3.7 and Python 3.8, we only accept:
41+
/home/site/wwwroot/.python_packages/lib/site-packages
42+
'''
2443
minor_version = sys.version_info[1]
2544

2645
home = Path.home()
@@ -49,13 +68,19 @@ def determine_user_pkg_paths():
4968
user_pkg_paths = determine_user_pkg_paths()
5069

5170
joined_pkg_paths = os.pathsep.join(user_pkg_paths)
71+
72+
# On cloud, we prioritize third-party user packages
73+
# over worker packages in PYTHONPATH
5274
env['PYTHONPATH'] = f'{joined_pkg_paths}:{func_worker_dir}'
5375
os.execve(sys.executable,
5476
[sys.executable, '-m', 'azure_functions_worker']
5577
+ sys.argv[1:],
5678
env)
5779
else:
80+
# On local development, we prioritize worker packages over
81+
# third-party user packages (in .venv)
5882
sys.path.insert(1, func_worker_dir)
83+
add_script_root_to_sys_path()
5984
from azure_functions_worker import main
6085

6186
main.main()

python/test/worker.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1+
import sys
2+
import os
13
from azure_functions_worker import main
24

5+
6+
# Azure environment variables
7+
AZURE_WEBJOBS_SCRIPT_ROOT = "AzureWebJobsScriptRoot"
8+
9+
10+
def add_script_root_to_sys_path():
11+
'''Append function project root to module finding sys.path'''
12+
functions_script_root = os.getenv(AZURE_WEBJOBS_SCRIPT_ROOT)
13+
if functions_script_root is not None:
14+
sys.path.append(functions_script_root)
15+
16+
317
if __name__ == '__main__':
18+
add_script_root_to_sys_path()
419
main.main()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"scriptFile": "main.py",
3+
"entryPoint": "main",
4+
"bindings": [
5+
{
6+
"type": "httpTrigger",
7+
"direction": "in",
8+
"name": "req"
9+
},
10+
{
11+
"type": "http",
12+
"direction": "out",
13+
"name": "$return"
14+
}
15+
]
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
# Import a module from thirdparty package azure-eventhub
5+
import azure.eventhub as eh
6+
7+
8+
def main(req) -> str:
9+
return f'eh = {eh.__name__}'

tests/unittests/load_functions/brokenimplicit/function.json

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"scriptFile": "main.py",
3+
"entryPoint": "implicitinmport",
4+
"bindings": [
5+
{
6+
"type": "httpTrigger",
7+
"direction": "in",
8+
"name": "req"
9+
},
10+
{
11+
"type": "http",
12+
"direction": "out",
13+
"name": "$return"
14+
}
15+
]
16+
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3-
# Import simple module with implicit directory import statement should fail
3+
4+
# Import simple module with implicit statement should now be acceptable
5+
# since sys.path is now appended with function script root
46
from simple.main import main as s_main
57

68

7-
def brokenimplicit(req) -> str:
9+
def implicitinmport(req) -> str:
810
return f's_main = {s_main(req)}'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"scriptFile": "main.py",
3+
"entryPoint": "modulenotfound",
4+
"bindings": [
5+
{
6+
"type": "httpTrigger",
7+
"direction": "in",
8+
"name": "req"
9+
},
10+
{
11+
"type": "http",
12+
"direction": "out",
13+
"name": "$return"
14+
}
15+
]
16+
}

0 commit comments

Comments
 (0)