Python package #3604
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Python package | |
| on: | |
| workflow_call: | |
| # This workflow can be called by other workflows (like intelligent-testing.yml) | |
| inputs: | |
| concurrency_key: | |
| required: false | |
| type: string | |
| matrix_json: | |
| required: true | |
| type: string | |
| pytest_paths_json: | |
| required: false | |
| type: string | |
| default: "[]" | |
| functional_scripts_json: | |
| required: false | |
| type: string | |
| default: "[]" | |
| full_suite: | |
| required: false | |
| type: boolean | |
| default: false | |
| workflow_dispatch: | |
| inputs: | |
| concurrency_key: | |
| description: "Optional stable key to dedupe manual runs (for example: pr-123)" | |
| required: false | |
| type: string | |
| matrix_json: | |
| description: "JSON matrix definition" | |
| required: true | |
| type: string | |
| default: '{"include":[{"os":"ubuntu-latest","python-version":"3.12","extras": ""}]}' | |
| pytest_paths_json: | |
| description: "JSON array of pytest paths" | |
| required: false | |
| type: string | |
| default: "[]" | |
| functional_scripts_json: | |
| description: "JSON array of functional scripts" | |
| required: false | |
| type: string | |
| default: "[]" | |
| full_suite: | |
| description: "Run full pytest and default functional suite" | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| build: | |
| runs-on: ${{ matrix.os }} | |
| # Cancel outdated runs on the same OS and Python version when new commits are pushed | |
| # Only cancels on PRs. | |
| # Use a stable concurrency key only when one is explicitly provided | |
| # (e.g. PR/workflow_call/manual dedupe). Otherwise fall back to github.run_id | |
| # so pushes to main never cancel each other. | |
| concurrency: | |
| group: >- | |
| tests-${{ github.workflow }}- | |
| ${{ github.event_name == 'workflow_call' && inputs.concurrency_key | |
| || github.event_name == 'workflow_dispatch' && inputs.concurrency_key | |
| || github.run_id }}- | |
| ${{ matrix.os }}-${{ matrix.python-version }} | |
| cancel-in-progress: true | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(inputs.matrix_json) }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Free up disk space | |
| if: runner.os == 'Linux' | |
| run: | | |
| echo "Disk space before cleanup:" | |
| df -h | |
| # Remove unnecessary software to free up disk space | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf /usr/local/lib/android | |
| sudo rm -rf /opt/ghc | |
| sudo rm -rf /opt/hostedtoolcache/CodeQL | |
| sudo docker image prune --all --force | |
| echo "Disk space after cleanup:" | |
| df -h | |
| - name: Set up Python | |
| uses: conda-incubator/setup-miniconda@v3 | |
| with: | |
| channels: conda-forge | |
| channel-priority: strict | |
| python-version: ${{ matrix.python-version }} | |
| activate-environment: test | |
| auto-activate-base: false | |
| - name: Install dependencies | |
| shell: bash -el {0} | |
| run: | | |
| conda run -n test python -m pip install --upgrade pip setuptools wheel | |
| conda run -n test python -m pip install --no-cache-dir -e ".${{ matrix.extras }}" --group dev | |
| - name: Install ffmpeg (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| if [ "$RUNNER_OS" == "Linux" ]; then | |
| sudo apt-get update | |
| sudo apt-get install -y ffmpeg | |
| elif [ "$RUNNER_OS" == "macOS" ]; then | |
| brew install ffmpeg || true | |
| fi | |
| - name: Install ffmpeg (Windows, pinned monthly BtbN build) | |
| # NOTE: The pinned version should be retained for ~2 years. This WILL fail if the BtbN release is removed, | |
| # so if you are two years in the future and this step fails, please check the builds. Thanks. | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| FFMPEG_TAG: autobuild-2026-03-31-13-11 | |
| FFMPEG_ASSET: ffmpeg-N-123777-g53537f6cf5-win64-gpl-shared.zip | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $tag = $env:FFMPEG_TAG | |
| $asset = $env:FFMPEG_ASSET | |
| $baseUrl = "https://github.com/BtbN/FFmpeg-Builds/releases/download/$tag" | |
| $url = "$baseUrl/$asset" | |
| $checksumsUrl = "$baseUrl/checksums.sha256" | |
| $tmpRoot = Join-Path $env:RUNNER_TEMP "ffmpeg-install" | |
| $zip = Join-Path $tmpRoot $asset | |
| $checksums = Join-Path $tmpRoot "checksums.sha256" | |
| $dest = Join-Path $tmpRoot "ffmpeg" | |
| if (Test-Path -LiteralPath $tmpRoot) { | |
| Remove-Item -LiteralPath $tmpRoot -Recurse -Force | |
| } | |
| New-Item -ItemType Directory -Path $tmpRoot | Out-Null | |
| Invoke-WebRequest -Uri $url -OutFile $zip | |
| Invoke-WebRequest -Uri $checksumsUrl -OutFile $checksums | |
| $expected = Get-Content -LiteralPath $checksums | | |
| ForEach-Object { | |
| if ($_ -match '^(?<sha>[0-9A-Fa-f]{64})\s+\*?(?<name>.+)$' -and $matches.name.Trim() -eq $asset) { | |
| $matches.sha.ToLowerInvariant() | |
| } | |
| } | | |
| Select-Object -First 1 | |
| if (-not $expected) { | |
| throw "Could not find checksum for $asset in $checksums" | |
| } | |
| $actual = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant() | |
| if ($actual -ne $expected) { | |
| throw "FFmpeg checksum mismatch. Expected $expected but got $actual" | |
| } | |
| Expand-Archive -LiteralPath $zip -DestinationPath $dest -Force | |
| $ffdir = Get-ChildItem -LiteralPath $dest -Directory | Select-Object -First 1 | |
| if (-not $ffdir) { | |
| throw "Could not find extracted FFmpeg directory." | |
| } | |
| $binDir = Join-Path $ffdir.FullName "bin" | |
| $binDir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Verify ffmpeg/ffprobe available | |
| shell: bash -el {0} | |
| run: | | |
| set -e | |
| ffmpeg -version | |
| ffprobe -version | |
| - name: Run pytest | |
| shell: bash -el {0} | |
| env: | |
| FULL_SUITE: ${{ inputs.full_suite }} | |
| PYTEST_PATHS_JSON: ${{ inputs.pytest_paths_json }} | |
| run: | | |
| python - << 'PY' | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| full_suite = os.environ["FULL_SUITE"].lower() == "true" | |
| if full_suite: | |
| cmd = [sys.executable, "-m", "pytest"] | |
| print("Running full pytest suite") | |
| else: | |
| paths = json.loads(os.environ.get("PYTEST_PATHS_JSON", "[]")) | |
| if not paths: | |
| print("No pytest paths selected; skipping pytest.") | |
| raise SystemExit(0) | |
| cmd = [sys.executable, "-m", "pytest", *paths] | |
| print("Running targeted pytest:", " ".join(paths)) | |
| raise SystemExit(subprocess.call(cmd)) | |
| PY | |
| - name: Run functional scripts | |
| shell: bash -el {0} | |
| env: | |
| FULL_SUITE: ${{ inputs.full_suite }} | |
| FUNCTIONAL_SCRIPTS_JSON: ${{ inputs.functional_scripts_json }} | |
| run: | | |
| python - << 'PY' | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| def is_windows_python_3_11_or_greater() -> bool: | |
| """Aligned with pyproject: .[tf*] omits TensorFlow on Windows for Python 3.11+.""" | |
| return sys.platform == "win32" and sys.version_info >= (3, 11) | |
| full_suite = os.environ["FULL_SUITE"].lower() == "true" | |
| if full_suite: | |
| scripts = [ | |
| "examples/testscript_tensorflow_single_animal.py", | |
| "examples/testscript_tensorflow_multi_animal.py", | |
| "examples/testscript_pytorch_single_animal.py", | |
| "examples/testscript_pytorch_multi_animal.py", | |
| ] | |
| else: | |
| scripts = json.loads(os.environ.get("FUNCTIONAL_SCRIPTS_JSON", "[]")) | |
| if not scripts: | |
| print("No functional scripts selected; skipping functional tests.") | |
| raise SystemExit(0) | |
| for script in scripts: | |
| if "tensorflow" in script and is_windows_python_3_11_or_greater(): | |
| ver = f"{sys.version_info.major}.{sys.version_info.minor}" | |
| print( | |
| f"Skipping TensorFlow example on Windows {ver} (no TF in .[tf*] for 3.11+): {script}" | |
| ) | |
| continue | |
| print("Running:", script) | |
| rc = subprocess.call([sys.executable, script]) | |
| if rc != 0: | |
| raise SystemExit(rc) | |
| PY |