Packaging و انتشار در PyPI
انتشار پکیج پایتون در PyPI به دیگران اجازه میدهد با یک pip install package_name کد شما را نصب کنند. در این فصل با ساختار مدرن پکیج (pyproject.toml)، setuptools/hatchling/poetry، انتشار در PyPI، Test PyPI، Versioning، Entry Points و CI/CD آشنا میشویم.
انتشار پکیج پایتون در PyPI به دیگران اجازه میدهد با یک pip install package_name کد شما را نصب کنند. در این فصل با ساختار مدرن پکیج (pyproject.toml)، setuptools/hatchling/poetry، انتشار در PyPI، Test PyPI، Versioning، Entry Points و CI/CD آشنا میشویم.
پکیجینگ مدرن – pyproject.toml
از پایتون 3.11 و PEP 621، فایل pyproject.toml استاندارد رسمی پکیجینگ است:
# pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-awesome-package"
version = "0.1.0"
description = "توضیح کوتاه پکیج"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "محمدعلی ناظری", email = "info@icsd.ir"}
]
keywords = ["python", "tutorial", "icsd"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Natural Language :: Persian",
]
dependencies = [
"requests>=2.28",
"click>=8.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-cov",
"black",
"mypy",
]
docs = [
"sphinx",
"sphinx-rtd-theme",
]
[project.urls]
Homepage = "https://github.com/yourname/my-awesome-package"
Documentation = "https://your-docs.com"
Repository = "https://github.com/yourname/my-awesome-package"
Issues = "https://github.com/yourname/my-awesome-package/issues"
[project.scripts]
mytool = "my_package.cli:main"
ساختار پروژه (src layout)
my-awesome-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── .gitignore
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── core.py
│ ├── utils.py
│ └── cli.py
├── tests/
│ ├── __init__.py
│ ├── test_core.py
│ └── test_utils.py
└── docs/
└── index.rst
چرا src layout؟ جلوگیری از import شدن نسخه نصبنشده هنگام تست. با src layout، باید پکیج را install کنید تا از آن استفاده کنید.
__init__.py
# src/my_package/__init__.py
"""My awesome package."""
__version__ = "0.1.0"
from .core import main_function, MainClass
from .utils import helper_function
__all__ = ["main_function", "MainClass", "helper_function"]
انتخاب Build Backend
| Backend | محبوبیت | ویژگی |
|---|---|---|
| setuptools | ⭐⭐⭐⭐⭐ | قدیمی، رایج |
| hatchling | ⭐⭐⭐⭐ | مدرن، سریع |
| poetry | ⭐⭐⭐⭐ | مدیریت dependency کامل |
| flit | ⭐⭐⭐ | ساده، بدون C extension |
| pdm | ⭐⭐⭐ | سازگار با PEP 582 |
setuptools
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
where = ["src"]
hatchling (پیشنهاد ما)
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/my_package"]
poetry
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "my-package"
version = "0.1.0"
description = "..."
authors = ["نام <email@example.com>"]
[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.28"
[tool.poetry.dev-dependencies]
pytest = "^7.0"
ساخت پکیج
pip install build
python -m build
# خروجی در dist/
ls dist/
# my_awesome_package-0.1.0-py3-none-any.whl (wheel)
# my-awesome-package-0.1.0.tar.gz (sdist)
تفاوت wheel و sdist
- sdist (.tar.gz): source distribution – کد منبع، نیاز به کامپایل دارد
- wheel (.whl): binary distribution – آماده نصب، سریعتر
نصب محلی برای تست
# نصب editable - تغییرات بدون reinstall
pip install -e .
# با dev dependencies
pip install -e ".[dev]"
# نصب از فایل wheel
pip install dist/my_awesome_package-0.1.0-py3-none-any.whl
Versioning – SemVer
Semantic Versioning: MAJOR.MINOR.PATCH
- MAJOR – تغییرات breaking (1.0.0 → 2.0.0)
- MINOR – قابلیت جدید بدون breaking (1.0.0 → 1.1.0)
- PATCH – bug fix (1.0.0 → 1.0.1)
0.1.0 - نسخه اولیه
0.2.0 - افزودن قابلیت جدید
0.2.1 - رفع باگ
1.0.0 - اولین نسخه پایدار
1.0.0a1 - alpha
1.0.0b1 - beta
1.0.0rc1 - release candidate
2.0.0 - تغییر breaking
Dynamic version
# خواندن version از __init__.py
[project]
dynamic = ["version"]
[tool.hatch.version]
path = "src/my_package/__init__.py"
Test PyPI – تمرین قبل از انتشار
قبل از انتشار در PyPI واقعی، در Test PyPI تست کنید:
- ثبتنام: test.pypi.org
- ساخت API token در تنظیمات حساب
- آپلود
pip install twine
# آپلود به Test PyPI
twine upload --repository testpypi dist/*
# نصب از Test PyPI برای تست
pip install --index-url https://test.pypi.org/simple/
--extra-index-url https://pypi.org/simple/
my-awesome-package
~/.pypirc
[distutils]
index-servers =
pypi
testpypi
[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmcCJ...
[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-AgENdGVzdC5weXBp...
انتشار در PyPI
# ساخت
python -m build
# آپلود
twine upload dist/*
# با مشخص کردن repository
twine upload --repository pypi dist/*
# تایید قبل از آپلود
twine check dist/*
پس از انتشار:
pip install my-awesome-package
Entry Points – دستورات CLI
# src/my_package/cli.py
import click
@click.command()
@click.option("--name", default="World", help="نام برای سلام")
@click.option("--count", default=1, help="تعداد تکرار")
def main(name, count):
"""ابزار CLI من."""
for _ in range(count):
click.echo(f"سلام {name}!")
if __name__ == "__main__":
main()
# pyproject.toml
[project.scripts]
mytool = "my_package.cli:main"
# پس از نصب:
# $ mytool --name علی --count 3
# سلام علی!
# سلام علی!
# سلام علی!
چند entry point
[project.scripts]
mytool = "my_package.cli:main"
mytool-admin = "my_package.admin:cli"
mytool-server = "my_package.server:run"
[project.gui-scripts]
mytool-gui = "my_package.gui:main"
README و LICENSE
README.md
# My Awesome Package
[](https://pypi.org/project/my-awesome-package/)
[](https://pypi.org/project/my-awesome-package/)
[](https://github.com/user/repo/actions)
پکیج پایتون عالی برای X و Y.
## نصب
```bash
pip install my-awesome-package
```
## استفاده
```python
from my_package import main_function
result = main_function("hello")
print(result)
```
## مستندات
[my-awesome-package.readthedocs.io](https://my-awesome-package.readthedocs.io)
## مجوز
MIT
LICENSE
MIT License
Copyright (c) 2026 محمدعلی ناظری
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction...
GitHub Actions – CI/CD
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run tests
run: pytest --cov=my_package
- name: Upload coverage
uses: codecov/codecov-action@v3
انتشار خودکار با Tag
# .github/workflows/publish.yml
name: Publish to PyPI
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # برای trusted publishing
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build
run: |
pip install build
python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
گنجاندن فایلهای غیر پایتون
# pyproject.toml
[tool.hatch.build.targets.wheel.shared-data]
"data/templates" = "share/my_package/templates"
# یا setuptools
[tool.setuptools.package-data]
my_package = ["data/*.json", "templates/*.html"]
# دسترسی به فایلهای پکیج
from importlib.resources import files
template = files("my_package.data").joinpath("template.html").read_text()
چکلیست انتشار
- ✅ نام پکیج در PyPI آزاد است (search کنید)
- ✅ pyproject.toml کامل (description، keywords، classifiers)
- ✅ README.md کامل با مثالها
- ✅ LICENSE وجود دارد
- ✅ تستها pass میشوند
- ✅ Documentation مشخص
- ✅ تست در Test PyPI
- ✅ Tag در git:
git tag v0.1.0; git push --tags - ✅ آپلود نهایی
- ✅ بررسی صفحه pypi.org/project/…
بهترین شیوهها
- از pyproject.toml استفاده کنید (نه setup.py قدیمی)
- src layout برای جلوگیری از import مسائل
- نسخه را در یک جا نگه دارید (dynamic version)
- SemVer را رعایت کنید
- قبل از انتشار، Test PyPI
- CHANGELOG.md نگه دارید
- دو ایمیل: یکی برای PyPI، یکی برای contact
- 2FA روی حساب PyPI فعال کنید
جمعبندی
- pyproject.toml استاندارد مدرن پکیجینگ
- src layout بهترین ساختار
- hatchling/setuptools/poetry برای build
- twine برای آپلود به PyPI
- Test PyPI برای تست انتشار
- SemVer برای versioning
- GitHub Actions برای CI/CD خودکار