Skip to content

Commit 5b41b0c

Browse files
committed
In an effort be consistent with the purpose of self.stdout and our own documentation, the following changes were made.
- No longer redirecting sys.stdout. - No longer capturing pyscript output written to sys.stdout.
1 parent 076d0e0 commit 5b41b0c

File tree

6 files changed

+54
-42
lines changed

6 files changed

+54
-42
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
`Cmd._build_parser()`. This code had previously been restored to support backward
77
compatibility in `cmd2` 2.0 family.
88

9+
- In an effort be consistent with the purpose of `self.stdout` and our own documentation, the
10+
following changes were made.
11+
- No longer redirecting `sys.stdout`.
12+
- No longer capturing pyscript output written to `sys.stdout`.
13+
- To assist with this change, calling `print()` within a pyscript now writes to
14+
`self.stdout`. Calling `self.poutput()` within a pyscript is still preferred, but that
15+
may not always be possible.
16+
917
- Enhancements
1018

1119
- Simplified the process to set a custom parser for `cmd2's` built-in commands. See

cmd2/cmd2.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2856,7 +2856,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
28562856

28572857
# Initialize the redirection saved state
28582858
redir_saved_state = utils.RedirectionSavedState(
2859-
cast(TextIO, self.stdout), sys.stdout, self._cur_pipe_proc_reader, self._redirecting
2859+
cast(TextIO, self.stdout), self._cur_pipe_proc_reader, self._redirecting
28602860
)
28612861

28622862
# The ProcReader for this command
@@ -2912,7 +2912,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
29122912
raise RedirectionError(f'Pipe process exited with code {proc.returncode} before command could run')
29132913
redir_saved_state.redirecting = True # type: ignore[unreachable]
29142914
cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr)
2915-
sys.stdout = self.stdout = new_stdout
2915+
self.stdout = new_stdout
29162916

29172917
elif statement.output:
29182918
if statement.output_to:
@@ -2926,7 +2926,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
29262926
raise RedirectionError('Failed to redirect output') from ex
29272927

29282928
redir_saved_state.redirecting = True
2929-
sys.stdout = self.stdout = new_stdout
2929+
self.stdout = new_stdout
29302930

29312931
else:
29322932
# Redirecting to a paste buffer
@@ -2944,7 +2944,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
29442944
# create a temporary file to store output
29452945
new_stdout = cast(TextIO, tempfile.TemporaryFile(mode="w+")) # noqa: SIM115
29462946
redir_saved_state.redirecting = True
2947-
sys.stdout = self.stdout = new_stdout
2947+
self.stdout = new_stdout
29482948

29492949
if statement.output == constants.REDIRECTION_APPEND:
29502950
self.stdout.write(current_paste_buffer)
@@ -2972,9 +2972,8 @@ def _restore_output(self, statement: Statement, saved_redir_state: utils.Redirec
29722972
# Close the file or pipe that stdout was redirected to
29732973
self.stdout.close()
29742974

2975-
# Restore the stdout values
2975+
# Restore self.stdout
29762976
self.stdout = cast(TextIO, saved_redir_state.saved_self_stdout)
2977-
sys.stdout = cast(TextIO, saved_redir_state.saved_sys_stdout)
29782977

29792978
# Check if we need to wait for the process being piped to
29802979
if self._cur_pipe_proc_reader is not None:
@@ -4449,22 +4448,13 @@ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env:
44494448
# Set up sys module for the Python console
44504449
self._reset_py_display()
44514450

4452-
cmd2_env.sys_stdout = sys.stdout
4453-
sys.stdout = self.stdout # type: ignore[assignment]
4454-
4455-
cmd2_env.sys_stdin = sys.stdin
4456-
sys.stdin = self.stdin # type: ignore[assignment]
4457-
44584451
return cmd2_env
44594452

44604453
def _restore_cmd2_env(self, cmd2_env: _SavedCmd2Env) -> None:
44614454
"""Restore cmd2 environment after exiting an interactive Python shell.
44624455
44634456
:param cmd2_env: the environment settings to restore
44644457
"""
4465-
sys.stdout = cmd2_env.sys_stdout # type: ignore[assignment]
4466-
sys.stdin = cmd2_env.sys_stdin # type: ignore[assignment]
4467-
44684458
# Set up readline for cmd2
44694459
if rl_type != RlType.NONE:
44704460
# Save py's history
@@ -4539,6 +4529,11 @@ def py_quit() -> None:
45394529
if self.self_in_py:
45404530
local_vars['self'] = self
45414531

4532+
# Since poutput() may not be available in a pyscript, like in the case when self_in_py is False,
4533+
# provide a version of print() which writes to self.stdout. This way, print's output can be
4534+
# captured and redirected.
4535+
local_vars['print'] = functools.partial(print, file=self.stdout)
4536+
45424537
# Handle case where we were called by do_run_pyscript()
45434538
if pyscript is not None:
45444539
# Read the script file

cmd2/py_bridge.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
"""
55

66
import sys
7-
from contextlib import (
8-
redirect_stderr,
9-
redirect_stdout,
10-
)
7+
from contextlib import redirect_stderr
118
from typing import (
129
IO,
1310
TYPE_CHECKING,
@@ -19,9 +16,7 @@
1916
cast,
2017
)
2118

22-
from .utils import ( # namedtuple_with_defaults,
23-
StdSim,
24-
)
19+
from .utils import StdSim
2520

2621
if TYPE_CHECKING: # pragma: no cover
2722
import cmd2
@@ -113,7 +108,7 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul
113108
if echo is None:
114109
echo = self.cmd_echo
115110

116-
# This will be used to capture _cmd2_app.stdout and sys.stdout
111+
# This will be used to capture _cmd2_app.stdout
117112
copy_cmd_stdout = StdSim(cast(Union[TextIO, StdSim], self._cmd2_app.stdout), echo=echo)
118113

119114
# Pause the storing of stdout until onecmd_plus_hooks enables it
@@ -127,7 +122,7 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul
127122
stop = False
128123
try:
129124
self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout)
130-
with redirect_stdout(cast(IO[str], copy_cmd_stdout)), redirect_stderr(cast(IO[str], copy_stderr)):
125+
with redirect_stderr(cast(IO[str], copy_stderr)):
131126
stop = self._cmd2_app.onecmd_plus_hooks(
132127
command,
133128
add_to_history=self._add_to_history,

cmd2/utils.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,22 @@
1313
import sys
1414
import threading
1515
import unicodedata
16-
from collections.abc import Callable, Iterable
16+
from collections.abc import (
17+
Callable,
18+
Iterable,
19+
)
1720
from difflib import SequenceMatcher
1821
from enum import Enum
19-
from typing import TYPE_CHECKING, Any, Optional, TextIO, TypeVar, Union, cast, get_type_hints
22+
from typing import (
23+
TYPE_CHECKING,
24+
Any,
25+
Optional,
26+
TextIO,
27+
TypeVar,
28+
Union,
29+
cast,
30+
get_type_hints,
31+
)
2032

2133
from . import constants
2234
from .argparse_custom import ChoicesProviderFunc, CompleterFunc
@@ -683,23 +695,20 @@ class RedirectionSavedState:
683695
def __init__(
684696
self,
685697
self_stdout: Union[StdSim, TextIO],
686-
sys_stdout: Union[StdSim, TextIO],
687698
pipe_proc_reader: Optional[ProcReader],
688699
saved_redirecting: bool,
689700
) -> None:
690701
"""RedirectionSavedState initializer.
691702
692703
:param self_stdout: saved value of Cmd.stdout
693-
:param sys_stdout: saved value of sys.stdout
694704
:param pipe_proc_reader: saved value of Cmd._cur_pipe_proc_reader
695705
:param saved_redirecting: saved value of Cmd._redirecting.
696706
"""
697707
# Tells if command is redirecting
698708
self.redirecting = False
699709

700-
# Used to restore values after redirection ends
710+
# Used to restore self.stdout after redirection ends
701711
self.saved_self_stdout = self_stdout
702-
self.saved_sys_stdout = sys_stdout
703712

704713
# Used to restore values after command ends regardless of whether the command redirected
705714
self.saved_pipe_proc_reader = pipe_proc_reader

docs/features/settings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ be run by the [edit](./builtin_commands.md#edit) command.
6161

6262
### feedback_to_output
6363

64-
Controls whether feedback generated with the `cmd2.Cmd.pfeedback` method is sent to `sys.stdout` or
64+
Controls whether feedback generated with the `cmd2.Cmd.pfeedback` method is sent to `self.stdout` or
6565
`sys.stderr`. If `False` the output will be sent to `sys.stderr`
6666

6767
If `True` the output is sent to `stdout` (which is often the screen but may be

tests/test_run_pyscript.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010

1111
from cmd2 import (
12+
cmd2,
1213
plugin,
1314
utils,
1415
)
@@ -18,14 +19,6 @@
1819
run_cmd,
1920
)
2021

21-
HOOK_OUTPUT = "TEST_OUTPUT"
22-
23-
24-
def cmdfinalization_hook(data: plugin.CommandFinalizationData) -> plugin.CommandFinalizationData:
25-
"""A cmdfinalization_hook hook which requests application exit"""
26-
print(HOOK_OUTPUT)
27-
return data
28-
2922

3023
def test_run_pyscript(base_app, request) -> None:
3124
test_dir = os.path.dirname(request.module.__file__)
@@ -133,11 +126,23 @@ def test_run_pyscript_dir(base_app, request) -> None:
133126
assert out[0] == "['cmd_echo']"
134127

135128

136-
def test_run_pyscript_stdout_capture(base_app, request) -> None:
137-
base_app.register_cmdfinalization_hook(cmdfinalization_hook)
129+
def test_run_pyscript_stdout_capture(request) -> None:
130+
class HookApp(cmd2.Cmd):
131+
HOOK_OUTPUT = "TEST_OUTPUT"
132+
133+
def __init__(self) -> None:
134+
super().__init__()
135+
self.register_cmdfinalization_hook(self.cmdfinalization_hook)
136+
137+
def cmdfinalization_hook(self, data: plugin.CommandFinalizationData) -> plugin.CommandFinalizationData:
138+
"""A cmdfinalization_hook prints output."""
139+
self.poutput(self.HOOK_OUTPUT)
140+
return data
141+
142+
hook_app = HookApp()
138143
test_dir = os.path.dirname(request.module.__file__)
139144
python_script = os.path.join(test_dir, 'pyscript', 'stdout_capture.py')
140-
out, err = run_cmd(base_app, f'run_pyscript {python_script} {HOOK_OUTPUT}')
145+
out, err = run_cmd(hook_app, f'run_pyscript {python_script} {hook_app.HOOK_OUTPUT}')
141146

142147
assert out[0] == "PASSED"
143148
assert out[1] == "PASSED"

0 commit comments

Comments
 (0)