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

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. 

6 

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. 

13 

14A Rich progress spinner provides visual feedback during processing. 

15""" 

16 

17from __future__ import annotations 

18 

19import sys 

20 

21import click 

22from rich.console import Console 

23from rich.panel import Panel 

24from rich.progress import MofNCompleteColumn, Progress, SpinnerColumn, TextColumn 

25 

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 

29 

30 

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() 

39 

40 tools = resolve_tools( 

41 registry, 

42 include=include, 

43 exclude=exclude, 

44 include_host=include_host, 

45 ) 

46 

47 results: list[ToolUpdateResult] = [] 

48 

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)) 

58 

59 for tool in tools: 

60 progress.update(task, description=f'Processing tool: {tool.name}') 

61 

62 result = ToolUpdateResult(tool_name=tool.name, updated=False) 

63 

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 

72 

73 result.updated = changed 

74 

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 

89 

90 results.append(result) 

91 progress.advance(task) 

92 

93 # Display results with color-coded status messages. 

94 has_failure = False 

95 

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') 

124 

125 # Exit with non-zero code if any failure occurred. 

126 if has_failure: 

127 sys.exit(1)