[FIX] requirement: borner Textual, et déclarer lxml plutôt que l'hériter

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>
This commit is contained in:
Mathieu Benoit 2026-08-03 08:03:58 +00:00
parent 55f6127a72
commit a743c4462a
2 changed files with 17 additions and 2 deletions

View file

@ -24,7 +24,13 @@ openai
humanize humanize
requests requests
urwid urwid
textual # Borne recopiée de script/todo/textual_setup.py (TEXTUAL_SPEC) : les écrans TUI
# sont écrits pour Textual 8, qui casse son API entre majeures. Modifier les deux.
textual>=8,<9
# Dépendance directe : les outils d'analyse comparent des arbres XML avec lxml.
# Il n'arrivait que par pykeepass / openupgradelib / odoo-module-migrator, donc
# un jour où l'un d'eux s'en passe, il disparaît sans que rien ne le réclame.
lxml
python-dotenv python-dotenv
python-dateutil python-dateutil
unidecode unidecode

View file

@ -29,6 +29,15 @@ except Exception: # pragma: no cover - repli si i18n indisponible
return key return key
# Borne de version, à UN seul endroit. Les écrans TUI du dépôt sont écrits pour
# Textual 8 ; la bibliothèque casse son API entre majeures. « pip install
# textual » sans borne installerait la majeure suivante et casserait les écrans
# sans prévenir, alors même que requirement/erplibre_require-ments.txt la borne.
# Les deux chemins d'installation doivent dire la même chose : ce littéral est
# recopié dans le fichier de requirements, avec un commentaire qui pointe ici.
TEXTUAL_SPEC = "textual>=8,<9"
def available() -> bool: def available() -> bool:
"""Textual est-il importable maintenant ?""" """Textual est-il importable maintenant ?"""
return importlib.util.find_spec("textual") is not None return importlib.util.find_spec("textual") is not None
@ -47,7 +56,7 @@ def install_command():
verrait jamais. Hors venv, « --user » contourne le refus des verrait jamais. Hors venv, « --user » contourne le refus des
distributions dont l'environnement est « externally managed » (PEP 668). distributions dont l'environnement est « externally managed » (PEP 668).
""" """
cmd = [sys.executable, "-m", "pip", "install", "textual"] cmd = [sys.executable, "-m", "pip", "install", TEXTUAL_SPEC]
if not in_venv(): if not in_venv():
cmd.insert(4, "--user") cmd.insert(4, "--user")
return cmd return cmd