Construction et packaging¶
L'écosystème Python a profondement évolue ces dernières années avec l'émergence de nouveaux gestionnaires de paquets (poetry, uv) et la standardisation autour de pyproject.toml. Cette section couvre le packaging de librairies, la publication sur PyPI, la conteneurisation et les pipelines CI/CD.
Comparatif des gestionnaires de paquets¶
| Outil | Vitesse | Lock file | Virtualenv | Publish PyPI | Points forts |
|---|---|---|---|---|---|
| pip | Lent | Non natif | Non | Via twine | Standard, toujours present |
| poetry | Moyen | Oui | Oui | Oui | Tout-en-un, gestion des dépendances fine |
| uv | Très rapide | Oui | Oui | Non direct | Remplacé pip/venv/pip-tools, écrit en Rust |
| pdm | Rapide | Oui | Oui | Oui | Standards PEP 517/582, flexible |
uv en 2025
uv (de Astral, créateurs de ruff) est devenu le gestionnaire le plus rapide disponible. Il remplacé pip, pip-tools et virtualenv avec une compatibilité quasi-totale. Recommande pour les nouveaux projets.
pyproject.toml — Le standard actuel¶
pyproject.toml est le fichier de configuration unifie défini par PEP 517/518/621. Il remplacé setup.py, setup.cfg et requirements.txt pour les projets packagables.
Exemple complet avec uv¶
# pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mon-paquet"
version = "1.0.0"
description = "Un exemple de paquet Python"
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.12"
authors = [{ name = "Fabien", email = "contact@nuevolia.dev" }]
keywords = ["api", "fastapi", "exemple"]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3.12",
"License :: OSI Approved :: MIT License",
]
dependencies = [
"fastapi>=0.115",
"sqlalchemy>=2.0",
"pydantic>=2.0",
"uvicorn[standard]>=0.30",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.23",
"pytest-cov>=5.0",
"httpx>=0.27",
"hypothesis>=6.0",
]
lint = [
"ruff>=0.4",
"mypy>=1.10",
]
[project.scripts]
mon-api = "mon_paquet.main:run"
[project.urls]
Homepage = "https://nuevolia.dev"
Repository = "https://github.com/fabien/mon-paquet"
[tool.uv]
dev-dependencies = [
"pytest>=8.0",
"ruff>=0.4",
]
Commandes uv essentielles¶
# Installer uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Creer un nouveau projet
uv init mon-projet
cd mon-projet
# Ajouter une dependance
uv add fastapi
uv add --dev pytest ruff
# Installer les dependances (genere uv.lock)
uv sync
# Lancer un script dans l'environnement
uv run python main.py
uv run pytest
# Mettre a jour toutes les dependances
uv lock --upgrade
uv sync
Workflow avec poetry¶
# Installer poetry
curl -sSL https://install.python-poetry.org | python3 -
# Creer un projet
poetry new mon-projet
cd mon-projet
# Ajouter des dependances
poetry add fastapi uvicorn
poetry add --group dev pytest ruff mypy
# Installer et activer l'environnement
poetry install
poetry shell
# Publier sur PyPI
poetry build # Cree dist/mon_projet-1.0.0.tar.gz et .whl
poetry publish # Publie sur PyPI (necessite un compte)
Build et publication sur PyPI¶
# Avec uv et hatch
uv build
# => dist/mon_paquet-1.0.0-py3-none-any.whl
# => dist/mon_paquet-1.0.0.tar.gz
# Publication via twine (compatible pip/uv)
pip install twine
twine upload dist/*
# Sur PyPI Test d'abord (recommande)
twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ mon-paquet
Tokens PyPI
N'utilisez jamais votre mot de passe PyPI directement. Créez un token API sur pypi.org et stockez-le dans ~/.pypirc ou via la variable d'environnement TWINE_TOKEN.
Dockerfile optimise multi-stage¶
Un Dockerfile Python de production doit minimiser la taille de l'image finale et séparer les étapes de build et de runtime.
# Dockerfile
# ============================================================
# Stage 1 : builder — installe les dependances
# ============================================================
FROM python:3.12-slim AS builder
WORKDIR /build
# Copier uniquement les fichiers de dependances d'abord
# (optimise le cache Docker lors des modifications de code)
COPY pyproject.toml uv.lock ./
# Installer uv et les dependances dans un dossier isole
RUN pip install --no-cache-dir uv && \
uv export --no-dev --format requirements-txt > requirements.txt && \
pip install --no-cache-dir --target=/deps -r requirements.txt
# ============================================================
# Stage 2 : runtime — image minimale sans outils de build
# ============================================================
FROM python:3.12-slim AS runtime
# Utilisateur non-root pour la securite
RUN useradd --create-home --shell /bin/bash appuser
WORKDIR /app
# Copier les dependances depuis le builder
COPY --from=builder /deps /deps
ENV PYTHONPATH=/deps
# Copier le code applicatif
COPY --chown=appuser:appuser . .
USER appuser
# Variables d'environnement recommandees
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PORT=8000
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
# Construction et test de l'image
docker build -t mon-api:latest .
docker run -p 8000:8000 mon-api:latest
# Verification de la taille
docker images mon-api
.dockerignore¶
__pycache__/
*.pyc
*.pyo
.pytest_cache/
.mypy_cache/
.ruff_cache/
htmlcov/
dist/
*.egg-info/
.env
.env.*
tests/
docs/
*.md
.git/
Pipeline CI/CD — GitHub Actions¶
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint:
name: Lint et formatage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Installer uv
uses: astral-sh/setup-uv@v3
- name: Installer Python
run: uv python install 3.12
- name: Installer les dependances
run: uv sync --all-extras
- name: Verifier le formatage (ruff)
run: uv run ruff format --check .
- name: Linter (ruff)
run: uv run ruff check .
- name: Verification des types (mypy)
run: uv run mypy .
test:
name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Installer uv
uses: astral-sh/setup-uv@v3
- name: Installer Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Installer les dependances
run: uv sync --all-extras
- name: Executer les tests avec couverture
run: uv run pytest --cov=. --cov-report=xml --cov-fail-under=75
- name: Publier la couverture
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
build:
name: Build Docker
runs-on: ubuntu-latest
needs: [lint, test]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Build image Docker
run: docker build -t mon-api:${{ github.sha }} .
- name: Test de sante de l'image
run: |
docker run -d -p 8000:8000 --name test-container mon-api:${{ github.sha }}
sleep 5
curl -f http://localhost:8000/health || exit 1
docker stop test-container