GRM-14: feat: use click.echo() for user-facing messages with dual logging

- Create ui.py with say() helper that routes messages to both click.echo()
  (console/stdout, user-facing) and logging.getLogger('grm') (file audit trail)
- Update executor.py: replace logger.info() with say() for start, status, done
  messages; use say(level=ERROR, err=True) before raising AnsibleError
- Update report.py: replace logger.info() with say() for operation report lines
- Update all unit tests to patch say() instead of using capsys or get_logger
- Add test_ui.py with coverage for say() calling both click.echo and logging
- 117 tests, 100% coverage, pyright clean, ruff clean
This commit is contained in:
Emil Simeonov
2026-06-19 01:30:14 +02:00
parent 3b3e002f7f
commit 3f7507500b
6 changed files with 93 additions and 46 deletions
+7 -5
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import logging
import os
import re
import subprocess
@@ -11,6 +12,7 @@ from pathlib import Path
from .exceptions import AnsibleError
from .i18n import _
from .logging_config import get_logger
from .ui import say
class AnsibleExecutor:
@@ -25,8 +27,8 @@ class AnsibleExecutor:
log_file = self._prepare_log(cmd)
desc = description or _("Running Ansible playbook")
self._logger.info(desc)
self._logger.info(_("Full log: {log_file}", log_file=log_file))
say(f"[GRM] {desc}")
say(f"[GRM] {_('Full log: {log_file}', log_file=log_file)}")
returncode = self._stream(cmd, log_file)
@@ -36,14 +38,14 @@ class AnsibleExecutor:
code=returncode,
log_file=log_file,
)
self._logger.error(msg)
say(f"[GRM] {msg}", level=logging.ERROR, err=True)
raise AnsibleError(msg)
status = self._extract_status(log_file)
if status:
self._logger.info(status)
say(f"[GRM] {status}")
self._logger.info(_("Done. See full log: {log_file}", log_file=log_file))
say(f"[GRM] {_('Done. See full log: {log_file}', log_file=log_file)}")
def _prepare_log(self, cmd: list[str]) -> Path:
"""Create a log file with header."""