name: Python UV Quality on: workflow_call: inputs: python_version: type: string default: "3.13" uv_version: type: string default: "latest" working_directory: type: string default: "." cache_dependency_path: type: string default: "uv.lock" env: description: > Multiline env vars, one per line: KEY=VALUE required: false type: string default: "" uv_sync_args: type: string default: "--frozen --dev" format_command: type: string default: "uv run ruff format --check ." lint_command: type: string default: "uv run ruff check ." typecheck_command: type: string default: "uv run mypy ." test_command: type: string default: "uv run pytest" enable_db: description: Enable a local database container for tests. type: boolean default: false db_type: description: Database type to start when enable_db is true. type: string default: "postgres" secrets: ssh_private_key: required: false ssh_known_hosts: required: false jobs: quality: runs-on: ubuntu-latest permissions: contents: read defaults: run: working-directory: ${{ inputs.working_directory }} steps: - name: Checkout uses: actions/checkout@v4 - name: Load env vars if: ${{ inputs.env != '' }} run: | while IFS= read -r line; do [ -z "$line" ] && continue case "$line" in \#*) continue;; esac if [[ "$line" != *=* ]]; then echo "Invalid env line: $line" >&2 exit 1 fi echo "$line" >> "$GITHUB_ENV" done <<< "${{ inputs.env }}" - name: Start ssh-agent if: ${{ secrets.ssh_private_key != '' }} uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: ${{ secrets.ssh_private_key }} - name: Add SSH known hosts if: ${{ secrets.ssh_known_hosts != '' }} run: | mkdir -p ~/.ssh printf '%s\n' "${{ secrets.ssh_known_hosts }}" >> ~/.ssh/known_hosts chmod 644 ~/.ssh/known_hosts - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ inputs.python_version }} - name: Install uv env: UV_VERSION: ${{ inputs.uv_version }} run: | python -m pip install --upgrade pip if [ -z "$UV_VERSION" ] || [ "$UV_VERSION" = "latest" ]; then python -m pip install uv else python -m pip install "uv==$UV_VERSION" fi - name: Cache uv downloads uses: actions/cache@v4 with: path: ~/.cache/uv key: uv-${{ runner.os }}-${{ hashFiles(inputs.cache_dependency_path) }} restore-keys: | uv-${{ runner.os }}- - name: Sync dependencies run: uv sync ${{ inputs.uv_sync_args }} - name: Run format check if: ${{ inputs.format_command != '' }} run: ${{ inputs.format_command }} - name: Run lint if: ${{ inputs.lint_command != '' }} run: ${{ inputs.lint_command }} - name: Run typecheck if: ${{ inputs.typecheck_command != '' }} run: ${{ inputs.typecheck_command }} - name: Cleanup existing database containers if: ${{ inputs.enable_db }} run: | existing_ids="$(docker ps -aq --filter "name=^ci-postgres")" if [ -n "$existing_ids" ]; then docker rm -f $existing_ids >/dev/null 2>&1 || true fi - name: Start database if: ${{ inputs.enable_db }} run: | set -euo pipefail db_type="${{ inputs.db_type }}" case "$db_type" in postgres|postgresql) run_id="${GITHUB_RUN_ID:-local}" attempt="${GITHUB_RUN_ATTEMPT:-0}" container_name="ci-postgres-${run_id}-${attempt}" echo "DB_CONTAINER_NAME=$container_name" >> "$GITHUB_ENV" docker run -d --name "$container_name" \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres \ -e POSTGRES_DB=test_db \ -p 5432:5432 \ --health-cmd="pg_isready -U postgres" \ --health-interval=10s \ --health-timeout=5s \ --health-retries=5 \ postgres:16 for i in {1..30}; do health="$(docker inspect --format '{{.State.Health.Status}}' "$container_name" 2>/dev/null || true)" case "$health" in healthy) break ;; unhealthy) echo "Postgres reported unhealthy." >&2 docker logs "$container_name" || true exit 1 ;; "") echo "Postgres health status unavailable." >&2 docker logs "$container_name" || true exit 1 ;; esac sleep 1 done if [ "${health:-}" != "healthy" ]; then echo "Postgres did not become healthy in time." >&2 docker logs "$container_name" || true exit 1 fi if [ -z "${DATABASE_URL:-}" ]; then echo "DATABASE_URL=postgresql://postgres:postgres@localhost:5432/test_db" >> "$GITHUB_ENV" fi ;; *) echo "Unsupported db_type: $db_type" >&2 exit 1 ;; esac - name: Run tests if: ${{ inputs.test_command != '' }} run: ${{ inputs.test_command }} - name: Cleanup database if: ${{ always() && inputs.enable_db }} run: | if [ -n "${DB_CONTAINER_NAME:-}" ]; then docker rm -f "$DB_CONTAINER_NAME" >/dev/null 2>&1 || true fi