Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
30 changes: 23 additions & 7 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,14 @@ jobs:
grep -F ">=3.11" <<<"$output"

artifact-python311:
name: Wheel install and smoke (Py 3.11)
name: Wheel install and smoke (Py 3.11, ${{ matrix.sdk }})
needs: [changes, artifact-build]
if: needs.changes.outputs.full_required == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sdk: ["azure-functions==1.21.0", "azure-functions>=1.21.0"]
steps:
- name: Checkout dependency metadata
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -315,7 +319,8 @@ jobs:
run: |
wheel=$(find compatibility-dist -maxdepth 1 -name '*.whl' -print -quit)
test -n "$wheel"
python -m pip install "$wheel"
python -m pip install "$wheel" "${{ matrix.sdk }}"
python -m pip show azure-functions

- name: Check installed wheel outside the checkout
working-directory: /tmp
Expand Down Expand Up @@ -343,15 +348,16 @@ jobs:
PY

host-smoke:
name: Real Functions host smoke (Py ${{ matrix.python-version }})
name: Real Functions host smoke (Py ${{ matrix.python-version }}, ${{ matrix.sdk }})
runs-on: ubuntu-latest
needs: changes
needs: [changes, artifact-build]
if: needs.changes.outputs.full_required == 'true'
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
sdk: ["azure-functions==1.21.0", "azure-functions>=1.21.0"]
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -370,9 +376,17 @@ jobs:
npm install --global azure-functions-core-tools@4 --unsafe-perm true
npm install --global azurite

- name: Download candidate wheel
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: compatibility-dist
path: compatibility-dist

- name: Install smoke app dependencies
working-directory: tests/host_smoke
run: pip install -r requirements.txt
run: |
python -m pip install compatibility-dist/*.whl "${{ matrix.sdk }}"
python -m pip check
python -m pip show azure-functions

- name: Start Azurite and Functions host
run: |
Expand All @@ -384,9 +398,11 @@ jobs:
run: |
for attempt in {1..30}; do
if response="$(curl --fail --silent http://127.0.0.1:7071/api/smoke)" \
&& default_response="$(curl --fail --silent http://127.0.0.1:7071/api/smoke-default)"; then
&& default_response="$(curl --fail --silent http://127.0.0.1:7071/api/smoke-default)" \
&& contract_response="$(curl --fail --silent http://127.0.0.1:7071/api/smoke-contract)"; then
test "$response" = "smoke"
test "$default_response" = "smoke_default"
test "$contract_response" = "sdk-contract-ok"
exit 0
fi
sleep 2
Expand Down
26 changes: 26 additions & 0 deletions docs/sdk-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Azure Functions SDK compatibility

Python runtime support and SDK support are different contracts. This package
supports Python 3.11–3.14 and requires `azure-functions>=1.21.0`.

The SDK bound is intentionally open. Required CI resolves both the exact floor
(`1.21.0`) and the newest stable release allowed by the requirement, installs
the candidate wheel, and runs import/structured-output smoke checks. The same
SDK selections run under a real local Functions host on Python 3.11 and 3.12,
including invocation context and secure redaction assertions. Pip's stable
resolver is used; prereleases are not certified implicitly.

| Configuration | Evidence boundary |
| --- | --- |
| SDK 1.21.0, Python 3.11 | Candidate-wheel install/import and output smoke |
| Latest stable SDK >=1.21.0, Python 3.11 | Forward-compatibility candidate-wheel smoke; resolved version printed in CI |
| Both SDK selections, Python 3.11/3.12, Core Tools v4 | Candidate-wheel host registration, invocation fields and redaction |
| Python 3.13/3.14 | Offline library matrix; not a claim of host certification on those interpreters |
| Future SDK majors | Not certified before the forward-compatibility lane actually passes with that major |

An open dependency bound is permission for the resolver, not a claim that every
future major has been tested. On a new-major failure, review the SDK change and
either adapt with regression tests or introduce a justified temporary ceiling
and upgrade plan. Do not silently call an untested major certified. Keep the
existing required CI and Azure release gates; local host success does not prove
Application Insights ingestion or cloud deployment compatibility.
33 changes: 32 additions & 1 deletion tests/host_smoke/function_app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from __future__ import annotations

import io
import json
import logging

import azure.functions as func

from azure_functions_logging import with_context
from azure_functions_logging import JsonFormatter, attach_secure_redaction, with_context
from azure_functions_logging._context import ContextFilter

app = func.FunctionApp()

Expand All @@ -20,3 +25,29 @@ def smoke_default(
context: func.Context = None, # type: ignore[assignment]
) -> func.HttpResponse:
return func.HttpResponse(context.function_name)


@app.route(route="smoke-contract", auth_level=func.AuthLevel.ANONYMOUS)
@with_context(strict=True)
def smoke_contract(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
stream = io.StringIO()
handler = logging.StreamHandler(stream)
handler.setFormatter(JsonFormatter())
handler.addFilter(ContextFilter())
attach_secure_redaction(handler)
record = logging.getLogger("sdk-host-contract").makeRecord(
"sdk-host-contract",
logging.WARNING,
__file__,
1,
"sdk host contract",
(),
None,
extra={"token": "SYNTHETIC_SDK_SECRET"},
)
handler.handle(record)
payload = json.loads(stream.getvalue())
assert payload["invocation_id"] == context.invocation_id
assert payload["function_name"] == context.function_name
assert payload["extra"]["token"] == "***"
return func.HttpResponse("sdk-contract-ok")
20 changes: 20 additions & 0 deletions tests/test_sdk_compatibility_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pathlib import Path
import tomllib

import yaml


def test_wheel_and_host_smokes_select_oldest_and_latest_sdk() -> None:
root = Path(__file__).resolve().parents[1]
workflow = yaml.safe_load((root / ".github/workflows/ci-test.yml").read_text())
for name in ("artifact-python311", "host-smoke"):
job = workflow["jobs"][name]
assert job["strategy"]["matrix"]["sdk"] == [
"azure-functions==1.21.0",
"azure-functions>=1.21.0",
]
steps = job["steps"]
assert any("matrix.sdk" in step.get("run", "") for step in steps)
assert "artifact-build" in workflow["jobs"]["host-smoke"]["needs"]
project = tomllib.loads((root / "pyproject.toml").read_text())
assert "azure-functions>=1.21.0" in project["project"]["dependencies"]
Loading