name: Python UV Quality (with DB) 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: "" test_env: description: > Multiline env vars for tests, 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" alembic_command: type: string default: "" postgres_image: type: string default: "postgres:16" postgres_user: type: string default: "postgres" postgres_password: type: string default: "postgres" postgres_db: type: string default: "test_db" postgres_health_cmd: type: string default: "pg_isready -U postgres" postgres_health_interval: type: string default: "10s" postgres_health_timeout: type: string default: "5s" postgres_health_retries: type: string default: "5" redis_image: type: string default: "redis:7-alpine" redis_health_cmd: type: string default: "redis-cli ping" redis_health_interval: type: string default: "5s" redis_health_timeout: type: string default: "5s" redis_health_retries: type: string default: "20" secrets: ssh_private_key: required: false ssh_known_hosts: required: false jobs: quality: runs-on: ubuntu-latest services: postgres: image: ${{ inputs.postgres_image }} env: POSTGRES_USER: ${{ inputs.postgres_user }} POSTGRES_PASSWORD: ${{ inputs.postgres_password }} POSTGRES_DB: ${{ inputs.postgres_db }} ports: - 5432 options: >- --health-cmd="${{ inputs.postgres_health_cmd }}" --health-interval=${{ inputs.postgres_health_interval }} --health-timeout=${{ inputs.postgres_health_timeout }} --health-retries=${{ inputs.postgres_health_retries }} redis: image: ${{ inputs.redis_image }} options: >- --health-cmd="${{ inputs.redis_health_cmd }}" --health-interval=${{ inputs.redis_health_interval }} --health-timeout=${{ inputs.redis_health_timeout }} --health-retries=${{ inputs.redis_health_retries }} permissions: contents: read defaults: run: working-directory: ${{ inputs.working_directory }} steps: - name: Checkout uses: actions/checkout@v4 - name: Configure DB env run: | if [ "${ACT:-}" = "true" ] || [ "${GITEA_ACTIONS:-}" = "true" ]; then host="postgres" port="5432" else host="127.0.0.1" port="${{ job.services.postgres.ports['5432'] }}" fi echo "POSTGRES_HOST=$host" >> "$GITHUB_ENV" echo "POSTGRES_PORT=$port" >> "$GITHUB_ENV" echo "POSTGRES_USER=${{ inputs.postgres_user }}" >> "$GITHUB_ENV" echo "POSTGRES_PASSWORD=${{ inputs.postgres_password }}" >> "$GITHUB_ENV" echo "POSTGRES_DB=${{ inputs.postgres_db }}" >> "$GITHUB_ENV" echo "DATABASE_URL=postgres://${{ inputs.postgres_user }}:${{ inputs.postgres_password }}@$host:$port/${{ inputs.postgres_db }}" >> "$GITHUB_ENV" - 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: Load test env vars if: ${{ inputs.test_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.test_env }}" - name: Wait for postgres run: | python - <<'PY' import os import socket import time host = os.getenv("POSTGRES_HOST", "127.0.0.1") port = int(os.getenv("POSTGRES_PORT", "5432")) deadline = time.time() + 60 while True: try: with socket.create_connection((host, port), timeout=2): print(f"Postgres reachable at {host}:{port}") break except OSError: if time.time() > deadline: raise time.sleep(1) PY - 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: Alembic upgrade head (smoke test) if: ${{ inputs.alembic_command != '' }} run: ${{ inputs.alembic_command }} - name: Run tests if: ${{ inputs.test_command != '' }} run: ${{ inputs.test_command }}