In the first two parts of this series, we have: - Established a functional CI/CD pipeline for a Python application using GitHub Actions and PyPI. - Industrialized this pipeline with multi-version testing, a gradual release via Test PyPI, and quality and security tools.

In this third part, we will go.beyond just publishing on PyPIto build a complete, robust, and professional CI/CD pipeline with : - Poetryfor a modern packaging and optimized dependency management. - Dockerto create reproducible and cross-platform builds. - Conditional publicationto manage scenarios such as release candidates. - Automation tools(Renovate, Dependabot) to keep the pipeline up to date effortlessly.

Integration with Poetry

Poetry replaces the old packaging tools (setup.py, requirements.txt) by centralizing the management of dependencies and the build in`pyproject.toml`.

Installation of Poetry

# Installer Poetry
curl -sSL https://install.python-poetry.org | python3 -
# Vérifier la version
poetry --version

Project initialization

# Initialiser un nouveau projet avec Poetry
poetry init
# Suivre l'assistant pour renseigner : nom, version, description, licence, dépendances.

This generates a file`pyproject.toml` :

[tool.poetry]
name = "playlist-downloader"
version = "0.1.0"
description = "CLI tool for managing YouTube playlists"
authors = ["Christophe Hérolivier <[email protected]>"]

[tool.poetry.dependencies]
python = ">=3.8"
typer = "^0.9.0"
yt-dlp = "^2023.7.6"
google-api-python-client = "^2.0.0"
google-auth-oauthlib = "^1.0.0"

[tool.poetry.group.dev.dependencies]
pytest = "^7.0"
mypy = "^1.0"
bandit = "^1.7"
safety = "^2.3"
black = "^23.0"
ruff = "^0.1"

Adding and installing dependencies

poetry add typer yt-dlp google-api-python-client google-auth-oauthlib
poetry add --group dev pytest mypy black bandit safety ruff

Publication with Poetry

Poetry natively manages publishing:

# Publication sur Test PyPI
poetry publish --build --repository test-pypi

# Publication sur PyPI
poetry publish --build

This command automatically uses the information present in`pyproject.toml`.

Reproducible builds with Docker

To ensure identical executions in development, CI/CD and production, Docker integrates perfectly with Poetry.

Dockerfile example

FROM python:3.11-slim

WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry
RUN poetry install --no-root --only main

COPY . .

CMD ["poetry", "run", "python", "cli.py"]

This guarantees: - A frozen Python environment. - Locked dependencies via`poetry.lock`. - An executable image on any system supporting Docker

Integration in GitHub Actions

name: Docker Build

on:
  push:
    branches: [main]

jobs:
  build-docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build Docker image
        run: docker build -t ghcr.io/${{ github.repository }}:latest .
      - name: Push Docker image
        run: docker push ghcr.io/${{ github.repository }}:latest

Conditional publication

In a professional pipeline, one must be able to publish only in certain cases: - Release candidates to Test PyPI. - Stable versions to PyPI. - Builds Docker triggered only for`main`.

- name: Publish to Test PyPI
  if: contains(github.ref, '-rc')
  run: poetry publish --build --repository test-pypi

- name: Publish to PyPI
  if: startsWith(github.ref, 'refs/tags/v')
  run: poetry publish --build

This approach avoids accidental posts.

Dependency automation with Renovate and Dependabot

To prevent your dependencies from becoming obsolete, integrate automatic update tools.

Dependabot

version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Dependabot opens PRs every week to update Python and GitHub Actions dependencies.

Renovate

{
  "extends": ["config:base"],
  "packageRules": [
    {
      "matchManagers": ["pip"],
      "groupName": "python-dependencies",
      "schedule": ["before 6am on monday"]
    }
  ]
}

Renovate allows for finer control: dependency grouping, scheduling, and advanced rules.

Diagrams PlantUML

Use Case – Advanced CI/CD

@startuml
actor Developer
actor GitHub as "GitHub Actions"
actor PyPI
actor DockerRegistry as "Docker Registry"
actor Automation as "Renovate/Dependabot"

Developer --> (Push code)
(Trigger CI) --> GitHub
GitHub --> (Run Tests & Lint)
GitHub --> (Build Docker Image)
GitHub --> DockerRegistry
GitHub --> (Publish to Test PyPI)
GitHub --> (Promote to PyPI)
Automation --> (Update Dependencies)
@enduml

Sequence – Conditional Publication

@startuml
!theme vibrant
left to right direction
Developer -> GitHub: Push tag v1.2.3-rc
GitHub -> CI: Run tests
CI -> CD: Check release type
CD -> Test PyPI: Publish RC
Developer -> GitHub: Push tag v1.2.3
GitHub -> CD: Publish to PyPI
CD -> Docker Registry: Push Docker image
@enduml

States – CI/CD Pipeline

@startuml
[*] --> Idle
Idle --> CI_Running : push
CI_Running --> CI_Success : tests ok
CI_Running --> CI_Failed : tests fail
CI_Success --> CD_Running : tag detected
CD_Running --> CD_Success : publish ok
CD_Running --> CD_Failed : error
CD_Success --> [*]
@enduml

Deployment – CI/CD Architecture

@startuml
node "Developer Machine" {
  component "Git Client"
}

node "GitHub Actions" {
  component "CI Workflow"
  component "CD Workflow"
}

node "Package Repositories" {
  artifact "Test PyPI"
  artifact "PyPI"
  artifact "Docker Registry"
}

"Developer Machine" --> "GitHub Actions"
"GitHub Actions" --> "Test PyPI"
"GitHub Actions" --> "PyPI"
"GitHub Actions" --> "Docker Registry"
@enduml

Conclusion

In this third part, we saw how: - Modernize Python packaging with Poetry. - Ensure reproducible builds via Docker. Set up a conditional publication. - Automate dependency updates.

You now have a CI/CD pipelinecomplete, industrialized and secure, ready to evolve with your projects.

Related articles