Textual était déclaré sans version. Les quatre écrans TUI du dépôt sont écrits
pour la 8, et la bibliothèque casse son API entre majeures : un « pip install -U »
les casse tous sans prévenir.
Borner le fichier de requirements ne suffisait pas. install_command() faisait
« pip install textual », sans borne : « make install » aurait pris la 8, et
l'installation proposée à l'écran la majeure suivante. Deux chemins pour la même
dépendance, qui ne disent pas la même chose. La borne vit donc dans une seule
constante, TEXTUAL_SPEC, que install_command() utilise, et que le fichier de
requirements recopie avec un commentaire qui pointe dessus.
La borne ne s'applique qu'à l'installation : ensure() vérifie « est-ce
importable », pas « à quelle version ». Un Textual 9 déjà présent passe, et c'est
volontaire — refuser de démarrer sur une version qui marche peut-être serait pire
que le problème.
lxml devient une dépendance déclarée. Il n'arrivait que par pykeepass,
openupgradelib et odoo-module-migrator ; le jour où l'un d'eux s'en passe, il
disparaît d'un venv sans que rien ne le réclame. Sans borne : cyclonedx-python-lib
demande déjà « lxml >=4,<7 », en ajouter une seconde n'apporterait qu'un conflit
possible.
Vérifié : install_command() porte la borne, l'insertion de « --user » hors venv
reste au bon rang, et le Textual installé (8.2.8) la satisfait.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
« Installez textual pour le TUI (pip) » left the user to work out which pip,
which interpreter, which package — and it was printed from eight different
places. Every TUI screen now asks:
⚠ Textual est nécessaire pour cet écran.
L'installer maintenant ? (O/n, défaut : oui)
The command targets sys.executable, the interpreter that will have to import
it — installing a distribution package would land somewhere the venv never
looks. Outside a venv it adds --user, which is also what gets past the refusal
of distributions whose environment is externally managed (PEP 668).
Two details that decide whether this works at all:
· importlib.invalidate_caches() after installing. A failed import is
remembered, so without it textual stays « missing » for the rest of the
session despite having just been installed.
· a pip that exits non-zero never reports success. The check is « is it
importable NOW », not « did pip return 0 », and the failure suggests the
distribution package by name.
It lives in its own module rather than as a TODO method: todo_upgrade needs it
too and is imported BY todo, so putting it there would close a cycle.
One call site is deliberately NOT converted. The statistics screen only reads
files; it never touches Textual, and its old message claimed otherwise. An
import failure there is a real module problem and now says so.
Verified: already-present asks nothing and runs nothing; refusal installs
nothing; pip failing returns False and points at python3-textual; pip
succeeding returns True; prompt=False reports without asking. Then each of the
four TUI entries — telemetry, deploy form, deploy progress, migration resume —
offers and falls back cleanly on refusal, while the statistics screen stays
silent.
Also caught by those tests: « import importlib » alone does not expose
importlib.util, so availability could not be checked at all.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>