conda 26.9.4 pixi 0.82.1 virtualenv 21.14.12 pipx 1.18.7 pipenv 2026.10.2 PDM 2.29.4 Pex 2.105.1 cibuildwheel 4.3.5 manylinux and musllinux images 2026.10.26-1 pypa/gh-action-pypi-publish 1.14.3 python-build-standalone 20261026 Hatch 1.18.2 (2) pyenv 2.8.

Apurva Pathak 960 Reputation points
2026-11-03T12:47:53.27+00:00

Note- AI Drafted question because there was a lot to cover.

Hi,

I have a Python application running as a Linux Azure Function App and I’m trying to understand the behaviour I’m seeing with Python packages after multiple deployments.

Current setup:

  • The application is a Linux Azure Function App.
  • Deployment is performed through Azure DevOps using an AzureFunctionApp task.
  • The application is deployed using a ZIP artifact.
  • Python dependencies are located under:

/home/site/wwwroot/.python_packages/lib/site-packages/

  • The ZIP artifact generated by the pipeline is always clean.
  • However, after multiple deployments, older package metadata appears to remain in site-packages.

For example, for aiohttp I currently see:

  • aiohttp/
  • aiohttp-3.10.11.dist-info/
  • aiohttp-3.13.5.dist-info/
  • aiohttp-3.14.5.dist-info/

There is only one actual package directory:

  • aiohttp/

but there are multiple .dist-info directories corresponding to different versions from previous deployments.

The older .dist-info directories do NOT exist in the current deployment ZIP, so they appear to have been left behind by previous deployments.

My questions are:

  • When Python executes:

import aiohttp

which version of aiohttp is actually loaded?

  • Given that there is only one aiohttp/ directory, does Python simply import the code from that directory regardless of how many aiohttp-x.x.x.dist-info directories exist?
  • Are the .dist-info directories involved in Python’s import resolution, or are they only package metadata?
  • Can having multiple stale .dist-info directories cause runtime issues?

For example, could they affect:

importlib.metadata.version(“aiohttp”)

pip

dependency/version checks

libraries which query installed package metadata?

  • Could the application ever identify the wrong aiohttp version because multiple .dist-info directories exist?
  • Is it safe to completely remove:

/home/site/wwwroot/.python_packages

and then immediately redeploy the clean application artifact?

  • Would deleting the complete .python_packages directory be preferable to manually identifying and removing individual stale package/version directories?
  • For preventing this in future deployments, would running the Azure Function from an immutable deployment package be preferable to repeatedly extracting/overlaying deployment files into wwwroot?

I’m also planning to verify what the running application is actually importing using:

python -c “import aiohttp; print(‘Version:’, aiohttp.version); print(‘Loaded from:’, aiohttp.file)”

I’m mainly trying to understand whether these multiple .dist-info directories are simply stale metadata that should be cleaned up, or whether they could actually cause Python to use/report an incorrect package version while

Azure Functions
Azure Functions

An Azure service that provides an event-driven serverless compute platform.


1 answer

Sort by: Most helpful
  1. AI answer

    2026-11-03T13:55:21.2033333+00:00

    Python imports the single discoverable aiohttp package directory; the stale .dist-info directories do not select which code is imported, but they can make metadata-based version reporting ambiguous and should be removed by rebuilding the complete .python_packages tree and redeploying it.

    What Python loads

    import aiohttp follows Python’s normal import resolution over sys.path. It loads the first matching module or package—here, the relevant aiohttp/__init__.py—without using aiohttp-*.dist-info to choose a version.^1^

    The .dist-info directories describe installed distributions. Tools such as importlib.metadata discover and read them, but distribution names and import-package names do not necessarily have a one-to-one relationship.^2^ They commonly contain the version, dependency declarations, installed-file records, and entry points.^3^

    Consequently:

    • The code under aiohttp/ determines the imported implementation.
    • aiohttp.__file__ identifies the code location.
    • aiohttp.__version__, if exposed by that release, is supplied by the imported code.
    • importlib.metadata.version("aiohttp") reads distribution metadata and does not prove which implementation was imported.
    • Duplicate metadata is not a supported multi-version installation model. Metadata discovery should not be assumed to select the newest version; the result can depend on discovery order.^4^
    • Therefore, pip, dependency checks, entry-point discovery, or libraries using package metadata can report or act on the wrong stale record even though import aiohttp loads the current code.

    Use straight quotes and double underscores when checking the running environment:

    python - <<'PY'
    import aiohttp
    from importlib import metadata
    
    print("Module file:", aiohttp.__file__)
    print("Code version:", getattr(aiohttp, "__version__", "<not exposed>"))
    print("Metadata version:", metadata.version("aiohttp"))
    
    for dist in metadata.distributions():
        if (dist.metadata.get("Name") or "").lower() == "aiohttp":
            print("Metadata:", dist.version, dist._path)
    PY
    

    Run this inside the Function App environment, because executing it on the pipeline agent examines the agent’s Python installation instead.

    Clean recovery

    Deleting the complete .python_packages directory and immediately deploying a clean artifact is preferable to deleting selected .dist-info directories. It avoids leaving a mixture of package code, metadata, compiled extensions, and dependency files from different builds.

    1. Stop the Function App or otherwise prevent requests during cleanup.
    2. Delete /home/site/wwwroot/.python_packages.
    3. Deploy the complete clean ZIP containing the rebuilt .python_packages/lib/site-packages.
    4. Restart the app and run the diagnostic above.
    5. Confirm that the module path is under the expected deployment and that only one aiohttp distribution is listed.

    Risk: deleting .python_packages makes third-party imports unavailable until deployment completes. A deployment slot is safer where the hosting plan supports one.

    For a local build, recreate the folder rather than installing over an existing target:

    rm -rf .python_packages
    python -m pip install \
      --target="./.python_packages/lib/site-packages" \
      --requirement requirements.txt
    

    Azure Functions requires locally built dependencies in that directory, and the build environment should match the Linux production environment and Python version.^5^

    ZIP deployment deletes or updates files tracked as part of an earlier deployment but can retain other files and directories. That behavior can explain why files created outside the tracked deployment set survive.^6^

    Preventing recurrence

    Prefer immutable Run From Package where your hosting plan supports it. It mounts the ZIP as read-only wwwroot, so the running tree exactly reflects the package instead of accumulating extracted files.^6^

    With AzureFunctionApp@2, explicitly select it rather than relying on auto-detection:

    - task: AzureFunctionApp@2
      inputs:
        azureSubscription: 'service-connection'
        appType: 'functionAppLinux'
        appName: 'function-app-name'
        package: '$(Pipeline.Workspace)/drop/function.zip'
        deploymentMethod: 'runFromPackage'
    

    The task supports runFromPackage, which mounts the package instead of extracting it.^7^

    Plan-specific constraints matter:

    • Flex Consumption: already runs from a package; do not set WEBSITE_RUN_FROM_PACKAGE.
    • Premium or Dedicated: set WEBSITE_RUN_FROM_PACKAGE=1.
    • Linux Consumption: 1 is unsupported; locally built packages require an external package URL. The Azure DevOps task also states that its deploymentMethod selection is unsupported for Linux Consumption.
    • Do not combine Run From Package with remote build; remote build instead requires its documented build settings.^6^

    References

    1. importlib — The implementation of import — Python 3.15.2 documentation
    2. importlib.metadata – Accessing package metadata — Python 3.15.2 ...
    3. Recording installed projects - Python Packaging User Guide
    4. Migration guide - importlib_metadata 10.1.0 documentation
    5. Build your Python Azure Functions apps
    6. Package-based deployment for Azure Functions (dedicated-plan)
    7. AzureFunctionApp@2 - Azure Functions Deploy v2 task
    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.