Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6b989e0
initial commit for supporting runtime base
hallvictoria Apr 24, 2026
fb6b6e8
lint + add tests
hallvictoria Apr 24, 2026
d0bccce
lint
hallvictoria Apr 24, 2026
484b6e2
Merge branch 'dev' into hallvictoria/runtime-base
hallvictoria Apr 27, 2026
f2c0c7d
wrap around app setting
hallvictoria Apr 27, 2026
325f6b1
Merge branch 'hallvictoria/runtime-base' of https://github.com/Azure/…
hallvictoria Apr 27, 2026
41b782d
add hasattr check to not break old base ext versions
hallvictoria May 4, 2026
3c03c90
enable feature for running tests
hallvictoria May 4, 2026
e8b0e03
Merge branch 'dev' into hallvictoria/runtime-base
hallvictoria May 4, 2026
0e10100
Merge branch 'dev' of https://github.com/Azure/azure-functions-python…
hallvictoria May 5, 2026
225d20e
fix if condition
hallvictoria May 5, 2026
ad9ac97
Merge branch 'hallvictoria/runtime-base' of https://github.com/Azure/…
hallvictoria May 5, 2026
a594c87
feedback part 1
hallvictoria May 5, 2026
cbaa2d7
feedback part 2
hallvictoria May 5, 2026
3faaf48
lint
hallvictoria May 5, 2026
546e668
run tests with runtime feature disable
hallvictoria May 5, 2026
8e495cb
fix tests
hallvictoria May 6, 2026
7933430
fix tests
hallvictoria May 6, 2026
60568ef
fix tests
hallvictoria May 6, 2026
b79418f
fix tests
hallvictoria May 6, 2026
a43c30d
fix tests
hallvictoria May 7, 2026
554516b
FIX TESTS
hallvictoria May 7, 2026
fd682d9
feedback
hallvictoria May 8, 2026
3dca7d5
fix flaky tests?
hallvictoria May 8, 2026
ade99fa
fix internal vs public feeds
hallvictoria May 8, 2026
72d7ba4
replace sc with feed
hallvictoria May 8, 2026
d5b6a43
fix nuget.config
hallvictoria May 8, 2026
cddc38d
annoying!!!
hallvictoria May 11, 2026
64a425d
annoying!!! again!!!
hallvictoria May 11, 2026
6d24b2a
feedback
hallvictoria May 11, 2026
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 eng/ci/emulator-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ extends:
- template: /eng/templates/jobs/ci-emulator-tests.yml@self
parameters:
PoolName: 1es-pool-azfunc
ArtifactFeed: 'internal/PythonWorker_Internal_PublicPackages'
ArtifactFeed: 'internal/PythonWorker_Internal_PublicPackages'
NuGetServiceConnection: 'PythonWorker_Internal_PublicPackages'
6 changes: 5 additions & 1 deletion eng/ci/official-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ extends:
parameters:
PoolName: 1es-pool-azfunc
ArtifactFeed: 'internal/PythonWorker_Internal_PublicPackages'
NuGetServiceConnection: 'PythonWorker_Internal_PublicPackages'
- stage: RunWorkerUnitTests
dependsOn: BuildPythonWorker
jobs:
- template: /eng/templates/jobs/ci-unit-tests.yml@self
parameters:
PoolName: 1es-pool-azfunc
ArtifactFeed: 'internal/PythonWorker_Internal_PublicPackages'
NuGetServiceConnection: 'PythonWorker_Internal_PublicPackages'
- stage: RunWorkerDockerConsumptionTests
dependsOn: BuildPythonWorker
jobs:
Expand Down Expand Up @@ -102,6 +104,7 @@ extends:
PROJECT_DIRECTORY: 'runtimes/v2'
PoolName: 1es-pool-azfunc
ArtifactFeed: 'internal/PythonWorker_Internal_PublicPackages'
NuGetServiceConnection: 'PythonWorker_Internal_PublicPackages'

# Python V1 Library Build and Test Stages
- stage: BuildV1Library
Expand All @@ -120,4 +123,5 @@ extends:
PROJECT_NAME: 'Python V1 Library'
PROJECT_DIRECTORY: 'runtimes/v1'
PoolName: 1es-pool-azfunc
ArtifactFeed: 'internal/PythonWorker_Internal_PublicPackages'
ArtifactFeed: 'internal/PythonWorker_Internal_PublicPackages'
NuGetServiceConnection: 'PythonWorker_Internal_PublicPackages'
6 changes: 5 additions & 1 deletion eng/ci/public-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ extends:
PROJECT_DIRECTORY: 'workers'
PoolName: 1es-pool-azfunc-public
ArtifactFeed: 'public/PythonWorker_PublicPackages'
NuGetServiceConnection: 'PythonWorker_PublicPackages'
- stage: RunWorkerEmulatorTests
dependsOn: BuildPythonWorker
jobs:
- template: /eng/templates/jobs/ci-emulator-tests.yml@self
parameters:
PoolName: 1es-pool-azfunc-public
ArtifactFeed: 'public/PythonWorker_PublicPackages'
NuGetServiceConnection: 'PythonWorker_PublicPackages'

# Python V2 Library Build and Test Stages
- stage: BuildV2Library
Expand All @@ -114,6 +116,7 @@ extends:
PROJECT_DIRECTORY: 'runtimes/v2'
PoolName: 1es-pool-azfunc-public
ArtifactFeed: 'public/PythonWorker_PublicPackages'
NuGetServiceConnection: 'PythonWorker_PublicPackages'

# Python V1 Library Build and Test Stages
- stage: BuildV1Library
Expand All @@ -132,4 +135,5 @@ extends:
PROJECT_NAME: 'V1 Library'
PROJECT_DIRECTORY: 'runtimes/v1'
PoolName: 1es-pool-azfunc-public
ArtifactFeed: 'public/PythonWorker_PublicPackages'
ArtifactFeed: 'public/PythonWorker_PublicPackages'
NuGetServiceConnection: 'PythonWorker_PublicPackages'
15 changes: 15 additions & 0 deletions eng/templates/jobs/ci-emulator-tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
parameters:
PROJECT_DIRECTORY: 'workers'
ArtifactFeed: ''
NuGetServiceConnection: ''

jobs:
- job: "TestPython"
Expand Down Expand Up @@ -55,6 +56,20 @@ jobs:
displayName: 'Install .NET 10'
inputs:
version: 10.0.x
- task: NuGetAuthenticate@1
displayName: 'NuGet Authenticate'
- bash: |
# Remove the feed that doesn't match the current service connection
if [[ "${{ parameters.NuGetServiceConnection }}" == "PythonWorker_PublicPackages" ]]; then
# Remove internal feed for public builds
sed -i '/_Internal_PublicPackages/d' nuget.config
else
# Remove public feed for internal builds
sed -i '/PythonWorker_PublicPackages[^_]/d' nuget.config
fi
echo "Updated nuget.config:"
cat nuget.config
displayName: 'Configure NuGet feed for current organization'
- bash: |
chmod +x eng/scripts/install-dependencies.sh
chmod +x eng/scripts/test-setup.sh
Expand Down
15 changes: 15 additions & 0 deletions eng/templates/jobs/ci-library-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ parameters:
PROJECT_NAME: ''
PROJECT_DIRECTORY: ''
ArtifactFeed: ''
NuGetServiceConnection: ''

jobs:
- job: "TestPython"
Expand Down Expand Up @@ -29,6 +30,20 @@ jobs:
displayName: 'Install .NET 10'
inputs:
version: 10.0.x
- task: NuGetAuthenticate@1
displayName: 'NuGet Authenticate'
- bash: |
# Remove the feed that doesn't match the current service connection
if [[ "${{ parameters.NuGetServiceConnection }}" == "PythonWorker_PublicPackages" ]]; then
# Remove internal feed for public builds
sed -i '/_Internal_PublicPackages/d' nuget.config
else
# Remove public feed for internal builds
sed -i '/PythonWorker_PublicPackages[^_]/d' nuget.config
fi
echo "Updated nuget.config:"
cat nuget.config
displayName: 'Configure NuGet feed for current organization'
- bash: |
chmod +x eng/scripts/install-dependencies.sh
eng/scripts/install-dependencies.sh $(PYTHON_VERSION) ${{ parameters.PROJECT_DIRECTORY }}
Expand Down
15 changes: 15 additions & 0 deletions eng/templates/jobs/ci-unit-tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
parameters:
PROJECT_DIRECTORY: 'workers'
ArtifactFeed: ''
NuGetServiceConnection: ''

jobs:
- job: "TestPython"
Expand Down Expand Up @@ -55,6 +56,20 @@ jobs:
displayName: 'Install .NET 10'
inputs:
version: 10.0.x
- task: NuGetAuthenticate@1
displayName: 'NuGet Authenticate'
- bash: |
# Remove the feed that doesn't match the current service connection
if [[ "${{ parameters.NuGetServiceConnection }}" == "PythonWorker_PublicPackages" ]]; then
# Remove internal feed for public builds
sed -i '/_Internal_PublicPackages/d' nuget.config
else
# Remove public feed for internal builds
sed -i '/PythonWorker_PublicPackages[^_]/d' nuget.config
fi
echo "Updated nuget.config:"
cat nuget.config
displayName: 'Configure NuGet feed for current organization'
- bash: |
chmod +x eng/scripts/install-dependencies.sh
chmod +x eng/scripts/test-setup.sh
Expand Down
15 changes: 15 additions & 0 deletions eng/templates/official/jobs/ci-e2e-tests.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
parameters:
PROJECT_DIRECTORY: 'workers'
NuGetServiceConnection: 'PythonWorker_Internal_PublicPackages'

jobs:
- job: "TestPython"
Expand Down Expand Up @@ -78,6 +79,20 @@ jobs:
displayName: 'Install .NET 10'
inputs:
version: 10.0.x
- task: NuGetAuthenticate@1
displayName: 'NuGet Authenticate'
- bash: |
# Remove the feed that doesn't match the current service connection
if [[ "${{ parameters.NuGetServiceConnection }}" == "PythonWorker_PublicPackages" ]]; then
# Remove internal feed for public builds
sed -i '/_Internal_PublicPackages/d' nuget.config
else
# Remove public feed for internal builds
sed -i '/PythonWorker_PublicPackages[^_]/d' nuget.config
fi
echo "Updated nuget.config:"
cat nuget.config
displayName: 'Configure NuGet feed for current organization'
- bash: |
chmod +x eng/scripts/install-dependencies.sh
chmod +x eng/scripts/test-setup.sh
Expand Down
8 changes: 8 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="PythonWorker_Internal_PublicPackages" value="https://pkgs.dev.azure.com/azfunc/internal/_packaging/PythonWorker_Internal_PublicPackages/nuget/v3/index.json" />
<add key="PythonWorker_PublicPackages" value="https://azfunc.pkgs.visualstudio.com/public/_packaging/PythonWorker_PublicPackages/nuget/v3/index.json" />
</packageSources>
</configuration>
88 changes: 88 additions & 0 deletions workers/proxy_worker/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Licensed under the MIT License.

import asyncio
import importlib
import logging
import os
import queue
Expand All @@ -11,6 +12,7 @@
import typing
from asyncio import AbstractEventLoop
from dataclasses import dataclass
from importlib.metadata import entry_points
from typing import Any, Optional

import grpc
Expand All @@ -30,6 +32,7 @@
check_python_eol
)
from proxy_worker.utils.constants import (
PYTHON_ENABLE_AGENT_RUNTIME,
PYTHON_ENABLE_DEBUG_LOGGING,
)
from proxy_worker.version import VERSION
Expand Down Expand Up @@ -417,7 +420,92 @@ def stop(self) -> None:

@staticmethod
def reload_library_worker(directory: str):
"""
Load the appropriate runtime using the base package pattern.

This uses the runtime base package to automatically discover which
runtime is loaded.

If no runtime is registered via the base package, it falls back to
the traditional detection method.
"""
global _library_worker, _library_worker_has_cv

if is_envvar_true(PYTHON_ENABLE_AGENT_RUNTIME):
try:
# Import base package
try:
import azurefunctions.extensions.base as runtime_base
except ImportError:
logger.debug("Base extension package not found: %s",
traceback.format_exc())
runtime_base = None

# Discover all installed runtime packages via entry points
available_runtimes = list(entry_points(group='azurefunctions.runtimes'))

# Only one runtime should be defined
if len(available_runtimes) > 1:
runtime_names = [ep.name for ep in available_runtimes]
raise RuntimeError(
"Multiple runtimes detected: %s. "
"Only one runtime should be defined." % runtime_names
)

# Load the single runtime entry point if available
if available_runtimes:
ep = available_runtimes[0]
try:
# Load the entry point (triggers import and
# metaclass registration)
ep.load()
logger.debug("Loaded runtime entry point: %s" % ep.name)
except Exception as e:
raise RuntimeError(
"Failed to load runtime entry point %s: %s" % (ep.name, e)
)

# Check if a runtime was registered
# Check if the runtime base package has the RuntimeFeatureChecker
# Check if the runtime is loaded
if runtime_base is not None \
and hasattr(runtime_base, 'RuntimeFeatureChecker') \
Comment thread
hallvictoria marked this conversation as resolved.
and runtime_base.RuntimeFeatureChecker.runtime_loaded():
# Get the registered runtime module
# (e.g., "azure_functions_fastapi.runtime")
runtime_module_name = (
runtime_base.RuntimeTrackerMeta.get_module())
runtime_name = (
runtime_base.RuntimeTrackerMeta.get_runtime_name())
package_name = (
runtime_base.RuntimeTrackerMeta.get_package_name())

logger.debug("Runtime registered: %s (module: %s). "
"Importing runtime package: %s",
runtime_name, runtime_module_name, package_name)

# Import the top-level runtime package (which exports
# the public API)
runtime_module = importlib.import_module(package_name)
_library_worker = runtime_module
_library_worker_has_cv = _library_worker.invocation_id_cv

# Module has been imported, end check
return
else:
logger.error("Base extension version is not compatible "
"for custom runtimes. "
"Please update to version 1.2.0 or greater.")
raise RuntimeError("Base extension version is not compatible "
"for custom runtimes. "
"Please update to version 1.2.0 or greater.")
except Exception as e:
logger.error("Error when loading runtime: %s",
traceback.format_exc())
raise e

# No runtime registered via base package
# Use traditional detection
v2_scriptfile = os.path.join(directory, get_script_file_name())
if os.path.exists(v2_scriptfile):
try:
Expand Down
2 changes: 2 additions & 0 deletions workers/proxy_worker/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
PYTHON_SCRIPT_FILE_NAME = "PYTHON_SCRIPT_FILE_NAME"
PYTHON_SCRIPT_FILE_NAME_DEFAULT = "function_app.py"

PYTHON_ENABLE_AGENT_RUNTIME = "PYTHON_ENABLE_AGENT_RUNTIME"

# EOL Dates
PYTHON_EOL_DATES = {
'3.13': '2029-10',
Expand Down
Loading
Loading