تطوير الويب

تحزيم Python بـ pyproject.toml في 2026: البناء والنشر على PyPI

7 دقائق للقراءة

🔝 الدليل الرئيسي للسلسلة: بايثون: لغة ومنظومة وأطر للمطوّرين

تحزيم مكتبة أو تطبيق Python في 2026 لم يعد بحاجة للمرور عبر setup.py وsetup.cfg وMANIFEST.in القديمة. وحّدت PEP 621 بيانات المشروع في pyproject.toml؛ ووحّدت PEP 517/518 الـ build backends؛ والمنظومة (uv، Hatch، setuptools، Poetry) تواءمت. النتيجة: ملف إعداد واحد قابل للقراءة بشريًا، حتمي، متوافق مع PyPI، وقابل للاستخدام من كل الأدوات. يبني هذا الدرس خطوة بخطوة مشروعًا قابلًا للتحزيم: بنية src-layout، بيانات PEP 621، build backend، بناء محلي، نشر على PyPI، وversioning تلقائي.

المتطلبات

  • Python 3.13.13 أو 3.14.5 متوفر
  • uv 0.11+ مُثبَّت (راجع Ruff وuv workflow) أو pip 24+
  • حساب PyPI وحساب TestPyPI (مجاني) إذا كنت تنوي النشر
  • الوقت المُقدَّر: 90 دقيقة

الخطوة 1 — اعتماد src-layout

src-layout هي بنية المشروع المُوصى بها من Python Packaging Authority منذ 2020. تضع الكود المصدري تحت مجلد src/ بدلًا من الجذر، مما يتجنّب أن يجد python -m pytest الحزمة غير المُثبَّتة عرضيًا عبر sys.path الضمني.

mon-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
├── .gitignore
├── .python-version
├── src/
│   └── mon_package/
│       ├── __init__.py
│       ├── core.py
│       ├── cli.py
│       └── py.typed              # signal PEP 561 : package typé
└── tests/
    ├── __init__.py
    ├── conftest.py
    └── test_core.py

ملف py.typed (فارغ) يُشير لأدوات فحص الأنواع أن الحزمة تُوزّع تعليقاتها — معيار PEP 561 لا غنى عنه لكي يحترم mypy أو pyright أنواعك من جانب المستهلك. __init__.py يمكن أن يكون فارغًا أو يُعيد تصدير API العامة (from .core import Client).

الخطوة 2 — pyproject.toml أدنى PEP 621

[project]
name = "itsc-tools"
version = "0.3.2"
description = "Outils de productivité pour développeurs francophones."
readme = "README.md"
requires-python = ">=3.13"
license = { text = "MIT" }
authors = [
    { name = "Mamadou Diallo", email = "contact@itskillscenter.io" }
]
keywords = ["productivity", "cli", "dev-tools"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
    "Topic :: Software Development :: Libraries",
    "Typing :: Typed"
]
dependencies = [
    "httpx>=0.27,<1.0",
    "pydantic>=2.13,<3.0",
    "click>=8.1",
]

[project.urls]
Homepage = "https://itskillscenter.io"
Documentation = "https://docs.itskillscenter.io/tools"
Repository = "https://github.com/itskillscenter/tools"
Issues = "https://github.com/itskillscenter/tools/issues"
Changelog = "https://github.com/itskillscenter/tools/blob/main/CHANGELOG.md"

ثلاثة عناصر حاسمة. الـ classifiers تُغذّي مُرشّحات بحث PyPI — اخترها بعناية، القائمة الرسمية على pypi.org/classifiers. Typing :: Typed يُشير للعالم أن الحزمة مُنمَّطة. project.urls يُغذّي الروابط في صفحة PyPI: بدون Repository، الزوّار لا يجدون كودك المصدري.

الخطوة 3 — اختيار build backend

الـ build backend هو البرنامج الذي يُحوّل كودك المصدري إلى wheel وsdist. أربعة خيارات مُهيمنة في 2026: setuptools (تاريخي)، hatchling (حديث، من Hatch)، flit-core (مُبسَّط)، uv build (مُدمج uv). لـ 90% من المشاريع، hatchling هو الخيار الصحيح.

[build-system]
requires = ["hatchling>=1.29"]
build-backend = "hatchling.build"

[tool.hatch.version]
path = "src/itsc_tools/__init__.py"   # lit __version__ depuis le code

[tool.hatch.build.targets.wheel]
packages = ["src/itsc_tools"]

[tool.hatch.build.targets.sdist]
include = [
    "/src",
    "/tests",
    "/README.md",
    "/LICENSE",
    "/CHANGELOG.md"
]

hatch.version.path يتجنّب تكرار الإصدار: نكتبه مرة واحدة في __init__.py تحت __version__ = "0.3.2"، وhatchling يقرأ القيمة عند البناء. يمكن أيضًا استخدام hatch-vcs لاشتقاق الإصدار مباشرة من tags Git.

الخطوة 4 — Entry points وسكربتات CLI

[project.scripts]
itsc = "itsc_tools.cli:main"
itsc-format = "itsc_tools.formatters:cli_entry"

[project.gui-scripts]
itsc-dashboard = "itsc_tools.dashboard:main"

# Plugin discoverable par d'autres packages
[project.entry-points."pytest11"]
itsc_plugin = "itsc_tools.pytest_plugin"

بمجرد تثبيت الحزمة عبر pip install itsc-tools أو uv add itsc-tools، يصبح الأمر itsc متاحًا في PATH الخاص بـ venv. الدالة المُشار إليها يجب أن تكون موجودة وقابلة للاستدعاء بدون وسائط — عادة dispatcher Click أو argparse.

الخطوة 5 — بناء الحزمة محليًا

uv build

ls dist/
# itsc_tools-0.3.2-py3-none-any.whl
# itsc_tools-0.3.2.tar.gz

unzip -l dist/itsc_tools-0.3.2-py3-none-any.whl
tar -tzf dist/itsc_tools-0.3.2.tar.gz

# Tester l'installation dans un venv temporaire
uv venv .test-install
.test-install/bin/python -m pip install dist/itsc_tools-0.3.2-py3-none-any.whl
.test-install/bin/itsc --help

# Vérifier les métadonnées
uv tool run twine check dist/*

الخطوة twine check تتحقق من الامتثال لـ PyPI: README مُعروض بشكل صحيح (Markdown أو reStructuredText)، البيانات موجودة، الـ classifiers صالحة. twine check بخطأ يُفشل الرفع، فمن الأفضل اكتشاف ذلك محليًا.

الخطوة 6 — Versioning وCHANGELOG

SemVer يبقى المعيار: MAJOR.MINOR.PATCH. لأتمتة الانتشار، نهجان: commitizen (يُولّد CHANGELOG من commits اصطلاحية) أو hatch-vcs (يقرأ الإصدار من tags Git).

# Approche hatch-vcs
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "vcs"

[tool.hatch.build.hooks.vcs]
version-file = "src/itsc_tools/_version.py"

# Approche commitizen
[tool.commitizen]
name = "cz_conventional_commits"
version = "0.3.2"
version_files = [
    "src/itsc_tools/__init__.py:__version__",
    "pyproject.toml:version"
]
tag_format = "v$version"
update_changelog_on_bump = true

uv tool install commitizen
cz commit   # commit guidé conventionnel
cz bump     # incrémente version + tag + changelog

نهج commitizen أكثر هيكلية للفرق: الـ commits يجب أن تحترم conventional commits (feat:، fix:، BREAKING CHANGE:)، والإصدار يُحسَب تلقائيًا. لمشروع شخصي، hatch-vcs أخف: git tag v0.4.0 يكفي.

الخطوة 7 — النشر على TestPyPI ثم PyPI

TestPyPI هو الـ staging الرسمي. ننشر هناك أولًا، نُثبّت من هناك للتحقق، ثم ننشر على PyPI الحقيقي. منذ 2022، المصادقة عبر API tokens أو trusted publishers (OIDC).

# Créer un token sur https://test.pypi.org/manage/account/token/
# puis https://pypi.org/manage/account/token/

cat > ~/.pypirc <<EOF
[distutils]
index-servers = pypi testpypi

[pypi]
username = __token__
password = pypi-AgEIc...

[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-AgEIc...
EOF
chmod 600 ~/.pypirc

# Publication TestPyPI
uv tool run twine upload --repository testpypi dist/*

# Test depuis TestPyPI
uv venv .check
.check/bin/pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple itsc-tools

# Publication PyPI réelle
uv tool run twine upload dist/*

ثلاث انضباطات حاسمة. الـ token PyPI محصور بالمشروع — إذا اختُرق، نُعيد توليده دون لمس الباقي. الـ --extra-index-url يسمح لـ TestPyPI بحل التبعيات من PyPI الحقيقي. اختبر التثبيت من TestPyPI قبل تلوّث PyPI الحقيقي — إصدار معطوب لا يمكن استبداله، فقط yanking.

الخطوة 8 — النشر التلقائي من GitHub Actions (Trusted Publishing)

Trusted Publishing (OIDC) هو المسار المُوصى به من PyPI منذ 2023: صفر token مُخزَّن، GitHub Actions يُصادق عبر OIDC، PyPI يتحقق من المصدر.

# .github/workflows/publish.yml
name: Publish
on:
  push:
    tags: ["v*"]

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    environment: release        # créé dans Settings → Environments
    permissions:
      id-token: write           # nécessaire pour OIDC
    steps:
      - uses: actions/checkout@v5
      - uses: astral-sh/setup-uv@v6
      - run: uv build
      - uses: pypa/gh-action-pypi-publish@release/v1
        # pas de token : OIDC trusted publisher

على جانب PyPI، في Manage project → Publishing، نُعلن repo GitHub + workflow + environment كـ Trusted Publisher. منذ الآن، إنشاء tag v0.4.0، الدفع، وPyPI يستلم الحزمة موقّعة من GitHub. لا token لإدارة أو تجديد. لمنظمة، هذا معيار الأمان 2026.

أخطاء شائعة

العَرَض السبب الحل
بناء فارغ أو أدنى مسار خاطئ في hatch.build.targets.wheel.packages الإشارة إلى src/mon_package، لا src/
الاختبارات مُثبَّتة في wheel لا فصل src/tests اعتماد src-layout بصرامة
README مكسور على PyPI الصيغة غير مُعلَنة إضافة content-type في [project.readme]
Classifier غير صالح → رفض الرفع typo في classifier التحقق على pypi.org/classifiers من السلسلة الدقيقة
تعارض إصدار على PyPI إعادة رفع نفس الإصدار زيادة الإصدار؛ PyPI لا يسمح بإعادة الرفع أبدًا
Token PyPI مرفوض username مختلف عن __token__ username = __token__ بالضبط

الأسئلة الشائعة

setuptools، hatchling، flit أم poetry؟
Hatchling لمشاريع جديدة: حديث، إعداد واضح، plugins. Setuptools إذا كان لديك legacy أو حاجة لامتدادات C. Flit للمشاريع المُصغّرة pure-Python. Poetry لا يزال مستخدمًا لكن uv + hatchling يحلّان محله في 2026.

هل ننشر sdist وwheel؟
نعم، الاثنان. Wheel للتثبيت السريع، sdist لمن يريد التجميع من المصدر أو لتوزيعات Linux التي تُعيد التحزيم.

كيف نُصدّر من tag Git؟
hatch-vcs مع tool.hatch.version.source = "vcs". الـ commits غير المُعلَّمة تخرج كـ 0.3.2.dev3+gabcdef تلقائيًا.

كيف نُضيف ملفات غير Python (templates، JSON)؟
إضافة [tool.hatch.build.targets.wheel.force-include] مع الأنماط أو استخدام include-package-data = true إذا setuptools.

كيف ننشر حزمة خاصة داخليًا؟
Self-host devpi أو gitea-packages، أو استخدام AWS CodeArtifact / Azure Artifacts. uv أو pip يدعمان --index-url مع credentials.

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité