<!--
  canonical-url: https://www.avonture.be/fr/blog/python-pydot
  published:     2024-12-18
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Python - Générer des flux avec pydot

> Utilisez Python et la bibliothèque pydot pour générer facilement des diagrams-as-code comme des flowcharts ETL, des diagrammes de classes et des arbres de décision. Avec installation Docker et exemples de code.

---
![Python - Générer des flux avec Pydot](/img/v2/diagrams.webp)

**TL;DR**

Cet article présente Pydot, une bibliothèque Python de diagram-as-code qui transforme du code Python en images (flux ETL, diagrammes de classes, flux de données, arbres de décision). À l'aide d'une petite image Docker, un script `.py` décrivant le diagramme est exécuté avec `docker run ... python etl.py` pour produire un PNG. D'autres exemples proviennent de la documentation Graphviz.

[Pydot](https://github.com/pydot/pydot) est un générateur **diagram-as-code** : vous écrivez des lignes de code et, grâce à un processus magique, vous pouvez restituer ce code sous forme d'image.

*Ce blog contient deux autres articles de la même famille : [Docker - Diagrams as code](/blog/docker-diagram-as-code), qui utilise la bibliothèque `diagrams` et ses icônes de fournisseurs cloud, et [Documentation as Code - Transform Your Infrastructure into Beautiful Diagrams with Python and Mermaid](/blog/docker-python-mermaid), où le diagramme est généré **depuis** votre `compose.yaml` au lieu d'être écrit à la main.*

Pour les gens comme moi, catastrophiques dès qu'il s'agit de visuel, c'est de l'or en barre.

Imaginons que vous deviez décrire un processus informatique comme un ETL (les données sont chargées, telle ou telle règle de transformation est appliquée et le résultat est chargé dans une base de données, par exemple). Vous vous voyez déjà dessiner des rectangles avec un outil comme <https://app.diagrams.net/> (anciennement draw\.io), ou vous pouvez opter pour une méthode plus intelligente.

Voyons comment dans cet article.

Pydot peut générer une image comme celle ci-dessous :

![ETL](./images/etl.webp)

D'abord, il nous faut un container Docker Python. Créez un fichier appelé `Dockerfile` avec ce contenu :

**Dockerfile**

```docker
FROM python:3.13-slim

# Prevents Python from writing pyc files.
ENV PYTHONDONTWRITEBYTECODE=1

# hadolint ignore=DL3008
RUN set -e -x \
    && apt-get update -yqq \
    && apt-get install -y --no-install-recommends graphviz \
    && apt-get clean \
    && rm -rf /tmp/* /var/lib/apt/lists/*

# hadolint ignore=DL3013
RUN pip install --no-cache-dir pydot
```

Et créez l'image en lançant `docker build --tag pydot .`.

Rien de plus n'est nécessaire, sauf... notre flux.

Pour construire une image ETL comme celle ci-dessus, créez un nouveau fichier appelé par exemple `etl.py` avec ce contenu :

**etl.py**

```python
graph = pydot.Dot(graph_type='digraph', rankdir='TB')

node_csv = pydot.Node('CSV', shape='box', style='filled', fillcolor='lightblue')
node_json = pydot.Node('JSON', shape='box', style='filled', fillcolor='lightblue')

node_extract = pydot.Node('Extract', shape='box')

node_clean = pydot.Node('Clean', shape='box')
node_transform = pydot.Node('Transform', shape='box')

node_load = pydot.Node('Load', shape='box', style='filled', fillcolor='lightgreen')

graph.add_edge(pydot.Edge(node_csv, node_extract))
graph.add_edge(pydot.Edge(node_json, node_extract))
graph.add_edge(pydot.Edge(node_extract, node_clean))
graph.add_edge(pydot.Edge(node_clean, node_transform))
graph.add_edge(pydot.Edge(node_transform, node_load))

node_enrich = pydot.Node('Get more data', shape='box')
graph.add_edge(pydot.Edge(node_transform, node_enrich))
graph.add_edge(pydot.Edge(node_enrich, node_load))

graph.write_png('etl.png')
```

Et maintenant, la partie facile : transformez le fichier Python `etl.py` en image en lançant `docker run --rm -it -v "${PWD}":/diagram -w /diagram pydot python etl.py`.

Et bingo, vous avez maintenant une image appelée `etl.png` dans votre répertoire.

## Quelques autres exemples {#some-other-examples}

Cherchez des exemples sur Internet et vous tomberez par exemple sur ce site : <https://graphviz.readthedocs.io/en/stable/examples.html>.

Vous y trouverez des exemples Python et, en plus, l'image rendue.

### Classes POO {#oop-classes}

**class\_diagram.py**

```python
graph = pydot.Dot(graph_type='digraph')

# Add nodes
node_person = pydot.Node('Person', shape='box')
node_student = pydot.Node('Student', shape='box')
node_teacher = pydot.Node('Teacher', shape='box')

# Add nodes to the graph
graph.add_node(node_person)
graph.add_node(node_student)
graph.add_node(node_teacher)

# Add edges (inheritance)
graph.add_edge(pydot.Edge(node_person, node_student))
graph.add_edge(pydot.Edge(node_person, node_teacher))

# Render the graph
graph.write_png('class_diagram.png')
```

![Diagramme de classes](./images/class_diagram.webp)

### Flux de données {#data-flow}

**data\_flow\.py**

```python
graph = pydot.Dot(graph_type='digraph')

# Data sources
source1 = pydot.Node('Source 1', shape='box')
source2 = pydot.Node('Source 2', shape='box')
graph.add_node(source1)
graph.add_node(source2)

# Process nodes
process1 = pydot.Node('Process 1', shape='oval')
process2 = pydot.Node('Process 2', shape='oval')
graph.add_node(process1)
graph.add_node(process2)

# Data store
data_store = pydot.Node('Data Store', shape='cylinder')
graph.add_node(data_store)

# Edges representing data flow
graph.add_edge(pydot.Edge(source1, process1))
graph.add_edge(pydot.Edge(source2, process1))
graph.add_edge(pydot.Edge(process1, process2))
graph.add_edge(pydot.Edge(process1, data_store))
graph.add_edge(pydot.Edge(data_store, process2))

# Render the graph
graph.write_png('data_flow_diagram.png')
```

![Flux de données](./images/data_flow_diagram.webp)

### Flux de décision {#decision-flow}

**decision\_flow\.py**

```python
graph = pydot.Dot(graph_type='digraph')

# Add nodes
node_start = pydot.Node('Start', shape='oval')
node_process1 = pydot.Node('Process 1', shape='box')
node_decision = pydot.Node('Decision', shape='diamond')
node_process2 = pydot.Node('Process 2', shape='box')
node_end = pydot.Node('End', shape='oval')

# Add edges
graph.add_edge(pydot.Edge(node_start, node_process1))
graph.add_edge(pydot.Edge(node_process1, node_decision))
graph.add_edge(pydot.Edge(node_decision, node_process2, label='Yes'))
graph.add_edge(pydot.Edge(node_decision, node_end, label='No'))
graph.add_edge(pydot.Edge(node_process2, node_end))

# Write the graph to a file
graph.write_png('flowchart.png')
```

![Flux de décision](./images/decision_flow.webp)

### Arbre de décision {#decision-tree}

**decision\_tree.py**

```python
graph = pydot.Dot(graph_type='digraph')

# Add nodes
node_root = pydot.Node('Root', shape='box')
node_left = pydot.Node('Left Child', shape='box')
node_right = pydot.Node('Right Child', shape='box')
node_left_left = pydot.Node('Left-Left Grandchild', shape='ellipse')
node_left_right = pydot.Node('Left-Right Grandchild', shape='ellipse')
node_right_left = pydot.Node('Right-Left Grandchild', shape='ellipse')
node_right_right = pydot.Node('Right-Right Grandchild', shape='ellipse')

# Add nodes to the graph
graph.add_node(node_root)
graph.add_node(node_left)
graph.add_node(node_right)
graph.add_node(node_left_left)
graph.add_node(node_left_right)
graph.add_node(node_right_left)
graph.add_node(node_right_right)

# Add edges
graph.add_edge(pydot.Edge(node_root, node_left, label='Feature 1 < 5'))
graph.add_edge(pydot.Edge(node_root, node_right, label='Feature 1 >= 5'))
graph.add_edge(pydot.Edge(node_left, node_left_left, label='Feature 2 < 3'))
graph.add_edge(pydot.Edge(node_left, node_left_right, label='Feature 2 >= 3'))
graph.add_edge(pydot.Edge(node_right, node_right_left, label='Feature 3 < 10'))
graph.add_edge(pydot.Edge(node_right, node_right_right, label='Feature 3 >= 10'))

# Render the graph
graph.write_png('decision_tree.png')
```

![Arbre de décision](./images/decision_tree.webp)
