Python
Journaux liées à cette note :
J'ai découvert AIChat, alternative à llm cli
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.
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) :
- AI Tools & MCP
- AI Agents
- LLM Arena
- La partie RAG semble plus avancée
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
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
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
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
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
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 07 mai 2025 à 14:22
Ici dans le code source de Open WebUI, #JaiDécouvert Pyodide :
Pyodide is a Python distribution for the browser and NodeJS based on WebAssembly.
Journal du mercredi 12 février 2025 à 10:44
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 :
- Le premier est le plus connu "Frontend/Backend Protocol". C'est celui qui est utilisé par la commande
psqlou les librairies telles que Postgres.js (en NodeJS), Psycopg (en Python)… - Le second nommé "Streaming Replication Protocol"
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.
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.
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
Un ami m'a fait découvrir uv (https://github.com/astral-sh/uv).
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 :
- L'installation de Python : https://docs.astral.sh/uv/guides/install-python/
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
- Système pour lancer des scripts Python : https://docs.astral.sh/uv/guides/scripts/
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/.
- Permet de lancer des outils : https://docs.astral.sh/uv/guides/tools/
Par exemple, le linter Python ruff, exemple :
$ uv tool run ruff
- uv met à disposition des images Docker : https://docs.astral.sh/uv/guides/integration/docker/
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
Playground qui présente comment je setup un projet Python Flask en 2025
Je pense que cela doit faire depuis 2015 que je n'ai pas développé une application en Python Flask !
Entre 2008 et 2015, j'ai beaucoup itéré dans mes méthodes d'installation et de setup de mes environnements de développement Python.
D'après mes souvenirs, si je devais dresser la liste des différentes étapes, ça donnerai ceci :
- 2006 : aucune méthode, j'installe Python 🙂
- 2007 : je me bats avec setuptools et distutils (mais ça va, c'était plus mature que ce que je pouvais trouver dans le monde PHP qui n'avait pas encore imaginé composer)
- 2008 : je trouve la paie avec virtualenv
- 2010 : j'ai peur d'écrire des scripts en Bash alors à la place, j'écris un script
bootstrap.pydans lequel j'essaie d'automatiser au maximum l'installation du projet - 2012 : je me bats avec buildout pour essayer d'automatiser des éléments d'installation. Avec le recul, je réalise que je n'ai jamais rien compris à buildout
- 2012 : j'utilise Vagrant pour fixer les éléments d'installation, je suis plutôt satisfait
- 2015 : je suis radicale, j'enferme tout l'environnement de dev Python dans un container de développement, je monte un path volume pour exposer le code source du projet dans le container. Je bricole en
entrypointavec la commande "sleep".
Des choses ont changé depuis 2015.
Mais, une chose que je n'ai pas changée, c'est que je continue à suivre le modèle The Twelve-Factors App et je continue à déployer tous mes projets packagé dans des images Docker. Généralement avec un simple docker-compose.yml sur le serveur, ou alors Kubernetes pour des projets de plus grande envergure… mais cela ne m'arrive jamais en pratique, je travaille toujours sur des petits projets.
Choses qui ont changé : depuis fin 2018, j'ai décidé de ne plus utiliser Docker dans mes environnements de développement pour les projets codés en NodeJS, Golang, Python…
Au départ, cela a commencé par uniquement les projets en NodeJS pour des raisons de performance.
J'ai ensuite découvert Asdf et plus récemment Mise. À partir de cela, tout est devenu plus facilement pour moi.
Avec Asdf, je n'ai plus besoin "d'enfermer" mes projets dans des containers Docker pour fixer l'environnement de développement, les versions…
Cette introduction est un peu longue, je n'ai pas abordé le sujet principal de cette note 🙂.
Je viens de publier un playground d'un exemple de projet minimaliste Python Flask suivant mes pratiques de 2025.
Voici son repository : mise-python-flask-playground
Ce playground est "propulsé" par Docker et Mise.
J'ai documenté la méthode d'installation pour :
- Linux (Fedora (distribution que j'utilise au quotidien) et Ubuntu)
- MacOS avec Brew
- MS Windows avec WSL2
Je précise que je n'ai pas eu l'occasion de tester l'installation sous Windows, hier j'ai essayé, mais je n'ai pas réussi à installer WSL2 sous Windows dans un Virtualbox lancé sous Fedora. Je suis à la recherche d'une personne pour tester si mes instructions d'installation sont valides ou non.
Briques technologiques présentes dans le playground :
- La dernière version de Python installée par Mise, voir .mise.toml
- Une base de données PostgreSQL lancé par Docker
- J'utilise named volumes comme expliqué dans cette note : 2024-12-09_1550
- Flask-SQLAlchemy
- Flask-Migrate
- Une commande
flask initdbavec Click pour reset la base de données - Utiliser d'un template Jinja2 pour qui affiche les
usersen base de données
Voici quelques petites subtilités.
Dans le fichier alembic.ini j'ai modifié le paramètre file_template parce que j'aime que les fichiers de migration soient classés par ordre chronologique :
[alembic]
# template used to generate migration files
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s
20250205_124639_users.py
20250205_125437_add_user_lastname.py
Ici le port de PostgreSQL est généré dynamiquement par docker compose :
postgres:
image: postgres:17
...
ports:
- 5432 # <= ici
Avec cela, fini les conflits de port quand je lance plusieurs projets en même temps sur ma workstation.
L'URL vers le serveur PostgreSQL est générée dynamiquement par le script get_postgres_url.sh qui est appelé par le fichier .envrc. Tout cela se passe de manière transparente.
J'initialise ici les extensions PostgreSQL :
def init_db():
db.drop_all()
db.session.execute(db.text('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'))
db.session.execute(db.text('CREATE EXTENSION IF NOT EXISTS "unaccent"'))
db.session.commit()
db.create_all()
et ici dans la première migration :
def upgrade():
op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
op.execute('CREATE EXTENSION IF NOT EXISTS "unaccent";')
op.create_table('users',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('firstname', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)