Coverage for /home/runner/work/bijux-cli/bijux-cli/src/bijux_cli/commands/sleep.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 `sleep` command for the Bijux CLI.
6This module provides a simple command to pause execution for a specified duration.
7It is primarily used for scripting, testing, or rate-limiting operations within
8automated workflows. The command returns a structured payload confirming the
9duration slept.
11Output Contract:
12 * Success: `{"slept": float}`
13 * Verbose: Adds `{"python": str, "platform": str}` to the payload.
14 * Error: `{"error": str, "code": int}`
16Exit Codes:
17 * `0`: Success.
18 * `1`: Internal or configuration-related error.
19 * `2`: Invalid argument (e.g., negative duration) or timeout exceeded.
20"""
22from __future__ import annotations
24from collections.abc import Mapping
25import platform
26import time
28import typer
30from bijux_cli.commands.utilities import (
31 ascii_safe,
32 emit_error_and_exit,
33 new_run_command,
34 validate_common_flags,
35)
36from bijux_cli.contracts import ConfigProtocol
37from bijux_cli.core.constants import (
38 DEFAULT_COMMAND_TIMEOUT,
39 HELP_DEBUG,
40 HELP_FORMAT,
41 HELP_NO_PRETTY,
42 HELP_QUIET,
43 HELP_VERBOSE,
44)
45from bijux_cli.core.di import DIContainer
47typer.core.rich = None # type: ignore[attr-defined,assignment]
49sleep_app = typer.Typer( # pytype: skip-file
50 name="sleep",
51 help="Pause execution for a specified duration.",
52 rich_markup_mode=None,
53 context_settings={"help_option_names": ["-h", "--help"]},
54 no_args_is_help=False,
55)
58def _build_payload(include_runtime: bool, slept: float) -> Mapping[str, object]:
59 """Constructs the structured payload for the sleep command.
61 Args:
62 include_runtime (bool): If True, includes Python version and platform
63 information in the payload.
64 slept (float): The number of seconds the command slept.
66 Returns:
67 Mapping[str, object]: A dictionary containing the sleep duration and
68 optional runtime details.
69 """
70 payload: dict[str, object] = {"slept": slept}
71 if include_runtime:
72 payload["python"] = ascii_safe(platform.python_version(), "python_version")
73 payload["platform"] = ascii_safe(platform.platform(), "platform")
74 return payload
77@sleep_app.callback(invoke_without_command=True)
78def sleep(
79 ctx: typer.Context,
80 seconds: float = typer.Option(
81 ..., "--seconds", "-s", help="Duration in seconds (must be ≥ 0)"
82 ),
83 quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
84 verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
85 fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
86 pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
87 debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
88) -> None:
89 """Defines the entrypoint and logic for the `bijux sleep` command.
91 This function validates the requested sleep duration against configuration
92 limits, pauses execution, and then emits a structured payload confirming
93 the duration.
95 Args:
96 ctx (typer.Context): The Typer context for the CLI.
97 seconds (float): The duration in seconds to pause execution. Must be
98 non-negative and not exceed the configured command timeout.
99 quiet (bool): If True, suppresses all output except for errors.
100 verbose (bool): If True, includes Python and platform details in the
101 output payload.
102 fmt (str): The output format, either "json" or "yaml". Defaults to "json".
103 pretty (bool): If True, pretty-prints the output for human readability.
104 debug (bool): If True, enables debug diagnostics, implying `verbose`
105 and `pretty`.
107 Returns:
108 None:
110 Raises:
111 SystemExit: Exits with a contract-compliant status code and payload
112 upon any error, such as a negative sleep duration or a timeout
113 violation.
114 """
115 command = "sleep"
117 fmt_lower = validate_common_flags(fmt, command, quiet)
119 if seconds < 0:
120 emit_error_and_exit(
121 "sleep length must be non-negative",
122 code=2,
123 failure="negative",
124 command=command,
125 fmt=fmt_lower,
126 quiet=quiet,
127 include_runtime=verbose,
128 debug=debug,
129 )
131 cfg: ConfigProtocol = DIContainer.current().resolve(ConfigProtocol)
133 try:
134 timeout = float(cfg.get("BIJUXCLI_COMMAND_TIMEOUT", DEFAULT_COMMAND_TIMEOUT))
135 except Exception as exc:
136 emit_error_and_exit(
137 f"Failed to read timeout: {exc}",
138 code=1,
139 failure="config",
140 command=command,
141 fmt=fmt_lower,
142 quiet=quiet,
143 include_runtime=verbose,
144 debug=debug,
145 )
147 if seconds > timeout:
148 emit_error_and_exit(
149 "Command timed out because sleep duration exceeded the configured timeout.",
150 code=2,
151 failure="timeout",
152 command=command,
153 fmt=fmt_lower,
154 quiet=quiet,
155 include_runtime=verbose,
156 debug=debug,
157 )
159 time.sleep(seconds)
161 new_run_command(
162 command_name=command,
163 payload_builder=lambda include: _build_payload(include, seconds),
164 quiet=quiet,
165 verbose=verbose,
166 fmt=fmt_lower,
167 pretty=pretty,
168 debug=debug,
169 )