Coverage for harbor_cli/commands/cli/cli_config.py: 28%

68 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-09 12:09 +0100

1from __future__ import annotations 

2 

3from pathlib import Path 

4from typing import Any 

5from typing import Optional 

6 

7import typer 

8from pydantic import Extra 

9 

10from ...config import HarborCLIConfig 

11from ...logs import logger 

12from ...output.console import console 

13from ...output.console import err_console 

14from ...output.console import exit_err 

15from ...output.console import success 

16from ...output.render import render_result 

17from ...output.table.anysequence import AnySequence 

18from ...state import state 

19 

20# Create a command group 

21app = typer.Typer( 

22 name="cli-config", 

23 help="Manage CLI configuration.", 

24 no_args_is_help=True, 

25) 

26 

27 

28def render_config(config: HarborCLIConfig, as_toml: bool) -> None: 

29 if as_toml: 

30 console.print(config.toml(expose_secrets=False), markup=False) 

31 else: 

32 render_result(config) 

33 

34 

35@app.command("get") 

36def get_cli_config( 

37 ctx: typer.Context, 

38 as_toml: bool = typer.Option( 

39 True, 

40 "--toml/--no-toml", 

41 help="Show the current configuration in TOML format after setting the value. Overrides --format.", 

42 ), 

43) -> None: 

44 """Show the current CLI configuration.""" 

45 render_config(state.config, as_toml) 

46 logger.info(f"Source: {state.config.config_file}") 

47 

48 

49@app.command("keys") 

50def get_cli_config_keys(ctx: typer.Context) -> None: 

51 """Show the current CLI configuration.""" 

52 

53 def get_fields(field: dict[str, Any], current: str) -> list[str]: 

54 fields = [] 

55 if isinstance(field, dict): 

56 for sub_key, sub_value in field.items(): 

57 f = get_fields(sub_value, f"{current}.{sub_key}") 

58 fields.extend(f) 

59 else: 

60 fields.append(current) 

61 return fields 

62 

63 ff = [] 

64 d = state.config.dict() 

65 for key, value in d.items(): 

66 if isinstance(value, dict): 

67 ff.extend(get_fields(value, key)) 

68 else: 

69 ff.append(key) # no subkeys 

70 

71 render_result(AnySequence(values=ff, title="Config Keys")) 

72 

73 

74@app.command("set", no_args_is_help=True) 

75def set_cli_config( 

76 ctx: typer.Context, 

77 key: str = typer.Argument( 

78 ..., 

79 help="Key to set. Subkeys can be specified using dot notation. e.g. [green]'harbor.url'[/]", 

80 ), 

81 value: str = typer.Argument(..., help="Value to set."), 

82 path: Path = typer.Option(None, "--path", help="Path to save configuration file."), 

83 session: bool = typer.Option( 

84 False, 

85 "--session", 

86 help="Set the value in the current session only. The value will not be saved to disk. Only useful in REPL mode.", 

87 ), 

88 show_config: bool = typer.Option( 

89 True, 

90 "--show/--no-show", 

91 help="Show the current configuration after setting the value.", 

92 ), 

93 as_toml: bool = typer.Option( 

94 True, 

95 "--toml/--no-toml", 

96 help="Render config as TOML if [green]--show[/] is set. Overrides [green]--format[/].", 

97 ), 

98) -> None: 

99 """Set a key in the CLI configuration.""" 

100 attrs = [] 

101 if "." in key: 

102 attrs = key.split(".") 

103 

104 try: 

105 # temporarily forbid extra fields, so typos raise an error 

106 state.config.__config__.extra = Extra.forbid 

107 if attrs: 

108 obj = getattr(state.config, attrs[0]) 

109 for attr in attrs[1:-1]: 

110 obj = getattr(obj, attr) 

111 setattr(obj, attrs[-1], value) 

112 else: 

113 setattr(state.config, key, value) 

114 except ( 

115 ValueError, # pydantic raises ValueError for unknown fields 

116 AttributeError, 

117 ): 

118 err_console.print(f"Invalid key: [red]{key}[/]") 

119 finally: 

120 state.config.__config__.extra = Extra.forbid 

121 

122 if not session: 

123 state.config.save(path=path) 

124 

125 if show_config: 

126 render_config(state.config, as_toml) 

127 

128 

129@app.command("write") 

130def write_session_config( 

131 ctx: typer.Context, 

132 path: Optional[Path] = typer.Option( 

133 None, 

134 "--path", 

135 help="Path to save configuration file. Overrides the config's current path.", 

136 ), 

137) -> None: 

138 """Write the current [bold]session[/] configuration to disk. Used to save 

139 changes made with [green]harbor cli-config set --session[/] in REPL mode.""" 

140 save_path = path or state.config.config_file 

141 if save_path is None: 

142 exit_err( 

143 "No path specified and no path found in current configuration. Use [green]--path[/] to specify a path." 

144 ) 

145 state.config.save(path=save_path) 

146 success(f"Saved configuration to [green]{save_path}[/]") 

147 

148 

149# TODO: reload config