From 55f6127a72eb0343a9b50c8e36d7e2857a1f6e2d Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 3 Aug 2026 08:03:42 +0000 Subject: [PATCH] [FIX] todo: un PostgreSQL injoignable proposait sa trace d'appel comme base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit « db --list » rendait un code de retour que personne ne lisait. Comme la sortie et l'erreur passent par le même flux (stderr=STDOUT, execute.py), les lignes de la trace d'appel devenaient les entrées du menu : [1] Traceback (most recent call last): [2] File "…/odoo-bin", line 8, in [0] Retour Choisir [1] renvoyait « Traceback (most recent call last): » comme nom de base à l'appelant, qui le passait à sa commande — sauvegarde, restauration, shell. Le code de retour était pourtant là, 120, dans la variable « status », écrasée deux lignes plus bas par la réponse de click.prompt. Le contrôle se fait donc AVANT de construire le menu : code non nul, on affiche les dernières lignes de l'erreur réelle et on rend False. Une liste vide dit aussi ce qu'elle est, au lieu d'un écran ne portant que « Retour ». Au passage, la branche de saisie invalide appelait t("cmd_not_found") — une clé absente de TRANSLATIONS, donc affichée telle quelle, en anglais technique. Les autres menus disent t("Command not found !") ; celui-ci le dit maintenant aussi. « quiet=True » s'ajoute parce que la sortie brute n'a plus de raison d'être vue : le menu affiche la liste, et en cas d'échec c'est l'erreur qui est montrée. Vérifié sur les quatre chemins, PGPORT=1 pour simuler la panne : injoignable rend False sans afficher un seul menu ; [0] rend False ; [1] rend « test » ; une saisie invalide affiche un message traduit dans les deux langues. Co-Authored-By: Claude Opus 5 (1M context) --- script/todo/database_manager.py | 39 ++++++++++++++++++++++++++------- script/todo/todo_i18n.py | 12 ++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/script/todo/database_manager.py b/script/todo/database_manager.py index 798a30e..2037e2c 100644 --- a/script/todo/database_manager.py +++ b/script/todo/database_manager.py @@ -31,28 +31,51 @@ class DatabaseManager: self._dir_path = path def select_database(self) -> str | bool: + """Faire choisir une base parmi celles que PostgreSQL expose. + + Le code de retour de « db --list » est vérifié AVANT de construire le + menu. Sans cette vérification, un PostgreSQL injoignable ne se distingue + pas d'une base absente : la sortie et l'erreur sont fusionnées dans le + même flux (`stderr=STDOUT`, execute.py), donc les lignes de la trace + d'appel devenaient les entrées du menu. « Traceback (most recent call + last): » s'affichait comme la base [1], et la choisir renvoyait cette + ligne comme nom de base à l'appelant, qui la passait à sa commande. + """ cmd_server = "./odoo_bin.sh db --list" - status, databases = self._execute.exec_command_live( + status, output = self._execute.exec_command_live( cmd_server, return_status_and_output=True, + quiet=True, source_erplibre=False, single_source_erplibre=True, ) - choices = [{"prompt_description": a.strip()} for a in databases] + if status: + print(f"❌ {t('Cannot list the databases (exit code): ')}{status}") + print(f" {t('Is PostgreSQL running?')}") + for line in output[-5:]: + print(f" {line}") + return False + + databases = [a.strip() for a in output if a.strip()] + if not databases: + print(f"ℹ️ {t('No database on this PostgreSQL server.')}") + return False + + choices = [{"prompt_description": a} for a in databases] help_info = self._fill_help_info(choices) - valid_choices = [str(a) for a in range(len(choices) + 1) if a] + valid_choices = [str(a + 1) for a in range(len(databases))] while True: - status = click.prompt(help_info) + answer = click.prompt(help_info) print() - if status == "0": + if answer == "0": return False - elif status in valid_choices: - database_name = databases[int(status) - 1].strip() + elif answer in valid_choices: + database_name = databases[int(answer) - 1] print(database_name) return database_name else: - print(t("cmd_not_found")) + print(t("Command not found !")) def _confirm_drop(self, message: str) -> bool: """Ask for an explicit 'oui'/'yes' confirmation, default is no.""" diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index ea90b82..801e5db 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -478,6 +478,18 @@ TRANSLATIONS = { "fr": "Aucune base de données sélectionnée.", "en": "No database selected.", }, + "Cannot list the databases (exit code): ": { + "fr": "Impossible de lister les bases (code de retour) : ", + "en": "Cannot list the databases (exit code): ", + }, + "Is PostgreSQL running?": { + "fr": "PostgreSQL est-il démarré ?", + "en": "Is PostgreSQL running?", + }, + "No database on this PostgreSQL server.": { + "fr": "Aucune base sur ce serveur PostgreSQL.", + "en": "No database on this PostgreSQL server.", + }, "Manage execution processes!": { "fr": "Manipuler les processus d'exécution!", "en": "Manage execution processes!",