Coverage for /home/runner/work/bijux-cli/bijux-cli/src/bijux_cli/commands/config/get.py: 100%
32 statements
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-19 23:36 +0000
« 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
4"""Implements the `config get` subcommand for the Bijux CLI.
6This module contains the logic for retrieving the value of a specific key
7from the active configuration store. It provides a structured, machine-readable
8response containing the value or an error if the key is not found.
10Output Contract:
11 * Success: `{"value": str}`
12 * Verbose: Adds `{"python": str, "platform": str}` to the payload.
13 * Error: `{"error": str, "code": int}`
15Exit Codes:
16 * `0`: Success.
17 * `1`: An unexpected error occurred while accessing the configuration.
18 * `2`: The specified key was not found in the configuration.
19"""
21from __future__ import annotations
23import platform
25import typer
27from bijux_cli.commands.utilities import (
28 ascii_safe,
29 emit_error_and_exit,
30 new_run_command,
31 parse_global_flags,
32)
33from bijux_cli.contracts import ConfigProtocol
34from bijux_cli.core.constants import (
35 HELP_DEBUG,
36 HELP_FORMAT,
37 HELP_NO_PRETTY,
38 HELP_QUIET,
39 HELP_VERBOSE,
40)
41from bijux_cli.core.di import DIContainer
42from bijux_cli.core.exceptions import CommandError
45def get_config(
46 ctx: typer.Context,
47 key: str = typer.Argument(..., help="Configuration key to look up"),
48 quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
49 verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
50 fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
51 pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
52 debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
53) -> None:
54 """Retrieves the value for a given configuration key.
56 This function fetches the value for the specified key from the configuration
57 service and uses the `new_run_command` helper to emit it in a structured
58 payload. It handles errors, such as the key not being found.
60 Args:
61 ctx (typer.Context): The Typer context for the CLI.
62 key (str): The configuration key whose value should be retrieved.
63 quiet (bool): If True, suppresses all output except for errors.
64 verbose (bool): If True, includes Python/platform details in the output.
65 fmt (str): The output format, "json" or "yaml".
66 pretty (bool): If True, pretty-prints the output.
67 debug (bool): If True, enables debug diagnostics.
69 Returns:
70 None:
72 Raises:
73 SystemExit: Always exits with a contract-compliant status code and
74 payload, indicating success or detailing the error.
75 """
76 flags = parse_global_flags()
78 quiet = flags["quiet"]
79 verbose = flags["verbose"]
80 fmt = flags["format"]
81 pretty = flags["pretty"]
82 debug = flags["debug"]
84 include_runtime = verbose
85 fmt_lower = fmt.lower()
87 command = "config get"
89 config_svc = DIContainer.current().resolve(ConfigProtocol)
91 try:
92 value = config_svc.get(key)
93 except CommandError as exc:
94 if str(exc).startswith("Config key not found"):
95 emit_error_and_exit(
96 f"Config key not found: {key}",
97 code=2,
98 failure="not_found",
99 command=command,
100 fmt=fmt_lower,
101 quiet=quiet,
102 include_runtime=include_runtime,
103 debug=debug,
104 extra={"key": key},
105 )
106 emit_error_and_exit(
107 f"Failed to get config: {exc}",
108 code=1,
109 failure="get_failed",
110 command=command,
111 fmt=fmt_lower,
112 quiet=quiet,
113 include_runtime=include_runtime,
114 debug=debug,
115 )
117 def payload_builder(include_runtime: bool) -> dict[str, object]:
118 """Builds a payload containing the retrieved configuration value.
120 Args:
121 include_runtime (bool): If True, includes Python and platform info.
123 Returns:
124 dict[str, object]: A dictionary containing the key's value and
125 optional runtime metadata.
126 """
127 payload: dict[str, object] = {"value": value}
128 if include_runtime:
129 payload["python"] = ascii_safe(platform.python_version(), "python_version")
130 payload["platform"] = ascii_safe(platform.platform(), "platform")
131 return payload
133 new_run_command(
134 command_name=command,
135 payload_builder=payload_builder,
136 quiet=quiet,
137 verbose=verbose,
138 fmt=fmt_lower,
139 pretty=pretty,
140 debug=debug,
141 )