Skip to content

Commit a5dcace

Browse files
authored
Merge pull request #1779 from AryanRG/fix/annotated-help-output
Route annotated command help through the application output stream
2 parents 1bb1619 + 74cec46 commit a5dcace

3 files changed

Lines changed: 47 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 4.3.1 (TBD)
2+
3+
- Bug Fixes
4+
- Fixed `@with_annotated` command `--help` output bypassing the application's output stream,
5+
leaving redirected help files empty and preventing help from being captured by embedded apps.
6+
17
## 4.3.0 (October 13, 2026)
28

39
- Stabilized features

‎cmd2/annotated.py‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3171,11 +3171,12 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> _CommandReturn:
31713171
namespace = ns_provider(provider_self if provider_self is not None else cmd2_app)
31723172

31733173
try:
3174-
if with_unknown_args:
3175-
ns, unknown = arg_parser.parse_known_args(parsed_arglist, namespace)
3176-
else:
3177-
ns = arg_parser.parse_args(parsed_arglist, namespace)
3178-
unknown = None
3174+
with arg_parser.output_to(cmd2_app.stdout):
3175+
if with_unknown_args:
3176+
ns, unknown = arg_parser.parse_known_args(parsed_arglist, namespace)
3177+
else:
3178+
ns = arg_parser.parse_args(parsed_arglist, namespace)
3179+
unknown = None
31793180
except SystemExit as exc:
31803181
raise Cmd2ArgparseError from exc
31813182

‎tests/test_annotated.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2467,6 +2467,41 @@ def test_error_produces_stderr(self, app) -> None:
24672467
_out, err = run_cmd(app, "greet")
24682468
assert any("error" in line.lower() or "usage" in line.lower() for line in err)
24692469

2470+
@pytest.mark.parametrize(
2471+
("app_fixture", "command"),
2472+
[
2473+
("app", "greet"),
2474+
("app", "flex"),
2475+
("subcmd_app", "manage"),
2476+
("subcmd_app", "manage add"),
2477+
("subcmd_app", "manage member add"),
2478+
],
2479+
)
2480+
@pytest.mark.parametrize("destination", ["stdout", ">", ">>"])
2481+
def test_help_respects_output_destination(self, request, capsys, tmp_path, app_fixture, command, destination) -> None:
2482+
"""Help follows app.stdout, including file redirection, without leaking to sys.stdout."""
2483+
app = request.getfixturevalue(app_fixture)
2484+
original_stdout = app.stdout
2485+
help_file = tmp_path / "help.txt"
2486+
line = f"{command} --help"
2487+
if destination != "stdout":
2488+
help_file.write_text("existing\n", encoding="utf-8")
2489+
line += f' {destination} "{help_file}"'
2490+
2491+
out, err = run_cmd(app, line)
2492+
2493+
assert app.stdout is original_stdout
2494+
assert not err
2495+
if destination == "stdout":
2496+
help_text = "\n".join(out)
2497+
else:
2498+
assert not out
2499+
help_text = help_file.read_text(encoding="utf-8")
2500+
assert help_text.startswith("existing\n") == (destination == ">>")
2501+
assert f"Usage: {command}" in help_text
2502+
assert "--help" in help_text
2503+
assert capsys.readouterr() == ("", "")
2504+
24702505
def test_no_args_raises_type_error(self, app) -> None:
24712506
with pytest.raises(TypeError, match="Expected arguments"):
24722507
app.do_greet()

0 commit comments

Comments
 (0)