GitHub Actions لمشاريع بايثون: دليل عملي لإعداد CI

بقلم فريق تقني ·· أدوات المطورين
GitHub Actions لمشاريع بايثون: دليل عملي لإعداد CI

تنسى تشغيل pytest قبل الدمج، فيصل كود مكسور إلى الفرع الرئيسي ولا تكتشفه إلا لاحقاً. يلزمك شيئان: ملف واحد في مستودعك يجعل GitHub يشغّل اختباراتك مع كل دفعة وكل طلب سحب، وإعداد في صفحة المستودع يمنع الدمج ما لم تنجح الاختبارات. بعدها لا يبقى تشغيلها قراراً بشرياً.

أين يوضع الملف وما بنيته

الثابت هو المجلد لا الاسم: ضع الملف في .github/workflows/ci.yml أو سمِّه ما شئت. GitHub يقرأ كل ملف YAML داخل .github/workflows ويشغّله بوصفه سير عمل.

المفاتيح التي تحتاجها أربعة. on يحدد متى يعمل السير: عند الدفع، أو عند فتح طلب سحب، أو كليهما. jobs يضم المهام، وكل مهمة تعمل على آلة مستقلة. runs-on يحدد نظام تلك الآلة، وubuntu-latest يقابل حالياً Ubuntu 24.04. steps قائمة الخطوات بالترتيب: إجراء جاهز عبر uses، أو أمر طرفية عبر run. وسقف المهمة الواحدة 6 ساعات على مشغّلات GitHub، ولن تقترب منه.

أول سير عمل يعمل فعلاً

name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: astral-sh/[email protected]
        with:
          enable-cache: true
      - run: uv sync --locked --all-extras --dev
      - run: uv run pytest tests

انتبه لاختلاف صيغة الوسم بين السطرين: actions/checkout@v7 صالح لأن هذا الإجراء ينشر وسماً رئيسياً متحركاً، أما setup-uv فتوقّف عن ذلك منذ إصداره الثامن؛ كتابة @v9 تفشل، والصحيح تثبيت الإصدار كاملاً @v9.0.0.

الحاسم هنا --locked: تفشل المهمة إن كان uv.lock غير محدَّث ويحتاج إلى إعادة توليد، أي أن أحدهم أضاف اعتمادية ونسي تحديث القفل. البديل --frozen يتجاهل هذا التحقق ويثبّت ما في القفل كما هو. في CI اختر --locked دائماً؛ الفشل الصريح أرخص من بناء صامت بحزم قديمة. لأساسيات القفل نفسه راجع أداة uv لإدارة حزم بايثون: دليل عملي للانتقال من pip وvenv، ولكتابة الاختبارات نفسها راجع شرح pytest: من أول اختبار إلى fixture وparametrize.

قيمة enable-cache الافتراضية auto، أي مفعّلة أصلاً على مشغّلات GitHub؛ كتابتها صراحة توضيح لا أكثر.

Ruff: خطوة فحص وخطوة تنسيق

أضف Ruff بوصفه اعتمادية تطوير: uv add --dev ruff. عندها يثبّت uv.lock إصداره، فلا تتغيّر نتيجة الفحص من تشغيل إلى آخر. ثم أضف خطوتين إلى سير العمل:

      - run: uv run ruff check --output-format=github .
      - run: uv run ruff format --check

الوسيط --output-format=github يجعل الأخطاء تظهر معلّمة على أسطرها داخل واجهة طلب السحب بدل أن تدفنها السجلات.

Ruff 0.16.0، الصادر في 23 يوليو 2026، رفع القواعد المفعّلة افتراضياً من 59 إلى 413. ترقيته على مشروع قائم قد تُفشل CI فجأة بمئات الأخطاء في كود لم يتغيّر. للعودة إلى السلوك السابق ثبّت الاختيار في pyproject.toml تحت جدول [tool.ruff.lint] بالسطر select = ["E4", "E7", "E9", "F"]، ثم وسّع تدريجياً. التفاصيل في الانتقال إلى Ruff: فحص وتنسيق بايثون بأداة واحدة.

وثائق Ruff الرسمية ما زالت تعرض أمثلة بإصدارات قديمة مثل ruff-action@v3 وcheckout@v4. لا تنسخها؛ الأحدث اليوم actions/checkout v7.0.1 وastral-sh/setup-uv v9.0.0 وastral-sh/ruff-action v4.1.0.

مصفوفة إصدارات بايثون ومتى تستحق

إن كانت مكتبتك يستخدمها آخرون، اختبرها على أكثر من إصدار:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]
    steps:
      - uses: actions/checkout@v7
      - uses: astral-sh/[email protected]
        with:
          enable-cache: true
          python-version: ${{ matrix.python-version }}
      - run: uv sync --locked --all-extras --dev
      - run: uv run pytest tests

هذا المقطع يحل محل مهمة test كاملة، ولا يُلصق فوقها.

python-version في setup-uv يضبط UV_PYTHON ويتقدّم على .python-version وعلى ما في pyproject.toml. والحد الأعلى للمصفوفة 256 مهمة لكل تشغيل.

القيمة الافتراضية لـ fail-fast هي true، فأول إصدار يفشل يلغي بقية المهام قبل أن تكتمل. إن أردت رؤية الصورة كاملة اضبطه على false. وإن كان مشروعك تطبيقاً داخلياً يعمل على إصدار واحد، فالمصفوفة ترف يضاعف الدقائق بلا فائدة.

اجعل الفحص إلزامياً قبل الدمج

سير العمل وحده لا يمنع أحداً من الدمج فوق فحص فاشل. المسار: Settings ← Rules ← Rulesets ← New ruleset ← New branch ruleset، ثم فعّل قاعدة «Require status checks before merging» واختر مهمة الاختبار من القائمة. GitHub يوصي اليوم بـ Rulesets، لكن نظام Branch protection rules لم يُوقَف، والاثنان يعملان معاً على المستودع نفسه.

مصيدتان: الأولى أن الفحص لا يظهر في قائمة الاشتراط قبل أن يعمل مرة واحدة على الأقل، فادفع تغييراً أولاً ثم اضبط القاعدة. والثانية أن الفحص المطلوب يمر إذا كانت حالته نجاحاً أو تخطّياً أو حياداً؛ أي أن مهمة متخطاة بشرط if غير محقق تُحتسب مروراً ولا تمنع الدمج.

يمر محلياً ويسقط على المشغّل

الأسباب المتكررة قليلة:

  • uv.lock غير مرفوع إلى المستودع أو غير محدّث.
  • ملفات موجودة على جهازك لكن .gitignore يستبعدها.
  • حساسية حالة الأحرف: لينكس يفرّق بين Test_utils.py وtest_utils.py بينما ويندوز وmacOS لا يفرّقان، فاستيراد يعمل محلياً يفشل على المشغّل.
  • اختبارات تعتمد على الشبكة أو على متغيرات بيئة موجودة عندك فقط.
  • الأسرار لا تُمرَّر إلى طلبات السحب القادمة من نسخة متفرّعة، باستثناء GITHUB_TOKEN بصلاحيات مقيدة.

هل يستحق مشروعك ذلك؟

إن كان المستودع عاماً فنعم: GitHub Actions مجاني على المشغّلات القياسية للمستودعات العامة، ولا تحتاج بطاقة دفع لتشغيله، وهذه عقبة تسقط عن كثير من المطوّرين العرب.

للمستودعات الخاصة، الخطة المجانية تمنحك 2000 دقيقة شهرياً. الدقيقة الزائدة على لينكس بـ 0.006 دولار مقابل 0.062 دولار على macOS، أي عشرة أضعاف. أبقِ CI على ubuntu-latest وحده ما لم يكن مشروعك يستهدف macOS فعلاً، ولا تفعّل المصفوفة قبل أن تحتاجها.