Python


Journaux liées à cette note :

Enlever des couches : mon chemin de Make vers de simples scripts Bash #Doctrine, #dev-kit, #software-engineering, #mise

J'estime le temps de lecture de cette note à 10 minutes

Je profite d'une discussion entre deux amis au sujet de just et make pour partager mon point de vue et mes pratiques sur ce sujet.

Je tiens tout de suite à préciser que c'est un sujet qui me tient à cœur, parce qu'il m'irrite fortement : j'ai lutté pendant des années avec la mauvaise Developer eXperience de l'outil make dans mes projets, et je continue à voir tant de développeurs s'entêter à utiliser un outil dont la raison d'être est la résolution de dépendances basée sur les timestamps de fichiers — or, il me semble que cette fonctionnalité n'est probablement jamais utilisée, sauf dans les projets C ou C++.

Tout d'abord, je souhaite commencer par lister quelques éléments de complexité des makefile.

Quelques exemples de complexité accidentelle apportée par Make

  • Chaque ligne est un sous-shell indépendant et ça c'est super pénible, exemple :
# Le "cd" n'a aucun effet sur la ligne suivante
broken-cd:
	cd /tmp
	ls    # ← liste le répertoire original, pas /tmp

Une solution de contournement est d'utiliser des backslashes pour continuer la ligne, mais cela complique la lisibilité :

build:
    cd /tmp && \
    ls && \
    echo "done"
  • Par défaut, Make utilise sh et non pas bash ou zsh et ne supporte pas la construction [[ ]], les tableaux, etc qui cassent silencieusement. Exemple de code qui ne fonctionne pas :
check-env:
	@if [[ -z "$(ENV)" ]]; then \
		echo "ENV is not set"; \
		exit 1; \
	fi
	@echo "ENV = $(ENV)"
  • L'indentation du contenu des rules doit être une tabulation (pas des espaces)
  • Les $ doivent être doublés pour le shell, sinon make l'interprète, exemple :
greet:
	@MSG="Hello $(APP_NAME)" && \    # $(APP_NAME) → résolu par make ✓
	echo $$MSG                        # $$MSG → variable bash du sous-shell ✓
	@echo $(MSG)                      # $(MSG) → make cherche "MSG" → vide ! ✗

# Piège 3 : le $ doit être doublé pour bash, sinon make l'interprète
list:
	@for i in 1 2 3; do echo $$i; done   # ✓ correct
	@for i in 1 2 3; do echo $i; done    # ✗ make interprète $i → vide
  • Le préfixe "-" permet d'ignorer les erreurs d'une commande est une convention propre à makefile, sans équivalent dans Bash
clean:
	-rm -rf build/    # sans "-", make s'arrête si build/ n'existe pas
	-docker rmi $(APP_NAME)
  • Par défaut, make affiche chaque commande avant de l'exécuter. Pour le supprimer, il faut préfixer chaque ligne avec @ :
build:
    echo "Building..."      # affiche : echo "Building..." puis : Building...
    @echo "Building..."     # affiche seulement : Building...

Du coup, dans la pratique, on se retrouve à préfixer toutes les lignes avec @ :

deploy:
    @echo "Deploying..."
    @docker build -t myapp .
    @kubectl apply -f k8s/
  • Nécessité d'ajouter des .PHONY

Pourquoi tant de difficulté pour lancer de simples commandes ?

À chaque fois que je rencontrais des problèmes avec make, je culpabilisais. Je me disais que c'était de ma faute, que tout le monde utilisait make et qu'il devait y avoir une bonne raison. Je voyais bien que mon expérience de développeur (DX) était mauvaise, que je n'avais pas besoin de résolution de dépendance… mais je me disais que je devais utiliser make, et que mon erreur était de ne pas avoir pris le temps de lire sa documentation.
Alors je replongeais régulièrement dans les 16 chapitres de la documentation de make et je me demandais pourquoi je devais apprendre la syntaxe de make en plus de celle de bash. Et au final, je finissais même par détester Bash en plus de make.

Pourquoi tout cela était-il aussi compliqué, alors que je voulais seulement lancer de simples commandes et intégrer quelques conditions dans mes scripts ?

La recherche d'alternative

En 2018, la douleur des makefiles revenait souvent dans nos discussions en interne, au sein de mon équipe, et on cherchait régulièrement des alternatives. Parmi les pistes étudiées :

  • Task, en Golang, apparu en 2017 — je l'ai testé et ai fortement envisagé de l'adopter
  • Pydoit, en Python, démarré en 2008
  • Rake, en Ruby, lancé en 2003 — alors que je ne maîtrise pas le Ruby et que, par goût personnel, j'évite au maximum d'intégrer ce type de projet dans mes stacks
  • CMake, qu'un collègue avait exploré

Fin 2018, la prise de conscience

Fin 2018, je ne me souviens plus pour quelle raison, en parcourant le code source de Terraform, je suis tombé sur le dossier scripts/) de Terraform.

├── ...
├── Makefile
├── ...
├── scripts
│   ├── build.sh
│   ├── changelog-links.sh
│   ├── changelog.sh
│   ├── copyright.sh
│   ├── debug-terraform
│   ├── exhaustive.sh
│   ├── gofmtcheck.sh
│   ├── gogetcookie.sh
│   ├── goimportscheck.sh
│   ├── staticcheck.sh
│   ├── syncdeps.sh
│   └── version-bump.sh
└── ...

Et un fichier makefile minimaliste qui lance simplement des fichiers Bash :

$ cat Makefile
protobuf:
        go run ./tools/protobuf-compile .

fmtcheck:
        "$(CURDIR)/scripts/gofmtcheck.sh"

importscheck:
        "$(CURDIR)/scripts/goimportscheck.sh"

staticcheck:
        "$(CURDIR)/scripts/staticcheck.sh"

exhaustive:
        "$(CURDIR)/scripts/exhaustive.sh"

[...snip...]

Et, ce jour-là, je me suis senti très stupide d'avoir passé tant de temps à trouver une solution qui était en réalité très simple, à portée de main !

Je pense aussi que le fait que cette méthode ait été utilisée par Mitchell Hashimoto en personne, dans Terraform, m'a probablement donné une sorte d'autorisation d'utiliser cette approche.

J'ai compris que je pouvais simplement me passer de make.

2019 à 2026 : utilisation de simples scripts Bash

Suite à ma prise de conscience de fin 2018, j'ai appliqué un principe que je nomme "enlever des couches" : plutôt que d'ajouter une technologie pour résoudre un problème, réfléchir à ce que peut enlever pour réduire la complexité — et, par la même, peut-être supprimer le problème lui-même. C'est une vigilance consciente contre le biais cognitif du cargo cult : la tendance à reproduire des pratiques par habitude ou imitation, sans vraiment les comprendre ni les justifier.

En appliquant ce principe, il m'a semblé que je pouvais simplement enlever make — sans avoir à le remplacer par un outil tel que Task qui aurait été une couche supplémentaire dont je n'avais probablement pas besoin.

J'ai même pris conscience qu'en plaçant tous mes scripts dans un dossier ./scripts/, je bénéficiais nativement de l'autocomplétion de mes commandes par le filesystem — tout comme ce que proposait aussi make.

Par exemple :

  • make up devenait ./scripts/up.sh
  • make build devenait ./scripts/build.sh
  • make clean devenait ./scripts/clean.sh
  • etc.

Et surtout, je pouvais désormais pleinement me concentrer sur ma maîtrise de Bash pour améliorer l'expérience de développeur (DX) de mes kits de développement.

L'astuce du cd automatique

Pour exécuter ces commandes sans se préoccuper du dossier courant, j'ai ajouté la ligne suivante au début de chaque script :

cd "$(dirname "$0")/../"

Cela permet de lancer ./scripts/up.sh depuis la racine du projet comme depuis un sous-répertoire (cd subproject && ../scripts/up.sh), et le script s'exécutera toujours depuis le dossier parent de scripts.

Voici le boilerplate code qu'utilise la quasi-totalité de mes scripts :

#!/usr/bin/env bash
set -e

cd "$(dirname "$0")/../"

...

Mais "make" est un standard ?

L'argument revient souvent : « make est un standard, tout le monde le connaît, un nouveau contributeur saura immédiatement quoi faire. ».

Seulement voilà : la partie « standard » de make, celle que tout le monde utilise réellement, c'est make <target> — et c'est exactement ce que fait ./scripts/<target>.sh, sans syntaxe supplémentaire, sans pièges de tabulations, sans résolution de dépendances par timestamps dont on ne veut probablement pas.

Il me semble que cet argument touche au cargo cult : on place un Makefile à la racine du projet par habitude, sans vraiment tirer parti des capacités qui justifient l'existence même de make.

De plus, si l'on parle de standard, bash est probablement au moins aussi universel que make. Et écrire un script bash est sans doute plus accessible pour un développeur que d'apprendre les subtilités du makefile ($ doublés, sous-shells, .PHONY, @, -, etc.).

Il me semble donc que l'argument du "standard" est légitime — mais mon choix de ne plus utiliser make n'est pas un obstacle pour autant : si ./scripts/up.sh est clairement documenté dans le README, je pense que n'importe quel développeur comprendra sans difficulté son usage et sa fonction. Pas besoin de connaître make pour exécuter un script bash dont le nom est explicite.

Retour d'expérience : 4 ans, de 2 à 10 développeurs

J'ai utilisé cette méthode avec succès pendant 4 ans, en passant de 2 à 10 développeurs, sans que j'aie constaté de friction. À ma connaissance, personne n'a eu de difficulté avec ce système d'exécution des scripts et, il me semble, personne ne m'a suggéré de les remplacer par autre chose.

Et Just, alors ?

J'ai découvert just en 2022, puis je l'ai vu gagner en popularité à partir de 2023 (199 commentaires sur HackerNews) :

J'ai failli me laisser tenter. Mais je n'avais aucune douleur avec mes scripts, j'étais pleinement satisfait — et conformément au principe d'enlever des couches, ajouter une couche supplémentaire n'avait aucun intérêt.

D'autre part, just est riche en fonctionnalités et sa documentation est déjà importante : il me semble que c'est beaucoup à apprendre pour un outil dont je n'ai pas besoin.

Et puis j'ai craqué pour Mise Tasks

Je suis un grand utilisateur de Mise et dernièrement ce projet a ajouté la fonctionnalité Tasks. Et au grand désespoir de mon ami Alexandre — qui me fait régulièrement remarquer cette contradiction —, j'ai craqué, j'ai commencé à utiliser cette fonctionnalité en janvier 2026. Je n'ai pas d'argument solide à avancer ; sans doute un mélange de curiosité et d'affection pour Mise.

Contrairement à just, la fonctionnalité task de Mise reste minimaliste et est compatible avec mon paradigme : le if dans l'exemple ci-dessous est du Bash standard — pas besoin de $$, de \, de sous-shells par ligne. J'écris du Bash et rien d'autre.

D'autre part, Mise est déjà au cœur de mes development kit, je l'utilise à depuis 2023 à la place de Asdf pour installer du tooling de développement. Depuis 1 an, j'ai remplacé direnv par Mise. Par conséquent, ce n'est pas une dépendance en plus à ajouter à mes projets.

Mise task supporte trois syntaxes pour définir des tasks.

Dans le fichier .mise.toml, en ligne simple :

[tasks.build]
run = "pnpm run build"

Ou en bloc multiligne :

[tasks.clean]
run = """
if [ "$1" = "--with-lint" ]; then
  mise run lint
fi
pnpm run test
"""

Ou alors, via des scripts dans le dossier mise-tasks/, par exemple mise-tasks/build :

#!/usr/bin/env bash
#MISE description="Build the web application"
pnpm run build

Voici un extrait de Mise tasks mises en œuvre dans un vrai projet :

$ mise task
Name                           Description
build-cli                      Build the sklein-devbox CLI application
build-image                    Build the sklein-devbox container image
[...snip...]
up                             Start the devbox container

Le code source est consultable ici : https://github.com/stephane-klein/sklein-devbox/blob/main/.mise.toml

C'est important pour moi de préciser que j'ai bien conscience que Mise Tasks est une couche de plus — et que ça contredit ma doctrine « enlever des couches ».
Dans un projet d'équipe, je partirais par défaut sur des scripts Bash simples, sans Mise task. Je n'intégrerais Mise task que s'il y a un consensus fort de l'équipe — et je ne l'imposerais pas.

Remerciements

Je remercie mes deux amis de m'avoir motivé à écrire cette note — c'est un sujet que je souhaitais traiter depuis 2019 (j'avais même créé une issue à ce sujet dans mon ancien backlog).

J'ai découvert AIChat, alternative à llm cli #artificial-intelligence, #llm, #cli, #JaiDécouvert, #JaimeraisUnJour

Dans ce thread, #JaiDécouvert AIChat (https://github.com/sigoden/aichat), une alternative à llm (cli) codée en Rust.

AIChat is an all-in-one LLM CLI tool featuring Shell Assistant, CMD & REPL Mode, RAG, AI Tools & Agents, and More.

source

En parcourant le README.md, j'ai l'impression que AIChat propose une meilleure UX que llm (cli).

Je constate aussi que AIChat offre plus de fonctionnalités que llm (cli) :

Ce qui attire le plus mon attention, c'est le sous-projet llm-functions qui, d'après ce que j'ai lu, permet de créer très facilement des tools en Bash, Python ou Javascript. Exemples :

J'ai hâte de tester ça 🙂 ( #JaimeraisUnJour ).

Par contre, llm-functions ne semble pas encore permettre la configuration de Remote MCP server.

Je suis aussi intéressé par cette issue : TUI for managing, searching, and switching between chat sessions.

Un point qui m'inquiète un peu : le projet semble peu actif ces derniers mois.

Script d'analyse des coûts Aider à partir de l'historique #astuce, #CodeAssistant

J'ai cherché une fonctionnalité pour calculer le coût total de l'utilisation d'Aider sur un projet. Je n'ai rien trouvé dans la documentation ni dans les issues du projet Aider.

Je sais qu'Aider enregistre toutes les informations de session dans .aider.chat.history.md.

Ce fichier contient des lignes du type : "Tokens: 49k sent, 1.9k received. Cost: $0.17 message, $1.68 session".

À partir de ces informations, j'ai généré avec Claude Sonnet 4.5 le script Python aider_cost_analyzer.py.

Installation sous Fedora :

$ mkdir -p ~/.local/bin
$ curl -o ~/.local/bin/aider_cost_analyzer.py https://gist.githubusercontent.com/stephane-klein/3b3808c1b03b7e6ddfc4b22b69fdf776/raw/4262faec0cb2f468ea6b6d8751339b8e8497f005/aider_cost_analyzer.py
$ chmod +x ~/.local/bin/aider_cost_analyzer.py

Exemple d'utilisation :

$ aider_cost_analyzer.py .
Analyzing 1 history file(s) in '.':

.aider.chat.history.md:
  Messages: 30
  Tokens sent: 639,200 | received: 65,613
  Cost: $1.86

============================================================
Total tokens sent: 639,200
Total tokens received: 65,613
Total cost: $1.86

J'ai posté l'issue suivante dans le repository aider-ce : "Calculate cumulative token usage and cost across all Aider sessions".

J'ai découvert Adminer, alternative à PhpMyAdmin et je me suis intéressé à OPCache #php, #mysql, #WebDev, #JaiDécouvert

En travaillant sur un playground d'étude de Podman Quadlets, dans le README.md de l'image Docker mariadb, #JaiDécouvert le projet Adminer (https://www.adminer.org) qui semble être l'équivalent de PhpMyAdmin, mais sous la forme d'un fichier unique.

Je découvre aussi que contrairement à PhpMyAdmin, Adminer n'est pas limité à Mysql / MariaDB, il supporte aussi PostgreSQL.

En regardant le dépôt GitHub d'Adminer, je découvre que le gros fichier PHP de 496 kB est le résultat de la concaténation de nombreux fichiers php.
Ça me rassure, parce que je me demandais comment l'édition d'un fichier unique de cette taille pouvait être humainement gérable.

Je trouve astucieux ce mode de déploiement d'un projet PHP sous forme d'un seul fichier qui me fait penser à la méthode Golang. Cependant, je me pose des questions sur la performance de cette technique étant donné que PHP fonctionne en mode process-per request (CGI), ce qui signifie que ce gros fichier PHP est interprété à chaque action sur la page 🤔.

En creusant un peu le sujet avec Claude Sonnet 4.5, je découvre que depuis la version 5.5 de PHP, OPCache améliore significativement la vitesse des requêtes PHP, sans pour autant atteindre celle de Golang, NodeJS, Python ou Ruby qui utilisent des serveurs HTTP intégrés. La consommation mémoire reste supérieure dans des conditions d'implémentation comparables.
Avec OPCache, Adminer semble rester performant malgré l'utilisation d'un fichier unique.

Script qui permet de transformer automatiquement du texte en anglais en fichier audio #anglais, #astuce

J'ai généré avec Claude Sonnet 4.5 le script Python text_to_audio.py. Il me permet de transformer automatiquement du texte en anglais en fichier audio mp3.
J'intègre ensuite ces fichiers dans mes carte-mémoire Anki pour travailler mon anglais.

Installation sous Fedora :

$ mkdir -p ~/.local/bin
$ curl -o ~/.local/bin/text_to_audio.py https://gist.githubusercontent.com/stephane-klein/1406e746f0253956062d4adff7a692bd/raw/8571cdd91cae8ebcd208435daacf431cfc1cd353/text_to_audio.py
$ chmod +x ~/.local/bin/text_to_audio.py

Exemple d'utilisation :

$ text_to_audio.py "Reinforcement Learning from Human Feedback"
Downloading audio for: 'Reinforcement Learning from Human Feedback'
Language: en-GB
URL: https://translate.google.com/translate_tts?ie=UTF-8&tl=en-GB&client=tw-ob&q=Reinforcement+Learning+from+Human+Feedback
Saving to: /home/stephane/english_audio/2025-12-01_Reinforcement_Learning_from_Human_Feedback.mp3

✓ Successfully downloaded to '/home/stephane/english_audio/2025-12-01_Reinforcement_Learning_from_Human_Feedback.mp3'
File size: 28224 bytes

J'utilise actuellement un endpoint HTTP de Google Translator pour générer les fichiers audio, en attendant de trouver une alternative plus open source / communautaire.

Journal du jeudi 14 août 2025 à 11:20 #javascript

J'ai cherché en Javascript, l'équivalent de la fonction inspect.cleandoc de la librairie standard de Python (voir note 2025-06-13_1437).

J'ai trouvé la librairie string-dedent.

Exemple d'utilisation (source) :

const info = await transporter.sendMail({
	from: `"${process.env.FROM_NAME}" <${process.env.FROM_EMAIL}>`,
	to: toEmail,
	subject: 'Nouvelle et projet',
	text: dedent`
		Salut ${firstname},  

		Comment vas-tu ? Ça fait un bail qu'on ne s'est pas parlé ! J'espère que tu vas bien et que tout se passe pour le mieux de ton côté. De mon côté, ça tourne plutôt bien. Je suis toujours sur Fedora, et j'ai pas mal bidouillé avec la ligne de commande ces derniers temps. J'ai réussi à automatiser quelques trucs qui me simplifient la vie, c'est toujours un plaisir de pouvoir tout configurer comme on veut.  

		En ce moment, je suis replongé dans Neovim. J'essaie de configurer un workflow un peu plus efficace pour le développement. J'ai découvert quelques plugins géniaux qui me font gagner pas mal de temps. C'est un peu chronophage de tout configurer, mais le résultat en vaut la peine. Plus besoin de sortir de l'éditeur pour pas mal de tâches !  

		Quoi de neuf de ton côté ? Des projets intéressants en cours ? On devrait essayer de se voir bientôt pour en discuter autour d'un verre. Dis-moi ce que tu en penses !  

		${codeId}

		À bientôt,  
		Stéphane
	`,
	html: dedent`
		<p>Salut ${firstname},</p>

		<p>Comment vas-tu ? Ça fait un bail qu'on ne s'est pas parlé ! J'espère que tu vas bien et que tout se passe pour le mieux de ton côté. De mon côté, ça tourne plutôt bien. Je suis toujours sur Fedora, et j'ai pas mal bidouillé avec la ligne de commande ces derniers temps. J'ai réussi à automatiser quelques trucs qui me simplifient la vie, c'est toujours un plaisir de pouvoir tout configurer comme on veut.</p>

		<p>En ce moment, je suis replongé dans Neovim. J'essaie de configurer un workflow un peu plus efficace pour le développement. J'ai découvert quelques plugins géniaux qui me font gagner pas mal de temps. C'est un peu chronophage de tout configurer, mais le résultat en vaut la peine. Plus besoin de sortir de l'éditeur pour pas mal de tâches !</p>

		<p>Quoi de neuf de ton côté ? Des projets intéressants en cours ? On devrait essayer de se voir bientôt pour en discuter autour d'un verre. Dis-moi ce que tu en penses !</p>

		<p>${codeId}</p>

		<p>À bientôt,<br>
		Stéphane</p>
	`
});

Journal du vendredi 13 juin 2025 à 14:37 #JaiDécouvert, #python

Je viens de découvrir la fonction inspect.cleandoc de la librairie standard de Python.

Exemple :

# foobar.py
from inspect import cleandoc

def foobar(body):
    print(body)


foobar(
    body=cleandoc("""
        My text, with indentation

        - item 1
            - item 1.1
        - item 2

        Last line
    """)
)
$ python foobar.py
My text, with indentation

- item 1
    - item 1.1
- item 2

Last line

Je trouve cela très pratique pour améliorer la lisibilité du code source sans générer des indentations qui ne devraient pas être présentes dans les données.


Voir aussi, string-dedent, l'équivalent en Javascript : 2025-08-14_1120.

Journal du mercredi 12 février 2025 à 10:44 #PostgREST, #backup, #JaiDécouvert

En travaillant sur le projet Projet 23 - "Ajouter le support pg_basebackup incremental à restic-pg_dump-docker", j'ai découvert que PostgreSQL propose deux protocoles de communication :

Sans vraiment comprendre, en 2020, j'avais activé le protocole streaming replication dans ce POC postgresql-streaming-replication-playground. Mais je n'avais jamais pris conscience de l'existence de ce second protocole.

Les développeurs de PostgreSQL semblent avoir décidé de créer un second protocole parce qu'ils n'ont pas du tout le même objectif.
Streaming Replication Protocol est optimisé dans la transmission des WAL et des snapshots (copie de l'intégralité du dossier PGDATA).

Le protocole Streaming Replication Protocol est entre autres utilisé par pg_basebackup, barman, pgBackRest, ou Patroni.

Comment activer Streaming Replication Protocol ?

Les images Docker Postgres setup par défaut le fichier pg_hba.conf suivant :

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            trust
# IPv6 local connections:
host    all             all             ::1/128                 trust
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     trust
host    replication     all             127.0.0.1/32            trust
host    replication     all             ::1/128                 trust
host all all all scram-sha-256

J'ai compris que par défaut, toutes ces lignes configurent l'accès au Frontend/Backend Protocol.
Seules les lignes qui contiennent replication configurent l'accès au Streaming Replication Protocol.

Pour permettre l'accès au Streaming Replication Protocol par réseau TCP/IP en dehors du container Docker il est nécessaire d'ajouter la ligne suivante :

host    replication     all             all                     scram-sha-256

Suite à cette découverte, j'ai repensé à :

Pour faire face à ce problème, j'ai exploré fin 2023 une solution basée sur pgBackRest : Implémenter un POC de pgBackRest.
Je suis plus ou moins arrivé au bout de ce POC mais je n'ai pas été satisfait du résultat.
Je n'ai pas réussi à configurer pgBackRest en "pure Docker sidecar".
De plus, j'ai trouvé la restauration du backup difficile à exécuter.

source

et je me suis demandé si mon échec de configuration de pgBackRest en Docker sidecar n'était pas seulement dû au fait que je n'avais pas activé Streaming Replication Protocol 🤔.

La réponse semble être "oui" et "non".

Je suis tombé sur l'issue suivante : pgbackrest with postgresql in docker.

The problem might be harder than you think unfortunately. If the pgBackRest process is running on the VM (docker host), it will try to connect to PG locally using the unix socket, not the tcp "localhost" connection.

source

Je pense comprendre que pgBackRest ne permet pas d'utiliser des INET sockets pour communiquer avec PostgreSQL.

Toutefois, je me dis que je pourrais partager le volume PGDATA avec le sidecar pgBackRest pour lui donner accès à l'Unix Socket du Streaming Replication Protocol 🤔.

Entre mon exploration de pg_basebackup, mes envies de tester barman et de continuer mon POC pgBackRest… je me dis que je ne suis pas encore au bout de ce Yak!.

Journal du mercredi 05 février 2025 à 18:32 #OnMaPartagé, #JaiDécouvert, #python, #package, #mise

Un ami m'a fait découvrir uv (https://github.com/astral-sh/uv).

An extremely fast Python package and project manager, written in Rust.

source

Je trouve cela amusant de constater que Rust prend en charge de plus en plus d'outils pour différents langages 😉.

Le projet a commencé fin 2023.

Voici un thread Hacker News de 200 commentaires à ce sujet qui date de février 2024 : Uv: Python packaging in Rust .

L'article de ce thread contient beaucoup d'éléments intéressants : https://astral.sh/blog/uv

Son nom uv semble être une référence à uvloop.

J'en ai profité pour migrer le playground mise-python-flask-playground de pip vers uv : https://github.com/stephane-klein/mise-python-flask-playground/commit/2f1678798cfc6749dcfdb514a8fe4a3e54739844.

J'ai lancé une installation et effectivement, sa rapidité est très impressionnante :

$ uv pip install -r requirements.txt
Resolved 15 packages in 245ms
Prepared 15 packages in 176ms
Installed 15 packages in 37ms
 + alembic==1.14.1
 + blinker==1.9.0
 + click==8.1.8
 + flask==3.1.0
 + flask-migrate==4.1.0
 + flask-sqlalchemy==3.1.1
 + greenlet==3.1.1
 + itsdangerous==2.2.0
 + jinja2==3.1.5
 + mako==1.3.9
 + markupsafe==3.0.2
 + psycopg2-binary==2.9.10
 + sqlalchemy==2.0.37
 + typing-extensions==4.12.2
 + werkzeug==3.1.3

uv ne propose pas seulement une amélioration de l'installation de packages Python, mais propose beaucoup d'autres choses comme :

Pour cette partie, dans un but d'unification, je continuerai à utiliser Mise pour installer une version précise de Python. De plus, Mise intègre nativement UV : https://mise.jdx.dev/mise-cookbook/python.html#mise-uv

Exemple :

$ uv run example.py

Je pense avoir compris que cela lance ce script avec les dépendances du virtual environment du projet. Un peu comme fonctionne npm, yarn ou pnpm qui permet aux scripts d'utiliser les packages présents dans ./node_modules/.

Par exemple, le linter Python ruff, exemple :

$ uv tool run ruff

J'ai un peu parcouru la documentation de pyproject.toml : https://packaging.python.org/en/latest/guides/writing-pyproject-toml/.

J'ai lu aussi la section uv - Locking environments.

Suite à ces lectures, j'ai migré le playground mise-python-flask-playground vers pyproject.toml : https://github.com/stephane-klein/mise-python-flask-playground/commit/c17216464778df4bc00bf782d5a889cb3f198051.

Je ne suis pas certain que ces commandes soient une bonne pratique :

$ uv pip compile requirements.in -o requirements.txt
$ uv pip install -r requirements.txt