Coverage for src/mafw/devtools/cli/toolchain/update_cmd.py: 99%
60 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-26 09:13 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-26 09:13 +0000
1# Copyright 2026 European Union
2# Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
3# SPDX-License-Identifier: EUPL-1.2
4"""
5The ``update`` subcommand for the toolchain CLI group.
7This module implements the ``devtools toolchain update`` command, which iterates
8over all applicable :class:`~mafw.devtools.toolchain.base.ToolChainTool` instances
9and attempts to bring each tool's configuration to its latest version. For each
10tool that reports a change, the optional :meth:`~mafw.devtools.toolchain.base.ToolChainTool.post_update`
11hook is executed. Failures are accumulated and reported via Rich console output,
12with a non-zero exit code when any tool encountered an error.
14A Rich progress spinner provides visual feedback during processing.
15"""
17from __future__ import annotations
19import sys
21import click
22from rich.console import Console
23from rich.panel import Panel
24from rich.progress import MofNCompleteColumn, Progress, SpinnerColumn, TextColumn
26from mafw.devtools.cli.toolchain._group import common_options, toolchain
27from mafw.devtools.toolchain.base import ToolUpdateResult
28from mafw.devtools.toolchain.filtering import resolve_tools
31@toolchain.command(name='update')
32@common_options
33@click.pass_context
34def update(ctx: click.Context, include: tuple[str, ...], exclude: tuple[str, ...]) -> None:
35 """Update tools to their latest versions following each tool's update policy."""
36 registry = ctx.obj['registry']
37 include_host: bool = ctx.obj['include_host']
38 console = Console()
40 tools = resolve_tools(
41 registry,
42 include=include,
43 exclude=exclude,
44 include_host=include_host,
45 )
47 results: list[ToolUpdateResult] = []
49 # Process each tool with a Rich progress spinner for user feedback.
50 with Progress(
51 SpinnerColumn(),
52 TextColumn('[progress.description]{task.description}'),
53 MofNCompleteColumn(),
54 console=console,
55 transient=True,
56 ) as progress:
57 task = progress.add_task('Updating tools', total=len(tools))
59 for tool in tools:
60 progress.update(task, description=f'Processing tool: {tool.name}')
62 result = ToolUpdateResult(tool_name=tool.name, updated=False)
64 # Step 1: Attempt the update.
65 try:
66 changed = tool.update()
67 except Exception as exc: # noqa: BLE001
68 result.error = str(exc)
69 results.append(result)
70 progress.advance(task)
71 continue
73 result.updated = changed
75 # Step 2: If a change was made, run the post_update hook.
76 if changed:
77 try:
78 tool.post_update()
79 except Exception as exc: # noqa: BLE001
80 result.hook_error = str(exc)
81 # Attempt to revert the configuration change.
82 # The tool's post_update() may have already reverted internally,
83 # but we attempt again as a safety net for unexpected failures.
84 try:
85 if hasattr(tool, '_revert'): 85 ↛ 90line 85 didn't jump to line 90 because the condition on line 85 was always true
86 tool._revert() # noqa: SLF001
87 except Exception: # noqa: BLE001
88 result.revert_failed = True
90 results.append(result)
91 progress.advance(task)
93 # Display results with color-coded status messages.
94 has_failure = False
96 for result in results:
97 if result.error:
98 has_failure = True
99 console.print(
100 Panel(
101 f'[bold]{result.tool_name}[/bold]: {result.error}',
102 title='Update Error',
103 border_style='red',
104 )
105 )
106 elif result.hook_error:
107 has_failure = True
108 message = f'[bold]{result.tool_name}[/bold]: post-update hook failed: {result.hook_error}'
109 if result.revert_failed:
110 message += '\n[bold red]Revert also failed — manual intervention required.[/bold red]'
111 else:
112 message += '\n[dim]Configuration change was reverted.[/dim]'
113 console.print(
114 Panel(
115 message,
116 title='Post-Update Hook Error',
117 border_style='red',
118 )
119 )
120 elif result.updated:
121 console.print(f'[green]✓[/green] [bold]{result.tool_name}[/bold] updated successfully')
122 else:
123 console.print(f'[dim]—[/dim] [bold]{result.tool_name}[/bold] already up to date')
125 # Exit with non-zero code if any failure occurred.
126 if has_failure:
127 sys.exit(1)