MFormations
Modern Python Engineering

Chapitre 13

13 - DevOps Python

> **Duree :** 3 semaines > **Objectif :** Maitriser l'ecosysteme DevOps Python : Poetry, CI/CD, Docker, publication.

Cours 13 : DevOps Python

1. Poetry

1.1 Initialisation

poetry new mon-projet
cd mon-projet
poetry add fastapi uvicorn
poetry add --dev pytest ruff mypy

1.2 pyproject.toml

[tool.poetry]
name = "mon-projet"
version = "0.1.0"
description = "Un projet Python moderne"
authors = ["Dev Team"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.111.0"
uvicorn = {version = "^0.29.0", extras = ["standard"]}

[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
ruff = "^0.4.0"
mypy = "^1.9.0"
pre-commit = "^3.6.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

1.3 Commandes principales

poetry install               # Installer les dependances
poetry add requests          # Ajouter une依赖
poetry add --dev pytest      # Ajouter une dev-dependance
poetry update                # Mettre a jour
poetry build                 # Construire le package
poetry publish               # Publier sur PyPI
poetry export -f requirements.txt > requirements.txt  # Exporter

2. Ruff

2.1 Linter et formateur

# Linting
ruff check src/
ruff check --fix src/         # Autofix automatique

# Formatting
ruff format src/
ruff format --check src/      # Verifier sans modifier

2.2 Configuration

[tool.ruff]
line-length = 100
target-version = "py312"
select = ["E", "F", "I", "N", "W", "UP", "B", "SIM"]
ignore = ["B905"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

3. Mypy

3.1 Configuration stricte

[tool.mypy]
strict = true
python_version = "3.12"
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
no_implicit_optional = true
check_untyped_defs = true

3.2 Utilisation

mypy src/ --strict
mypy src/ --show-error-codes
mypy src/ --ignore-missing-imports

4. Pre-commit

4.1 Configuration

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.0
    hooks:
      - id: ruff
      - id: ruff-format

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.9.0
    hooks:
      - id: mypy
        args: [--strict]
        additional_dependencies: [pydantic]

4.2 Installation

pip install pre-commit
pre-commit install
pre-commit run --all-files

5. Docker

5.1 Multi-stage build

# Stage 1: Builder
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry && \
    poetry export -f requirements.txt > requirements.txt

# Stage 2: Runtime
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ src/
COPY alembic/ alembic/
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0"]

5.2 docker-compose

version: "3.8"
services:
  app:
    build: .
    ports: ["8000:8000"]
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/app
    depends_on: [db]
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes: ["pgdata:/var/lib/postgresql/data"]

volumes:
  pgdata:

6. CI/CD avec GitHub Actions

6.1 Workflow complet

name: CI/CD
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install poetry && poetry install
      - run: poetry run ruff check src/
      - run: poetry run mypy src/

  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "${{ matrix.python-version }}" }
      - run: pip install poetry && poetry install
      - run: poetry run pytest tests/ -v --cov=src

  publish:
    needs: [quality, test]
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install poetry
      - run: poetry build
      - run: poetry publish --username __token__ --password ${{ secrets.PYPI_TOKEN }}

7. Nox

import nox

@nox.session(python=["3.11", "3.12", "3.13"])
def tests(session):
    session.install("poetry")
    session.run("poetry", "install")
    session.run("pytest", "tests/", "-v")

@nox.session
def lint(session):
    session.install("ruff")
    session.run("ruff", "check", "src/")

@nox.session
def typecheck(session):
    session.install("poetry")
    session.run("poetry", "install")
    session.run("mypy", "src/", "--strict")
nox               # Run all sessions
nox -s tests      # Run only tests
nox -s lint-3.12  # Run lint on Python 3.12

8. Makefile

.PHONY: install lint format typecheck test build clean

install:
	poetry install

lint:
	poetry run ruff check src/

format:
	poetry run ruff format src/

typecheck:
	poetry run mypy src/ --strict

test:
	poetry run pytest tests/ -v --cov=src --cov-report=term-missing

build: lint typecheck test
	poetry build

clean:
	rm -rf dist/ .pytest_cache/ .mypy_cache/ .ruff_cache/
	find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true

.PHONY: docker-build
docker-build:
	docker build -t mon-app .

.PHONY: docker-run
docker-run:
	docker-compose up -d

9. Diagramme CI/CD

Diagramme en cours de génération...

10. Bonnes pratiques

  1. Toujours utiliser Poetry pour la gestion des dependances
  2. Ruff pour le linting ET le formatting
  3. Mypy --strict obligatoire dans la CI
  4. Pre-commit hooks pour capturer les erreurs localement
  5. Docker multi-stage pour des images legeres
  6. Testing matrix multi-version Python
  7. Publier automatiquement sur PyPI avec les tags git