Skip to content

Commit 3534989

Browse files
committed
Add get_console() to query global rich consoles
For an install progress bar, we'd like to emit logs while the progress bar updates (for uninstallation messages, etc.). To avoid interwoven logs, we need to log to the same console that the progress bar is using. This is easiest to achieve by simply storing a global stdout and stderr console, queried via a get_console() helper.
1 parent c5f3d69 commit 3534989

File tree

2 files changed

+27
-16
lines changed

2 files changed

+27
-16
lines changed

src/pip/_internal/utils/logging.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from dataclasses import dataclass
99
from io import TextIOWrapper
1010
from logging import Filter
11-
from typing import Any, ClassVar, Generator, List, Optional, TextIO, Type
11+
from typing import Any, ClassVar, Generator, List, Optional, Type
1212

1313
from pip._vendor.rich.console import (
1414
Console,
@@ -29,6 +29,8 @@
2929
from pip._internal.utils.misc import ensure_dir
3030

3131
_log_state = threading.local()
32+
_stdout_console = None
33+
_stderr_console = None
3234
subprocess_logger = getLogger("pip.subprocessor")
3335

3436

@@ -144,12 +146,21 @@ def on_broken_pipe(self) -> None:
144146
raise BrokenPipeError() from None
145147

146148

149+
def get_console(*, stderr: bool = False) -> Console:
150+
if stderr:
151+
assert _stderr_console is not None, "stderr rich console is missing!"
152+
return _stderr_console
153+
else:
154+
assert _stdout_console is not None, "stdout rich console is missing!"
155+
return _stdout_console
156+
157+
147158
class RichPipStreamHandler(RichHandler):
148159
KEYWORDS: ClassVar[Optional[List[str]]] = []
149160

150-
def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:
161+
def __init__(self, console: Console) -> None:
151162
super().__init__(
152-
console=PipConsole(file=stream, no_color=no_color, soft_wrap=True),
163+
console=console,
153164
show_time=False,
154165
show_level=False,
155166
show_path=False,
@@ -266,17 +277,16 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
266277
vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG"
267278

268279
# Shorthands for clarity
269-
log_streams = {
270-
"stdout": "ext://sys.stdout",
271-
"stderr": "ext://sys.stderr",
272-
}
273280
handler_classes = {
274281
"stream": "pip._internal.utils.logging.RichPipStreamHandler",
275282
"file": "pip._internal.utils.logging.BetterRotatingFileHandler",
276283
}
277284
handlers = ["console", "console_errors", "console_subprocess"] + (
278285
["user_log"] if include_user_log else []
279286
)
287+
global _stdout_console, stderr_console
288+
_stdout_console = PipConsole(file=sys.stdout, no_color=no_color, soft_wrap=True)
289+
_stderr_console = PipConsole(file=sys.stderr, no_color=no_color, soft_wrap=True)
280290

281291
logging.config.dictConfig(
282292
{
@@ -311,16 +321,14 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
311321
"console": {
312322
"level": level,
313323
"class": handler_classes["stream"],
314-
"no_color": no_color,
315-
"stream": log_streams["stdout"],
324+
"console": _stdout_console,
316325
"filters": ["exclude_subprocess", "exclude_warnings"],
317326
"formatter": "indent",
318327
},
319328
"console_errors": {
320329
"level": "WARNING",
321330
"class": handler_classes["stream"],
322-
"no_color": no_color,
323-
"stream": log_streams["stderr"],
331+
"console": _stderr_console,
324332
"filters": ["exclude_subprocess"],
325333
"formatter": "indent",
326334
},
@@ -329,8 +337,7 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
329337
"console_subprocess": {
330338
"level": level,
331339
"class": handler_classes["stream"],
332-
"stream": log_streams["stderr"],
333-
"no_color": no_color,
340+
"console": _stderr_console,
334341
"filters": ["restrict_to_subprocess"],
335342
"formatter": "indent",
336343
},

tests/unit/test_logging.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pip._internal.utils.logging import (
1111
BrokenStdoutLoggingError,
1212
IndentingFormatter,
13+
PipConsole,
1314
RichPipStreamHandler,
1415
indent_log,
1516
)
@@ -142,7 +143,8 @@ def test_broken_pipe_in_stderr_flush(self) -> None:
142143
record = self._make_log_record()
143144

144145
with redirect_stderr(StringIO()) as stderr:
145-
handler = RichPipStreamHandler(stream=stderr, no_color=True)
146+
console = PipConsole(file=stderr, no_color=True, soft_wrap=True)
147+
handler = RichPipStreamHandler(console)
146148
with patch("sys.stderr.flush") as mock_flush:
147149
mock_flush.side_effect = BrokenPipeError()
148150
# The emit() call raises no exception.
@@ -165,7 +167,8 @@ def test_broken_pipe_in_stdout_write(self) -> None:
165167
record = self._make_log_record()
166168

167169
with redirect_stdout(StringIO()) as stdout:
168-
handler = RichPipStreamHandler(stream=stdout, no_color=True)
170+
console = PipConsole(file=stdout, no_color=True, soft_wrap=True)
171+
handler = RichPipStreamHandler(console)
169172
with patch("sys.stdout.write") as mock_write:
170173
mock_write.side_effect = BrokenPipeError()
171174
with pytest.raises(BrokenStdoutLoggingError):
@@ -180,7 +183,8 @@ def test_broken_pipe_in_stdout_flush(self) -> None:
180183
record = self._make_log_record()
181184

182185
with redirect_stdout(StringIO()) as stdout:
183-
handler = RichPipStreamHandler(stream=stdout, no_color=True)
186+
console = PipConsole(file=stdout, no_color=True, soft_wrap=True)
187+
handler = RichPipStreamHandler(console)
184188
with patch("sys.stdout.flush") as mock_flush:
185189
mock_flush.side_effect = BrokenPipeError()
186190
with pytest.raises(BrokenStdoutLoggingError):

0 commit comments

Comments
 (0)