Workflow GitHub Actions untuk lint, test, dan release Python membantu tim menjaga kualitas kode sekaligus mengurangi pekerjaan manual saat merge dan rilis. Untuk proyek Python, pipeline yang baik biasanya memisahkan validasi cepat seperti lint dan format check dari proses yang lebih berat seperti test matrix dan publish artifact.

Artikel ini menunjukkan pendekatan praktis untuk membangun pipeline yang realistis: cache dependency, lint dengan ruff atau flake8, pemeriksaan format, unit test dengan pytest, matrix versi Python bila diperlukan, penyimpanan artifact, dan release berbasis tag. Sebagai konteks, pendekatan ini relevan untuk repositori tool Python di GitHub seperti repo referensi Codex-5.5-codex-instruct-5.5, tanpa mengasumsikan detail internal repo yang belum jelas.

Tujuan pipeline: cepat gagal, mudah dibaca, dan aman dirilis

Pipeline CI/CD yang efektif untuk Python sebaiknya menjawab tiga kebutuhan:

  • Feedback cepat untuk developer: pull request harus segera menunjukkan error lint, format, atau test yang gagal.
  • Reproducible build: dependency dan langkah build konsisten di setiap run.
  • Release yang terkontrol: hanya tag yang valid yang memicu proses rilis atau publish.

Dalam praktiknya, Anda tidak perlu langsung membuat workflow yang kompleks. Untuk tim kecil, struktur paling masuk akal biasanya terdiri dari:

  1. CI workflow untuk lint, format check, dan test pada push atau pull_request.
  2. Release workflow yang berjalan saat tag dibuat, lalu membangun artifact dan membuat release.

Pemisahan ini membuat debugging lebih mudah. Jika release gagal, Anda tidak perlu menelusuri semua langkah CI harian. Sebaliknya, jika test gagal di pull request, proses rilis tidak ikut tercampur.

Struktur dasar workflow GitHub Actions untuk Python

Berikut struktur minimal yang umum dipakai:

  • Trigger: push, pull_request, dan push.tags.
  • Setup Python: menggunakan actions/setup-python.
  • Dependency cache: mempercepat instalasi package.
  • Lint: ruff check atau flake8.
  • Format check: misalnya ruff format --check atau black --check.
  • Test: pytest.
  • Artifact: menyimpan hasil build seperti wheel atau sdist.
  • Release: membuat GitHub Release berbasis tag.

Jika proyek Anda adalah library atau CLI Python, artifact seperti .whl dan .tar.gz layak dibangun saat release. Jika proyek lebih ke aplikasi internal, artifact bisa berupa log test, coverage report, atau bundle deploy.

Contoh workflow CI minimal yang realistis

Contoh berikut memecah job lint dan test agar sinyal kegagalan lebih jelas. Workflow ini juga memakai cache dependency dan matrix Python untuk memastikan kompatibilitas lintas versi.

name: ci

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
          cache-dependency-path: |
            requirements.txt
            requirements-dev.txt
            pyproject.toml

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

      - name: Ruff lint
        run: ruff check .

      - name: Format check
        run: ruff format --check .

  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ['3.10', '3.11']
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'
          cache-dependency-path: |
            requirements.txt
            requirements-dev.txt
            pyproject.toml

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

      - name: Run tests
        run: pytest -q

Mengapa dipisah antara lint dan test?

Pemisahan job memberi beberapa keuntungan:

  • Diagnostik lebih jelas: error style tidak bercampur dengan error runtime.
  • Paralelisme: lint dan test bisa berjalan bersamaan.
  • Perluasan lebih mudah: coverage, integration test, atau build bisa ditambahkan tanpa membuat satu job terlalu panjang.

Untuk proyek kecil, semua langkah memang bisa digabung dalam satu job. Namun, begitu tim mulai aktif membuka pull request, pemisahan job biasanya lebih nyaman.

Kenapa menggunakan cache dependency?

Instalasi dependency adalah salah satu bagian paling sering menghabiskan waktu pada CI Python. Dengan actions/setup-python dan opsi cache pip, runner akan menyimpan package berdasarkan file dependency. Hasilnya, run berikutnya lebih cepat selama dependency tidak berubah.

Perlu diperhatikan:

  • Cache tidak menggantikan file lock atau pinning versi.
  • Jika dependency berubah tetapi file yang dijadikan key tidak ikut berubah, cache bisa terasa “aneh” atau tidak akurat.
  • Pastikan cache-dependency-path mencakup file yang benar-benar menjadi sumber dependency Anda.

Memilih tool lint dan format: ruff vs flake8

Untuk pipeline modern, ruff sering menjadi pilihan praktis karena cepat dan bisa menangani lint sekaligus format check. Namun flake8 masih relevan jika proyek lama Anda sudah bergantung pada plugin tertentu.

Kapan memilih ruff?

  • Ingin setup lebih ringkas.
  • Performa lint menjadi prioritas.
  • Ingin satu tool untuk lint dan format check.

Kapan mempertahankan flake8?

  • Proyek sudah stabil dengan ekosistem plugin flake8 tertentu.
  • Tim belum siap migrasi rule lint.
  • Konfigurasi existing sudah menjadi standar internal.

Jika memakai flake8, langkah lint bisa diganti menjadi:

- name: Flake8 lint
  run: flake8 .

Sedangkan format check dapat memakai tool terpisah seperti black --check .. Trade-off-nya: lebih banyak tool berarti lebih banyak konfigurasi, tetapi kadang migrasi bertahap menjadi lebih aman untuk tim.

Menambahkan build artifact dan release berbasis tag

Release berbasis tag cocok jika Anda ingin memastikan hanya versi yang disengaja yang menghasilkan artifact final. Pola umum:

  1. Developer atau maintainer membuat tag, misalnya v0.3.0.
  2. Workflow release berjalan.
  3. Pipeline membangun package Python.
  4. Artifact diunggah.
  5. GitHub Release dibuat dari tag tersebut.

Contoh workflow release minimal:

name: release

on:
  push:
    tags:
      - 'v*'

jobs:
  build-and-release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
          cache-dependency-path: |
            requirements.txt
            requirements-dev.txt
            pyproject.toml

      - name: Install build tools
        run: |
          python -m pip install --upgrade pip
          pip install build

      - name: Build package
        run: python -m build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: python-dist
          path: dist/

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: dist/*

Mengapa artifact tetap diunggah meski file sudah masuk ke release?

Artifact dan release asset punya tujuan berbeda:

  • Artifact CI berguna untuk inspeksi internal, debugging build, atau konsumsi langkah berikutnya dalam workflow.
  • Release asset ditujukan untuk distribusi ke pengguna dari halaman GitHub Release.

Menyimpan keduanya membantu ketika Anda ingin memeriksa output build walau release step gagal atau perlu audit ulang hasil build.

Jika ingin publish ke package registry

Jika nanti Anda ingin publish ke indeks package seperti PyPI, tambahkan job terpisah setelah build sukses. Sebaiknya jangan langsung mencampur publish registry dengan pembuatan release GitHub pada iterasi pertama. Pisahkan dulu agar jalur kegagalan lebih mudah dipahami, terutama untuk tim kecil.

Strategi fail-fast yang masuk akal

Fail-fast berarti pipeline berhenti secepat mungkin saat syarat dasar gagal. Tujuannya menghemat waktu runner dan mempercepat feedback. Namun ada nuance penting.

Kapan fail-fast sebaiknya aktif?

  • Pada langkah berurutan dalam satu job, misalnya jika install dependency gagal maka test memang tidak perlu jalan.
  • Pada lint atau format check yang menjadi gate wajib sebelum merge.

Kapan fail-fast matrix sebaiknya dimatikan?

Pada strategy.matrix, menggunakan fail-fast: false sering lebih berguna. Alasannya, jika Python 3.10 gagal tetapi 3.11 juga punya masalah berbeda, Anda ingin melihat semua hasil sekaligus. Ini mengurangi siklus commit-perbaikan yang berulang.

Untuk tim kecil, kombinasi yang sering efektif adalah:

  • Lint: wajib lulus dan cepat.
  • Test matrix: fail-fast: false agar semua versi tetap dilaporkan.
  • Release: hanya jalan setelah tag, dan sebaiknya bergantung pada branch yang sudah terlindungi.

Proteksi branch dan aturan merge

Workflow yang bagus akan jauh lebih efektif jika didukung aturan repository.

Rekomendasi branch protection

  • Wajib pull request untuk merge ke main.
  • Wajib status check CI lulus.
  • Batasi siapa yang boleh mendorong langsung ke branch utama.
  • Opsional: wajib review minimal satu orang.

Tanpa branch protection, pipeline hanya menjadi alat observasi, bukan alat pengendali kualitas. Seseorang tetap bisa mendorong kode rusak langsung ke branch utama.

Prinsip praktis: jika release diambil dari tag pada commit di main, maka kualitas release sangat bergantung pada kualitas gate menuju main.

Secret handling yang aman

Workflow release kadang membutuhkan secret, misalnya token registry atau kredensial signing. Beberapa aturan dasar yang sebaiknya diikuti:

  • Jangan hardcode secret di YAML.
  • Simpan secret di GitHub Actions Secrets atau gunakan environment dengan approval jika proses rilis sensitif.
  • Berikan permission minimum yang diperlukan.
  • Hindari mencetak variabel sensitif ke log.

Untuk release GitHub sederhana, GITHUB_TOKEN bawaan biasanya cukup jika permission contents: write diatur sesuai kebutuhan. Untuk publish ke layanan eksternal, gunakan secret terpisah dan batasi hanya pada workflow release.

Kesalahan umum terkait secret

  • Menggunakan satu token dengan cakupan terlalu luas untuk semua workflow.
  • Menjalankan workflow release pada event yang terlalu terbuka.
  • Tidak membatasi siapa yang bisa membuat tag rilis.

Jika tim Anda kecil, pengamanan minimal yang efektif adalah: tag rilis hanya dibuat maintainer, branch utama diproteksi, dan secret publish hanya tersedia pada workflow release.

Debugging masalah umum di GitHub Actions Python

1. Cache tidak terasa mempercepat

Biasanya penyebabnya adalah file dependency tidak konsisten atau cache key tidak mewakili dependency yang sebenarnya. Pastikan file seperti requirements-dev.txt atau pyproject.toml ikut dicantumkan.

2. Lint lulus lokal tetapi gagal di CI

Sering terjadi karena versi tool lokal berbeda dari CI. Solusinya, pin dependency dev secara lebih konsisten dan dokumentasikan cara menjalankan tool yang sama secara lokal.

3. Test gagal hanya pada satu versi Python

Ini justru nilai utama matrix testing. Periksa dependency conditional, syntax yang tidak kompatibel, atau asumsi perilaku library yang berubah antar versi.

4. Release terpicu pada tag yang tidak semestinya

Gunakan pola tag yang jelas, misalnya v*, dan batasi kebiasaan membuat tag sembarangan. Untuk kebutuhan lebih ketat, Anda dapat menambahkan validasi nama tag sebelum build.

5. Workflow terlalu lambat

Kurangi pekerjaan yang tidak perlu pada pull request. Misalnya:

  • Jalankan lint di satu versi Python saja.
  • Batasi integration test berat ke branch utama atau workflow terjadwal.
  • Pisahkan release build dari CI harian.

Checklist adopsi bertahap untuk tim kecil

Jika tim Anda belum punya pipeline yang matang, jangan langsung mengejar semua fitur. Gunakan urutan adopsi berikut:

  1. Tahap 1: tambahkan workflow CI dengan lint dan pytest pada pull_request.
  2. Tahap 2: aktifkan cache dependency dan format check.
  3. Tahap 3: tambahkan matrix Python bila proyek memang mendukung lebih dari satu versi.
  4. Tahap 4: aktifkan branch protection dan wajibkan status check.
  5. Tahap 5: tambahkan build artifact pada branch utama atau saat tag.
  6. Tahap 6: buat workflow release berbasis tag.
  7. Tahap 7: jika perlu, pisahkan publish ke registry dengan secret yang lebih ketat.

Urutan ini penting untuk menjaga developer experience. Pipeline yang terlalu rumit di awal justru sering di-bypass karena dianggap lambat atau sulit dipahami.

Penutup

Workflow GitHub Actions untuk proyek Python tidak harus rumit agar berguna. Mulailah dari lint, format check, dan unit test yang cepat, lalu tambahkan matrix, artifact, dan release berbasis tag ketika proses tim mulai stabil.

Untuk proyek tool Python di GitHub, termasuk yang konteksnya mirip repo referensi Codex-5.5-codex-instruct-5.5, desain pipeline yang baik adalah yang mudah dirawat, cepat memberi feedback, dan aman saat rilis. Jika tiga hal itu tercapai, CI/CD benar-benar membantu tim, bukan menambah beban operasional.