Coverage for /home/runner/work/bijux-cli/bijux-cli/src/bijux_cli/commands/config/export.py: 100%

32 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-08-19 23:36 +0000

1# SPDX-License-Identifier: MIT 

2# Copyright © 2025 Bijan Mousavi 

3 

4"""Implements the `config export` subcommand for the Bijux CLI. 

5 

6This module contains the logic for exporting the application's entire current 

7configuration to a specified destination, which can be a file or standard 

8output. The output format can be explicitly set to 'env', 'json', or 'yaml', 

9or it can be inferred from the destination file's extension. 

10 

11Output Contract: 

12 * Success (to file): `{"status": "exported", "file": str, "format": str}` 

13 * Success (to stdout): The raw exported configuration data is printed directly. 

14 * Verbose (to file): Adds `{"python": str, "platform": str}` to the payload. 

15 * Error: `{"error": str, "code": int}` 

16 

17Exit Codes: 

18 * `0`: Success. 

19 * `1` or `2`: An error occurred during the export process, such as a file 

20 write error or invalid format request. 

21""" 

22 

23from __future__ import annotations 

24 

25import platform 

26 

27import typer 

28 

29from bijux_cli.commands.utilities import ( 

30 ascii_safe, 

31 emit_error_and_exit, 

32 new_run_command, 

33 parse_global_flags, 

34) 

35from bijux_cli.contracts import ConfigProtocol 

36from bijux_cli.core.constants import ( 

37 HELP_DEBUG, 

38 HELP_FORMAT, 

39 HELP_NO_PRETTY, 

40 HELP_QUIET, 

41 HELP_VERBOSE, 

42) 

43from bijux_cli.core.di import DIContainer 

44from bijux_cli.core.exceptions import CommandError 

45 

46 

47def export_config( 

48 ctx: typer.Context, 

49 path: str = typer.Argument( 

50 ..., help="Destination file – use “-” to write to STDOUT" 

51 ), 

52 out_fmt: str = typer.Option( 

53 None, "--out-format", help="Force output format: env | json | yaml" 

54 ), 

55 quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET), 

56 verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE), 

57 fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT), 

58 pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY), 

59 debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG), 

60) -> None: 

61 """Exports the current configuration to a file or standard output. 

62 

63 This function writes all configuration key-value pairs to a specified 

64 destination. If the destination is a file path, a structured JSON/YAML 

65 confirmation message is printed to stdout upon success. If the destination 

66 is "-", the raw exported configuration is printed directly to stdout. 

67 

68 Args: 

69 ctx (typer.Context): The Typer context for the CLI. 

70 path (str): The destination file path, or "-" for standard output. 

71 out_fmt (str): The desired output format ('env', 'json', 'yaml'). If 

72 unspecified, it is inferred from the file extension. 

73 quiet (bool): If True, suppresses all output except for errors. 

74 verbose (bool): If True, includes Python/platform details in the 

75 confirmation payload (file export only). 

76 fmt (str): The format for the confirmation payload ("json" or "yaml"). 

77 pretty (bool): If True, pretty-prints the confirmation payload. 

78 debug (bool): If True, enables debug diagnostics. 

79 

80 Returns: 

81 None: 

82 

83 Raises: 

84 SystemExit: Always exits with a contract-compliant status code and 

85 payload, indicating success or detailing the error. 

86 """ 

87 flags = parse_global_flags() 

88 

89 quiet = flags["quiet"] 

90 verbose = flags["verbose"] 

91 fmt = flags["format"] 

92 pretty = flags["pretty"] 

93 debug = flags["debug"] 

94 

95 include_runtime = verbose 

96 fmt_lower = fmt.lower() 

97 

98 command = "config export" 

99 

100 config_svc = DIContainer.current().resolve(ConfigProtocol) 

101 

102 try: 

103 config_svc.export(path, out_fmt) 

104 except CommandError as exc: 

105 code = 2 if getattr(exc, "http_status", 0) == 400 else 1 

106 emit_error_and_exit( 

107 f"Failed to export config: {exc}", 

108 code=code, 

109 failure="export_failed", 

110 command=command, 

111 fmt=fmt_lower, 

112 quiet=quiet, 

113 include_runtime=include_runtime, 

114 debug=debug, 

115 ) 

116 

117 if path != "-": 

118 

119 def payload_builder(include_runtime: bool) -> dict[str, object]: 

120 """Builds the payload confirming a successful export to a file. 

121 

122 Args: 

123 include_runtime (bool): If True, includes Python and platform info. 

124 

125 Returns: 

126 dict[str, object]: The structured payload. 

127 """ 

128 payload: dict[str, object] = { 

129 "status": "exported", 

130 "file": path, 

131 "format": out_fmt or "auto", 

132 } 

133 if include_runtime: 

134 payload["python"] = ascii_safe( 

135 platform.python_version(), "python_version" 

136 ) 

137 payload["platform"] = ascii_safe(platform.platform(), "platform") 

138 return payload 

139 

140 new_run_command( 

141 command_name=command, 

142 payload_builder=payload_builder, 

143 quiet=quiet, 

144 verbose=verbose, 

145 fmt=fmt_lower, 

146 pretty=pretty, 

147 debug=debug, 

148 )