Migrate Python tooling to UV and Ty #5
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: backend | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: main | |
| paths: | |
| - "src/app/**" | |
| - "src/Dockerfile" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| - "docker/*" | |
| - "Makefile" | |
| - ".env.example" | |
| pull_request: | |
| branches: main | |
| paths: | |
| - "src/app/**" | |
| - "src/Dockerfile" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| - "docker/*" | |
| - "Makefile" | |
| - ".env.example" | |
| env: | |
| UV_VERSION: "0.11.14" | |
| PYTHON_VERSION: "3.11" | |
| jobs: | |
| build: | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| architecture: x64 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| activate-environment: true | |
| - uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/uv | |
| key: uv-${{ runner.os }}-backend-${{ hashFiles('uv.lock') }} | |
| - name: Install deps | |
| run: | | |
| make install-backend | |
| - name: Build docker image | |
| run: make build-backend | |
| start: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| - name: Load images | |
| run: | | |
| make build-backend | |
| docker compose -f docker-compose.dev.yml pull db localstack | |
| docker image ls -a | |
| - name: Start docker | |
| id: start-docker | |
| continue-on-error: true | |
| run: docker compose -f docker-compose.dev.yml up -d backend --wait | |
| - name: Generate debug logs | |
| if: steps.start-docker.outcome == 'failure' | |
| run: | | |
| docker compose -f docker-compose.dev.yml logs backend --tail=100 > build-logs.txt | |
| cat build-logs.txt | |
| - uses: actions/upload-artifact@v4 | |
| if: steps.start-docker.outcome == 'failure' | |
| with: | |
| name: build-docker-logs | |
| path: build-logs.txt | |
| - name: Exit the workflow | |
| if: steps.start-docker.outcome == 'failure' | |
| run: | | |
| echo "Docker startup failed. Logs saved as artifact build-docker-logs" | |
| exit 1 | |
| ruff: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| architecture: x64 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| - name: Run ruff | |
| run: | | |
| make install-quality | |
| uv run --group quality ruff --version | |
| make ruff-check | |
| ty: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| architecture: x64 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| - name: Run ty | |
| run: | | |
| make install-quality | |
| uv run --group quality ty --version | |
| make typing-check | |
| prek-hooks: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| architecture: x64 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| - name: Run prek hooks | |
| run: | | |
| make install-quality | |
| git checkout -b temp | |
| uv run prek --version | |
| make precommit | |
| deps-sync: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| architecture: x64 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| - name: Run dependency sync checker | |
| run: make deps-check | |
| tests: | |
| needs: start | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| architecture: x64 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| - name: Load images & start containers | |
| run: UV_GROUPS="server test" docker compose -f docker-compose.dev.yml up -d backend --wait --build | |
| - name: Run pytest | |
| run: | | |
| docker compose -f docker-compose.dev.yml exec -T backend pytest --cov=app --cov-report xml tests/ | |
| docker compose -f docker-compose.dev.yml cp backend:/app/coverage.xml ./coverage-backend.xml | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| flags: backend | |
| fail_ci_if_error: true | |
| migrations: | |
| needs: start | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| - name: Load images & start containers | |
| run: docker compose -f docker-compose.dev.yml up -d db localstack --wait | |
| - name: Run alembic migrations | |
| run: | | |
| docker compose -f docker-compose.dev.yml run --rm backend sh -c " \ | |
| echo '--- Upgrading to head ---' && \ | |
| alembic upgrade head && \ | |
| echo '--- Checking current revision ---' && \ | |
| alembic current && \ | |
| echo '--- Showing history ---' && \ | |
| alembic history --verbose && \ | |
| echo '--- Downgrading one revision ---' && \ | |
| alembic downgrade -1 && \ | |
| echo '--- Upgrading to head ---' && \ | |
| alembic upgrade head && \ | |
| echo '--- Migration cycle successful ---' \ | |
| " | |
| publish-image: | |
| needs: [build, tests, migrations] | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| packages: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: ${{ env.UV_VERSION }} | |
| - name: Load images | |
| run: | | |
| make build-backend | |
| docker image ls -a | |
| - name: Login to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Push to GitHub container registry | |
| run: docker push ghcr.io/${{ github.repository }}:latest | |
| deploy: | |
| needs: publish-image | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: appleboy/scp-action@v1.0.0 | |
| with: | |
| host: ${{ secrets.SSH_PROD_HOST }} | |
| username: ${{ secrets.SSH_PROD_USER }} | |
| key: ${{ secrets.SSH_DEPLOY_KEY }} | |
| source: "docker-compose.prod.yml" | |
| target: ./ | |
| - uses: appleboy/ssh-action@v1 | |
| with: | |
| host: ${{ secrets.SSH_PROD_HOST }} | |
| username: ${{ secrets.SSH_PROD_USER }} | |
| key: ${{ secrets.SSH_DEPLOY_KEY }} | |
| script: | | |
| # Ensure we have max disk space | |
| docker rmi -f $(docker images -f "dangling=true" -q) | |
| docker volume rm -f $(docker volume ls -f "dangling=true" -q) | |
| # Pull services | |
| docker compose -f docker-compose.prod.yml pull | |
| # Stop service | |
| if [ -f "docker-compose.yml" ]; then | |
| docker compose down --remove-orphans | |
| fi | |
| # Update compose + .env | |
| mv docker-compose.prod.yml docker-compose.yml | |
| if [ -f ".env" ]; then | |
| mv .env .env.prev | |
| fi | |
| touch .env | |
| # DB Container | |
| echo "POSTGRES_DB=${{ secrets.POSTGRES_DB }}" >> .env | |
| echo "POSTGRES_USER=${{ secrets.POSTGRES_USER }}" >> .env | |
| echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}" >> .env | |
| echo "POSTGRES_HOST=${{ secrets.POSTGRES_HOST }}" >> .env | |
| echo "POSTGRES_PORT=${{ secrets.POSTGRES_PORT }}" >> .env | |
| # Backend Container | |
| # DB Init | |
| echo "SUPERADMIN_EMAIL=${{ secrets.SUPERADMIN_EMAIL }}" >> .env | |
| echo "SUPERADMIN_PWD=${{ secrets.SUPERADMIN_PWD }}" >> .env | |
| # Auth | |
| echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> .env | |
| # Storage | |
| echo "S3_ACCESS_KEY=${{ secrets.S3_ACCESS_KEY }}" >> .env | |
| echo "S3_SECRET_KEY=${{ secrets.S3_SECRET_KEY }}" >> .env | |
| echo "S3_REGION=${{ secrets.S3_REGION }}" >> .env | |
| echo "S3_ENDPOINT_URL=${{ secrets.S3_ENDPOINT_URL }}" >> .env | |
| echo "S3_DISABLE_INTEGRITY_CHECK=${{ secrets.S3_DISABLE_INTEGRITY_CHECK }}" >> .env | |
| echo "RESEND_KEY=${{ secrets.RESEND_KEY }}" >> .env | |
| echo "EMAIL_FROM=${{ secrets.EMAIL_FROM }}" >> .env | |
| echo "BACKEND_HOST=${{ secrets.BACKEND_HOST }}" >> .env | |
| echo "RESEND_VERIFY_API_KEY=false" >> .env | |
| # Middlewares | |
| echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env | |
| echo "SERVER_NAME=${{ secrets.SERVER_NAME }}" >> .env | |
| echo "LOGFIRE_TOKEN=${{ secrets.LOGFIRE_TOKEN }}" >> .env | |
| # Others | |
| echo "SUPPORT_EMAIL=${{ secrets.SUPPORT_EMAIL }}" >> .env | |
| # Traefik Container | |
| echo "TRAEFIK_BACKEND_HOST=${{ secrets.TRAEFIK_BACKEND_HOST }}" >> .env | |
| echo "ACME_EMAIL=${{ secrets.ACME_EMAIL }}" >> .env | |
| # Restart services | |
| docker compose up -d --wait | |
| # Check it's up to date | |
| docker inspect -f '{{ .Created }}' $(docker compose images -q backend) | |
| - name: Ping server | |
| run: sleep 5 && curl ${{ secrets.PROD_ENDPOINT }} |