Skip to content

Commit 1768022

Browse files
committed
NamedTextIOWrapper: stop closing buffer
The NamedTextIOWrapper is closing the buffers that are owned by the StreamMixer, so implement a close() method that prevents it from doing that. Refs #824 Refs #2993
1 parent edbbe6e commit 1768022

File tree

3 files changed

+41
-12
lines changed

3 files changed

+41
-12
lines changed

CHANGES.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
.. currentmodule:: click
22

3+
Version next
4+
------------
5+
6+
- Prevent ``_NamedTextIOWrapper`` from closing streams owned by ``StreamMixer``.
7+
:issue:`824` :issue:`2993` :pr:`3139`
8+
9+
310
Version 8.3.1
411
--------------
512

src/click/testing.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,6 @@ def __init__(self) -> None:
9898
self.stdout: io.BytesIO = BytesIOCopy(copy_to=self.output)
9999
self.stderr: io.BytesIO = BytesIOCopy(copy_to=self.output)
100100

101-
def __del__(self) -> None:
102-
"""
103-
Guarantee that embedded file-like objects are closed in a
104-
predictable order, protecting against races between
105-
self.output being closed and other streams being flushed on close
106-
107-
.. versionadded:: 8.2.2
108-
"""
109-
self.stderr.close()
110-
self.stdout.close()
111-
self.output.close()
112-
113101

114102
class _NamedTextIOWrapper(io.TextIOWrapper):
115103
def __init__(
@@ -119,6 +107,15 @@ def __init__(
119107
self._name = name
120108
self._mode = mode
121109

110+
def close(self) -> None:
111+
"""
112+
The buffer this object contains belongs to some other object, so
113+
prevent the default __del__ implementation from closing that buffer.
114+
115+
.. versionadded:: 8.3.2
116+
"""
117+
...
118+
122119
@property
123120
def name(self) -> str:
124121
return self._name

tests/test_testing_logging.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import logging
2+
3+
import click
4+
from click.testing import CliRunner
5+
6+
7+
# Minimized version of:
8+
# https://github.com/pallets/click/issues/824#issuecomment-3027263262
9+
#
10+
# Test needs to be run as "pytest --log-cli-level 30 tests/test_testing_logging.py"
11+
# to test the intended functionality.
12+
def test_runner_logger():
13+
logger = logging.getLogger(__name__)
14+
15+
@click.command()
16+
@click.option("--name", prompt="Your name", help="The person to greet.")
17+
def hello(name):
18+
logger.warning("Greeting user now...")
19+
click.echo(f"Hello, {name}!")
20+
21+
runner = CliRunner()
22+
result = runner.invoke(hello, input="Peter")
23+
assert result.exit_code == 0
24+
# FIXME: second half of the output is missing with --log-cli-level 30.
25+
assert result.output in ("Your name: Peter\n", "Your name: Peter\nHello, Peter!\n")

0 commit comments

Comments
 (0)