Anasayfa / Software / Mastering CI/CD: Setting Up GitHub Actions for Python Projects

Mastering CI/CD: Setting Up GitHub Actions for Python Projects

GitHub Actions Python

Continuous integration (CI) is the backbone of modern software development. By automating tests, linting, and builds, you catch bugs early and keep your codebase healthy. GitHub Actions offers a powerful, native way to run CI pipelines directly from your repository, and it works beautifully with Python projects. In this guide, we’ll walk through every step you need to set up a reliable CI workflow, from repository preparation to advanced caching tricks. Whether you’re maintaining a small open‑source library or a large web service, the patterns here will scale with you.

What You’ll Need

  • A GitHub account and a repository containing a Python project.
  • Basic knowledge of Git and the command line.
  • Python 3.8+ installed locally for testing.
  • Familiarity with requirements.txt or pyproject.toml dependency files.
  • An optional Docker installation if you want container‑based testing.

Step 1: Organize Your Project Structure

Before adding any workflow files, ensure your project follows a conventional layout. A typical Python project looks like this:

my_project/                     # Root folder
├─ .git/                       # Git metadata (auto‑generated)
├─ .github/                    # GitHub‑specific configuration
│   └─ workflows/              # CI/CD workflow files go here
├─ src/                        # Your package source code
│   └─ my_package/             # Example package
│       ├─ __init__.py
│       └─ core.py
├─ tests/                      # Test suite (pytest preferred)
│   └─ test_core.py
├─ requirements.txt            # Runtime dependencies
├─ requirements-dev.txt        # Development & testing dependencies
├─ pyproject.toml (optional)   # Build system config
└─ README.md

Having a clear separation between src and tests makes it easier for tools like pytest to discover tests and for coverage reports to be accurate.

Step 2: Create a Basic Workflow File

Inside .github/workflows/, create a file named ci.yml. This file tells GitHub Actions what to do on each push or pull request.

name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Run tests
        run: |
          pytest --color=yes

This minimal pipeline checks out the code, installs Python 3.11, installs both runtime and development dependencies, and finally runs pytest. Commit and push this file; you’ll see a new workflow run in the “Actions” tab of your repo.

Step 3: Add Linting and Type Checking

Code quality tools such as flake8 (linting) and mypy (static type checking) help keep the codebase consistent. Extend the requirements-dev.txt file:

pytest
flake8
mypy

Then update the workflow to run these checks after installing dependencies.

      - name: Lint with flake8
        run: |
          flake8 src tests

      - name: Type check with mypy
        run: |
          mypy src

By failing the workflow when linting or typing errors appear, you enforce a clean code standard on every pull request.

Step 4: Cache Dependencies for Faster Builds

Installing dependencies from scratch on each run can waste minutes. GitHub Actions offers a built‑in caching action. Add a cache step before the install step:

      - name: Cache pip packages
        uses: actions/cache@v3
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-

The cache key hashes all requirements files, so any change to dependencies automatically creates a new cache. This can shave 30‑60 seconds off each run.

Step 5: Generate Test Coverage Reports

Coverage data tells you how much of your code is exercised by tests. Install coverage and the pytest-cov plugin, then modify the test step:

      - name: Install coverage tools
        run: |
          pip install coverage pytest-cov

      - name: Run tests with coverage
        run: |
          pytest --cov=src --cov-report=xml

      - name: Upload coverage to Codecov (optional)
        uses: codecov/codecov-action@v3
        with:
          files: coverage.xml

If you prefer a free, built‑in solution, you can use the actions/upload-artifact action to store the coverage.xml file as a workflow artifact.

Step 6: Deploy on Successful Builds (Optional)

Many Python projects need to publish a package to PyPI or deploy a web service. Below is a minimal example that pushes a new release to PyPI when a tag matching v* is created.

on:
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - name: Install build tools
        run: |
          python -m pip install --upgrade pip
          pip install build twine
      - name: Build distribution
        run: |
          python -m build
      - name: Publish to PyPI
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          twine upload dist/*

Remember to store your PyPI API token in the repository’s Settings → Secrets as PYPI_API_TOKEN. This step is optional but demonstrates how CI can become CD (continuous deployment) with just a few extra lines.

Common Mistakes to Avoid

1. Hard‑coding Python versions. Use a matrix strategy if you need to test against multiple versions.
2. Forgetting to cache. Skipping the cache step leads to slower builds and wasted CI minutes.
3. Running tests in the wrong directory. Ensure pytest runs from the repository root so it can locate tests/ and src/ correctly.
4. Missing requirements-dev.txt. Linting and type checking will fail if dev dependencies aren’t installed.
5. Exposing secrets. Never hard‑code API keys; always use GitHub Secrets.

Tips and Tricks

– Matrix builds. Test against Python 3.9, 3.10, and 3.11 in parallel to ensure compatibility.
– Conditional steps. Use if: github.ref == 'refs/heads/main' to run deployment only on the main branch.
– Reusable workflows. Store common steps (like linting) in a separate YAML file and call it with uses to keep your CI DRY.
– Self‑hosted runners. For heavy workloads, consider a self‑hosted runner with more CPU or GPU resources.

Frequently Asked Questions

Do I need a Docker container for Python CI?

No. GitHub’s ubuntu-latest runner already includes Python, pip, and common build tools. Use Docker only when you need a specific OS environment or system‑level dependencies.

How can I test multiple Python versions?

Define a matrix in the workflow:

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11']
    steps:
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      # rest of steps …

This runs the same job three times, one for each version.

Can I run my own scripts before the CI starts?

Yes. Add a step that executes a shell script or a Python module. For example, you might generate dynamic configuration files or download test data from an external source.

Conclusion

Setting up a continuous integration pipeline with GitHub Actions for a Python project is straightforward once you understand the building blocks: checkout, Python setup, dependency installation, caching, testing, linting, and optional deployment. By following the steps above, you’ll have a fast, reliable CI workflow that catches errors early, enforces code quality, and even automates releases. Remember to iterate on your pipeline—add matrix testing, improve caching, and keep an eye on common pitfalls. Happy coding, and may your builds always be green!

Photo by Rubaitul Azad on Unsplash

Etiketlendi: