01Log the Python and package versions next to your resultsVerified

The same code can give different results under different Python and package versions. Append the versions, OS and whether a virtual environment was used to a JSON Lines log on every run, so results can be reproduced later.

Approach and steps
  1. Get the Python and package versions with platform.python_version() and importlib.metadata.version().
  2. If sys.prefix != sys.base_prefix, the interpreter is running inside a virtual environment.
  3. Append one line per run to out_runtime_log.jsonl and keep it next to the result files.
  4. Record missing packages as null instead of stopping with an exception.
  5. Call the same function in Azure Functions or a notebook and compare with the local log.
PythonRuns locally
"""Record the Python runtime and package versions next to your results (JSON Lines)."""
import datetime
import importlib.metadata as md
import json
import platform
import sys

PACKAGES = ["pandas", "openpyxl", "python-docx", "azure-functions", "microsoft-agents-hosting-core"]


def runtime_record(packages):
    versions = {}
    for name in packages:
        try:
            versions[name] = md.version(name)
        except md.PackageNotFoundError:
            versions[name] = None
    return {
        "recorded_at": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds"),
        "python": platform.python_version(),
        "implementation": platform.python_implementation(),
        "os": platform.platform(),
        "executable": sys.executable,
        "in_venv": sys.prefix != sys.base_prefix,
        "packages": versions,
    }


if __name__ == "__main__":
    rec = runtime_record(PACKAGES)
    with open("out_runtime_log.jsonl", "a", encoding="utf-8") as f:
        f.write(json.dumps(rec, ensure_ascii=False) + "\n")
    print("python", rec["python"], "| venv:", rec["in_venv"])
    for name, version in rec["packages"].items():
        print(f"  {name}: {version or 'not installed'}")
Output(2026-09-12)
python 3.12.10 | venv: True
  pandas: 3.0.5
  openpyxl: 3.1.5
  python-docx: 1.2.0
  azure-functions: 1.25.0
  microsoft-agents-hosting-core: 1.5.0
Example instruction for Copilot

Write a Python function that appends one JSON Lines record with the Python version, OS, whether a virtual environment is active and the versions of given packages. Use only the standard library and record missing packages as null.

Caution

The record includes the interpreter path, which can contain a user name; check before sharing. Results can still differ with the same versions if the OS or data differ.

Availability
Standard library only (checked with Python 3.12.10).
Requires
pandas 3.0.5, openpyxl 3.1.5, python-docx 1.2.0, azure-functions 1.25.0, microsoft-agents-hosting-core 1.5.0
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11, pandas 3.0.5, openpyxl 3.1.5, python-docx 1.2.0, azure-functions 1.25.0, microsoft-agents-hosting-core 1.5.0)
Source
Python documentation, "venv — Creation of virtual environments"
Verified
2026-09-12 (v1)
Supporting passages from the sources
sys.prefix != sys.base_prefix to determine if the current interpreter is

02Compare the versions pinned in requirements.txt with what is installedVerified

In a virtual environment, run python -m pip freeze > requirements.txt and commit the pinned list. Before running elsewhere, compare the pinned versions with what is actually installed and show the lines that differ.

Approach and steps
  1. In the development venv, run python -m pip freeze > requirements.txt and commit it.
  2. In another environment, install the same versions with python -m pip install -r requirements.txt.
  3. Before running, use this script to compare each == pin with the version reported by importlib.metadata.
  4. Differences are shown as MISMATCH. With STRICT=1 the script exits with an error code.
  5. Write a freeze-style list to out_freeze.txt as a record of the run.
PythonRuns locally
"""Compare a pinned requirements file with the packages installed in the current environment."""
import importlib.metadata as md
import os
import re
import sys

REQ_FILE = os.environ.get("REQ_FILE", "samples/agents_requirements.txt")
PIN = re.compile(r"^([A-Za-z0-9._-]+)==([^\s;#]+)")


def read_pins(path):
    pins = {}
    with open(path, encoding="utf-8") as f:
        for line in f:
            m = PIN.match(line.strip())
            if m:
                pins[m.group(1)] = m.group(2)
    return pins


def compare(pins):
    rows = []
    for name, wanted in sorted(pins.items()):
        try:
            have = md.version(name)
        except md.PackageNotFoundError:
            have = None
        rows.append((name, wanted, have, "ok" if have == wanted else "MISMATCH"))
    return rows


def freeze_lines():
    """The same shape as `python -m pip freeze`, without starting another process."""
    return sorted(f"{d.metadata['Name']}=={d.version}" for d in md.distributions())


if __name__ == "__main__":
    rows = compare(read_pins(REQ_FILE))
    for name, wanted, have, state in rows:
        print(f"{name:<16} want {wanted:<8} have {str(have):<8} {state}")
    with open("out_freeze.txt", "w", encoding="utf-8") as f:
        f.write("\n".join(freeze_lines()) + "\n")
    bad = [r for r in rows if r[3] != "ok"]
    print("mismatches:", len(bad))
    sys.exit(1 if bad and os.environ.get("STRICT") == "1" else 0)
Output(2026-09-12)
azure-functions  want 1.25.0   have 1.25.0   ok
openpyxl         want 3.1.5    have 3.1.5    ok
pandas           want 3.0.5    have 3.0.5    ok
python-docx      want 1.2.0    have 1.2.0    ok
requests         want 2.0.0    have 2.34.2   MISMATCH
mismatches: 1
Example instruction for Copilot

Write a Python script that compares the == pins in requirements.txt with the versions installed in the current environment and prints the differences. Do not use subprocess; read versions with importlib.metadata, and also write a pip-freeze-style list to a file.

Caution

A freeze list also contains packages that were only pulled in as dependencies. Azure Functions installs the requirements.txt dependencies during remote build at deployment. A different Python version may not have the same package versions available.

Availability
Standard library only (checked with Python 3.12.10).
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11)
Source
Python documentation, "12. Virtual Environments and Packages"
Microsoft Learn, "Python developer reference for Azure Functions"
Verified
2026-09-12 (v1)
Supporting passages from the sources
The requirements.txt can then be committed to version control
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt
Python dependencies installed during publish when using remote build.

03Show the Python in Excel runtime in a =PY cell and compare it with local PythonVerified

Python in Excel code runs in the Microsoft Cloud, and your local Python setup does not apply. Put a cell that shows the versions on the first sheet, and compare them when you re-check the same aggregation with local pandas.

Approach and steps
  1. Put a =PY cell on the first worksheet that returns sys.version and the pandas and numpy __version__ as a table.
  2. Read the table with xl("ledger[#All]", headers=True) and include the row count to confirm the range that was read.
  3. On local Python, log the same items with the runtime-logging script.
  4. Different versions can shift results through different defaults. Keep both version sets with the cross-check results.
  5. Python in Excel cannot read external web sites or files, so keep the data in a table in the workbook.
PythonRuns in the Microsoft Cloud
# =PY cell on the first sheet: show the runtime used for this workbook's Python results
import sys

import numpy as np
import pandas as pd

df = xl("ledger[#All]", headers=True)
pd.DataFrame({
    "item": ["python", "pandas", "numpy", "ledger rows"],
    "value": [sys.version.split()[0], pd.__version__, np.__version__, len(df)],
})
Example instruction for Copilot

Write =PY cell code for Python in Excel that returns a two-column DataFrame with the Python, pandas and numpy versions and the row count of the table read with xl("ledger[#All]", headers=True).

Caution

Python in Excel code has no network access, and libraries are limited to the Anaconda-provided set. Packages and settings from your own installation are not available. Calculations run in isolated containers in the cloud.

Availability
Requires a license and platform that support Python in Excel (see the Excel × Python page).
Requires
pandas 3.0.5
Tested
run locally with xl() replaced by sample data (2026-09-12, Python 3.12.10 (venv) / Windows 11, pandas 3.0.5)
Practical edition
Excel ── Get answers computed with Python-based analysis
Source
Microsoft Support, "Introduction to Python in Excel"
Microsoft Support, "Data security and Python in Excel"
Verified
2026-09-12 (v1)
Supporting passages from the sources
Python in Excel calculations run in the Microsoft Cloud with a standard version of the Python language.
any customizations you've made to that Python installation won't be reflected in Python in Excel calculations.
The Python code doesn't have network access.

04Run functions locally with Core Tools and read settings from local.settings.jsonVerified

Python functions can be started locally with Core Tools (func start) inside a virtual environment. Keep app settings in the Values section of local.settings.json and read them with os.getenv. You can also call the function directly before starting the host.

Approach and steps
  1. Create and activate a virtual environment, then scaffold with func init MyProjFolder --worker-runtime python --model V2.
  2. Put settings in the Values section of local.settings.json and read them in code with os.getenv("LEDGER_PATH", default).
  3. Running this script with python copies Values into environment variables and calls the function directly.
  4. Then start the host with func start and send requests to the http://localhost:7071/api/... URL it prints.
  5. Authorization is not enforced locally for HTTP endpoints. Check keys after deploying to Azure.
PythonRuns locally
"""function_app.py: settings come from app settings in Azure and from local.settings.json on your machine."""
import csv
import json
import os

import azure.functions as func

app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)


@app.route(route="open_requests", methods=["GET"])
def open_requests(req: func.HttpRequest) -> func.HttpResponse:
    open_states = set(os.getenv("OPEN_STATES", "受付,確認中").split(","))
    with open(os.getenv("LEDGER_PATH", "ledger.csv"), encoding="utf-8-sig", newline="") as f:
        rows = [r for r in csv.DictReader(f) if r["状態"] in open_states]
    dept = req.params.get("dept")
    if dept:
        rows = [r for r in rows if r["依頼部署"] == dept]
    body = {"count": len(rows), "ids": [r["依頼ID"] for r in rows]}
    return func.HttpResponse(json.dumps(body, ensure_ascii=False), mimetype="application/json")


def load_local_settings(path):
    """Copy the Values section into os.environ; values already set in the environment win."""
    with open(path, encoding="utf-8") as f:
        values = json.load(f).get("Values", {})
    for name, value in values.items():
        os.environ.setdefault(name, str(value))
    return sorted(values)


if __name__ == "__main__":
    # Quick check without Core Tools (azure-functions >= 1.21.0). Use `func start` for the real host.
    print("settings:", load_local_settings(os.getenv("LOCAL_SETTINGS", "samples/agents_local.settings.json")))
    req = func.HttpRequest(method="GET", url="/api/open_requests", body=b"", params={"dept": "広報部"})
    resp = open_requests.build().get_user_function()(req)
    print(resp.status_code, resp.get_body().decode("utf-8"))
Output(2026-09-12)
settings: ['AzureWebJobsStorage', 'FUNCTIONS_WORKER_RUNTIME', 'LEDGER_PATH', 'OPEN_STATES']
200 {"count": 14, "ids": ["R-2026-002", "R-2026-003", "R-2026-007", "R-2026-008", "R-2026-010", "R-2026-011", "R-2026-019", "R-2026-022", "R-2026-023", "R-2026-025", "R-2026-026", "R-2026-028", "R-2026-029", "R-2026-038"]}
Example instruction for Copilot

Change this Azure Functions Python v2 function to read its settings with os.getenv, and add a __main__ block for local checks that copies Values from local.settings.json into environment variables and then calls the function directly.

Caution

local.settings.json can contain secrets such as connection strings, so never push it to a remote repository. Values are not uploaded to Azure automatically when you publish. A direct call skips bindings and host settings, so finish with func start.

Availability
Azure Functions Core Tools and azure-functions (1.21.0 or later for direct calls). Checked with azure-functions 1.25.0.
Requires
azure-functions 1.25.0
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11, azure-functions 1.25.0)
Source
Microsoft Learn, "Develop Azure Functions Locally by using Core Tools"
Microsoft Learn, "Python developer reference for Azure Functions"
Verified
2026-09-12 (v1)
Supporting passages from the sources
For Python, you must run Core Tools commands in a virtual environment.
func init MyProjFolder --worker-runtime python --model V2
Access the variables directly in your code by using os.environ or os.getenv.
Because the local.settings.json may contain secrets, such as connection strings, you should never store it in a remote repository.
By default, local settings aren't migrated automatically when the project is published to Azure.
By default, authorization isn't enforced locally for HTTP endpoints.

05Check Functions plan time limits and the Python version before deployingVerified

Azure Functions plans differ in how long a function may run, and HTTP responses are cut off at 230 seconds. The Python version is also limited by plan. Check host.json functionTimeout and the planned version against values copied from the docs' tables.

Approach and steps
  1. Write functionTimeout in host.json as [d.]hh:mm:ss. A value of -1 means unbounded.
  2. The Consumption plan defaults to 5 minutes with a 10-minute maximum; Flex Consumption, Premium and Dedicated default to 30 minutes with no maximum (see the table notes).
  3. An HTTP-triggered function must respond within 230 seconds whatever the setting. For long work, return an acknowledgement.
  4. Python function apps run on Linux only. Generally available versions are 3.10 to 3.14.
  5. Python 3.12 is the last version for Linux Consumption apps, and you can't change the Python version on a Consumption plan.
  6. The tables change, so note the check date next to the constants and re-read the docs before each deployment.
PythonRuns locally
"""Check host.json functionTimeout and the target Python version against hosting plan limits."""
import json
import os
import re

# Copied from "Azure Functions Scale and Hosting" and "Supported Languages in Azure Functions" on 2026-09-12.
PLAN_MAX_MINUTES = {"consumption": 10, "flex": None, "premium": None, "dedicated": None}  # None = unbounded
HTTP_RESPONSE_LIMIT_SECONDS = 230
PYTHON_GA = ["3.10", "3.11", "3.12", "3.13", "3.14"]
LINUX_CONSUMPTION_LAST = "3.12"
TIMESPAN = re.compile(r"^(?:(\d+)\.)?(\d{1,2}):(\d{2}):(\d{2})$")


def to_minutes(value):
    text = str(value).strip()
    if text == "-1":
        return None
    m = TIMESPAN.match(text)
    if not m:
        raise ValueError(f"not a [d.]hh:mm:ss timespan: {value!r}")
    d, h, mi, s = (int(x or 0) for x in m.groups())
    if h > 23 or mi > 59 or s > 59:
        raise ValueError(f"out of range: {value!r}")
    return d * 1440 + h * 60 + mi + s / 60


def version_key(v):
    return tuple(int(p) for p in v.split("."))


def check(host, plan, python_version, http_seconds):
    notes = []
    minutes = to_minutes(host.get("functionTimeout", "-1"))
    limit = PLAN_MAX_MINUTES[plan]
    if limit is not None and (minutes is None or minutes > limit):
        notes.append(f"functionTimeout {host.get('functionTimeout')} exceeds the {plan} maximum of {limit} min")
    if http_seconds > HTTP_RESPONSE_LIMIT_SECONDS:
        notes.append(f"HTTP work of {http_seconds}s exceeds {HTTP_RESPONSE_LIMIT_SECONDS}s: reply first, finish later")
    if python_version not in PYTHON_GA:
        notes.append(f"Python {python_version} is not in the supported list")
    elif plan == "consumption" and version_key(python_version) > version_key(LINUX_CONSUMPTION_LAST):
        notes.append(f"Linux Consumption stops at Python {LINUX_CONSUMPTION_LAST}")
    return notes


if __name__ == "__main__":
    with open(os.environ.get("HOST_JSON", "samples/agents_host.json"), encoding="utf-8") as f:
        host = json.load(f)
    print("functionTimeout:", host.get("functionTimeout"), "=", to_minutes(host.get("functionTimeout", "-1")), "min")
    for plan, py, seconds in [("consumption", "3.13", 300), ("flex", "3.13", 120)]:
        found = check(host, plan, py, seconds)
        print(f"[{plan} / Python {py} / HTTP {seconds}s]", "ok" if not found else "")
        for note in found:
            print("  -", note)
Output(2026-09-12)
functionTimeout: 00:12:00 = 12.0 min
[consumption / Python 3.13 / HTTP 300s] 
  - functionTimeout 00:12:00 exceeds the consumption maximum of 10 min
  - HTTP work of 300s exceeds 230s: reply first, finish later
  - Linux Consumption stops at Python 3.12
[flex / Python 3.13 / HTTP 120s] ok
Example instruction for Copilot

Write a Python script that converts functionTimeout in host.json ([d.]hh:mm:ss) to minutes and checks it against plan limits, the 230-second HTTP limit and the planned Python version. Keep the limits in constants with a comment giving the date they were checked.

Caution

The table lists October 2026 as the expected end of support for Python 3.10. Hosting on Linux in a Consumption plan retires on 30 September 2028, with Flex Consumption as the recommended target. The script's values were copied on the date of writing and can change.

Availability
Checked against the Microsoft Learn tables on 2026-09-12.
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11)
Source
Microsoft Learn, "Azure Functions Scale and Hosting"
Microsoft Learn, "Supported Languages in Azure Functions"
Microsoft Learn, "host.json reference for Azure Functions 2.x"
Microsoft Learn, "Update Language Versions in Azure Functions"
Verified
2026-09-12 (v1)
Supporting passages from the sources
Regardless of the function app timeout setting, 230 seconds is the maximum amount of time that an HTTP triggered function can take to respond to a request.
Python 3.12 is the last Python version supported for Linux Consumption plan apps.
You can't change the Python version when your function app runs in a Consumption plan.
The format of the timespan string needs to follow the syntax [d.]hh:mm:ss
A value of -1 indicates unbounded execution, but keeping a fixed upper bound is recommended.
Linux is the only supported operating system for the Python runtime stack.

06Check library versions in the first cell of a Fabric notebookVerified

In a Fabric notebook, libraries installed with %pip install last only for the current session. To use the same versions in scheduled runs and pipelines, pin them in an environment published in Full mode. Check the versions in the first cell and stop if they differ.

Approach and steps
  1. Register libraries for shared or scheduled work in an Environment and publish it in Full mode.
  2. Use %pip install only while experimenting; it does not survive the session.
  3. In the first cell, read versions with importlib.metadata and raise RuntimeError if they differ from the expected ones.
  4. Leave the result JSON in the cell output so you can later see which versions ran.
  5. Scheduled runs use the identity of whoever created or last updated the schedule, and pipeline runs use the pipeline's last modifier. Check data permissions for that person.
PythonRuns in the Microsoft Cloud
# First cell of a Fabric notebook: stop early if the session's libraries differ from the pinned set
import importlib.metadata as md
import json
import sys

EXPECTED = {"pandas": "3.0.5", "openpyxl": "3.1.5"}  # keep in sync with the environment (Full mode)


def library_report(expected):
    report = {"python": sys.version.split()[0], "libraries": {}, "mismatch": []}
    for name, wanted in expected.items():
        try:
            have = md.version(name)
        except md.PackageNotFoundError:
            have = None
        report["libraries"][name] = have
        if have != wanted:
            report["mismatch"].append(f"{name}: want {wanted}, have {have}")
    return report


report = library_report(EXPECTED)
print(json.dumps(report, ensure_ascii=False))
if report["mismatch"]:
    raise RuntimeError("library mismatch: " + "; ".join(report["mismatch"]))
Output(2026-09-12)
{"python": "3.12.10", "libraries": {"pandas": "3.0.5", "openpyxl": "3.1.5"}, "mismatch": []}
Example instruction for Copilot

Write Python for the first cell of a Fabric notebook. Keep the expected library versions in a dict, compare them with the versions from importlib.metadata, print the result as JSON and raise RuntimeError on any mismatch.

Caution

The identity that runs a notebook differs for manual, pipeline and scheduled runs. Review the version history before running a notebook someone else edited. Update the expected-version dict whenever you change the environment.

Availability
A Microsoft Fabric workspace and notebook. Full and Quick modes are described in the Fabric environment docs.
Requires
pandas 3.0.5, openpyxl 3.1.5
Tested
run locally with xl() replaced by sample data (2026-09-12, Python 3.12.10 (venv) / Windows 11, pandas 3.0.5, openpyxl 3.1.5)
Source
Microsoft Learn, "How to use notebooks - Microsoft Fabric"
Verified
2026-09-12 (v1)
Supporting passages from the sources
libraries installed through inline commands (such as %pip install or install.packages()) are scoped to the current notebook session.
Full mode for reproducibility: Use Full mode when you need consistent library versions across collaborators, scheduled runs, or pipeline jobs.
Scheduler: Execution is triggered from a scheduled run. The notebook runs under the identity of the user who created or last updated the schedule.
The notebook runs under the identity of the pipeline's last modified user