Python Checks

python-checks@1.0.0 Template

Install with uv, Poetry or pip and run lint, type-check and test commands in order.

Start from it

flightstack template use python-checks

What it does

Detects the installer your repo already uses (uv, Poetry or pip) from what it commits, installs into an isolated environment, then runs each command in order and stops at the first failure.

Inputs

commands array required
Commands to run inside the project environment, in order, e.g. "ruff check .", "mypy src", "pytest -q".
workingDir string default .
Directory holding pyproject.toml or requirements.txt.
installer auto | uv | poetry | pip default auto
auto uses uv when uv.lock is committed, Poetry when poetry.lock is, and pip otherwise.
extras string
Extras or dependency groups to install, comma-separated, e.g. "dev".
requirementsFile string default requirements.txt
pip only - requirements file to install when there is no pyproject.toml.
pythonVersion string
Fail early unless python3 --version reports this prefix, e.g. "3.12".
env object
Extra environment variables for the commands.

Definition

name: Python Checks
description: >-
  Install a Python project with the tool it already uses (uv, Poetry or pip)
  and run lint, type-check and test commands in order.

triggers:
  - type: push
    branches: [main]
  - type: pullRequest

jobs:
  checks:
    name: Lint and test
    type: plugin/python-checks
    with:
      commands:
        - ruff check .
        - pytest

plugin:
  name: python-checks
  displayName: Python Checks
  description: >-
    Detect uv, Poetry or pip from the files the repo commits, install into an
    isolated environment, then run each command in order and stop at the
    first failure.
  category: quality

  supportedPlatforms:
    - linux
    - macos
    - windows

  parameters:
    type: object
    properties:
      commands:
        type: array
        description: >-
          Commands to run inside the project environment, in order, e.g.
          "ruff check .", "mypy src", "pytest -q".
      workingDir:
        type: string
        default: "."
        description: Directory holding pyproject.toml or requirements.txt.
      installer:
        type: string
        enum: [auto, uv, poetry, pip]
        default: auto
        description: >-
          `auto` uses uv when uv.lock is committed, Poetry when poetry.lock is,
          and pip otherwise.
      extras:
        type: string
        default: ""
        description: >-
          Extras or dependency groups to install, comma-separated, e.g. "dev".
      requirementsFile:
        type: string
        default: requirements.txt
        description: pip only - requirements file to install when there is no pyproject.toml.
      pythonVersion:
        type: string
        default: ""
        description: >-
          Fail early unless `python3 --version` reports this prefix, e.g. "3.12".
      env:
        type: object
        description: Extra environment variables for the commands.
    required: [commands]

  script: |
    set -euo pipefail
    cd "{{workingDir}}"

    py="$(command -v python3 || command -v python || true)"
    if [ -z "$py" ]; then
      echo "Python is not installed on this agent." >&2
      exit 1
    fi
    version="$("$py" --version 2>&1 | awk '{print $2}')"
    echo "python $version"
    wanted="{{pythonVersion}}"
    if [ -n "$wanted" ] && [ "${version#"$wanted"}" = "$version" ]; then
      echo "This job wants Python $wanted but the agent has $version." >&2
      exit 1
    fi

    installer="{{installer}}"
    if [ "$installer" = "auto" ]; then
      if [ -f uv.lock ]; then installer=uv
      elif [ -f poetry.lock ]; then installer=poetry
      else installer=pip
      fi
    fi
    echo "installer: $installer"

    extras="{{extras}}"
    {{#each env}}
    export {{@key}}="{{this}}"
    {{/each}}

    case "$installer" in
      uv)
        command -v uv >/dev/null 2>&1 || "$py" -m pip install --quiet uv
        groups=()
        for g in $(echo "$extras" | tr ',' ' '); do
          groups+=(--group "$g")
        done
        uv sync --frozen ${groups[@]+"${groups[@]}"}
        run() { uv run --frozen -- bash -c "$1"; }
        ;;
      poetry)
        command -v poetry >/dev/null 2>&1 || "$py" -m pip install --quiet poetry
        if [ -n "$extras" ]; then
          poetry install --no-interaction --with "$extras"
        else
          poetry install --no-interaction
        fi
        run() { poetry run bash -c "$1"; }
        ;;
      pip)
        "$py" -m venv .fs-venv
        . .fs-venv/bin/activate 2>/dev/null || . .fs-venv/Scripts/activate
        python -m pip install --quiet --upgrade pip
        if [ -f pyproject.toml ]; then
          if [ -n "$extras" ]; then pip install -e ".[$extras]"; else pip install -e .; fi
        elif [ -f "{{requirementsFile}}" ]; then
          pip install -r "{{requirementsFile}}"
        fi
        run() { bash -c "$1"; }
        ;;
    esac

    {{#each commands}}
    echo "--- {{this}}"
    run "{{this}}"
    {{/each}}