4
4
import logging .handlers
5
5
import os
6
6
import sys
7
+ import threading
8
+ from dataclasses import dataclass
7
9
from logging import Filter
8
- from typing import IO , Any , Callable , Iterator , Optional , TextIO , Type , cast
9
-
10
+ from typing import IO , Any , ClassVar , Iterator , List , Optional , TextIO , Type
11
+
12
+ from pip ._vendor .rich .console import (
13
+ Console ,
14
+ ConsoleOptions ,
15
+ ConsoleRenderable ,
16
+ RenderResult ,
17
+ )
18
+ from pip ._vendor .rich .highlighter import NullHighlighter
19
+ from pip ._vendor .rich .logging import RichHandler
20
+ from pip ._vendor .rich .segment import Segment
21
+
22
+ from pip ._internal .exceptions import DiagnosticPipError
10
23
from pip ._internal .utils ._log import VERBOSE , getLogger
11
24
from pip ._internal .utils .compat import WINDOWS
12
25
from pip ._internal .utils .deprecation import DEPRECATION_MSG_PREFIX
13
26
from pip ._internal .utils .misc import ensure_dir
14
27
15
- try :
16
- import threading
17
- except ImportError :
18
- import dummy_threading as threading # type: ignore
19
-
20
-
21
- try :
22
- from pip ._vendor import colorama
23
- # Lots of different errors can come from this, including SystemError and
24
- # ImportError.
25
- except Exception :
26
- colorama = None
27
-
28
-
29
28
_log_state = threading .local ()
30
29
subprocess_logger = getLogger ("pip.subprocessor" )
31
30
@@ -119,78 +118,56 @@ def format(self, record: logging.LogRecord) -> str:
119
118
return formatted
120
119
121
120
122
- def _color_wrap (* colors : str ) -> Callable [[str ], str ]:
123
- def wrapped (inp : str ) -> str :
124
- return "" .join (list (colors ) + [inp , colorama .Style .RESET_ALL ])
125
-
126
- return wrapped
127
-
128
-
129
- class ColorizedStreamHandler (logging .StreamHandler ):
130
-
131
- # Don't build up a list of colors if we don't have colorama
132
- if colorama :
133
- COLORS = [
134
- # This needs to be in order from highest logging level to lowest.
135
- (logging .ERROR , _color_wrap (colorama .Fore .RED )),
136
- (logging .WARNING , _color_wrap (colorama .Fore .YELLOW )),
137
- ]
138
- else :
139
- COLORS = []
140
-
141
- def __init__ (self , stream : Optional [TextIO ] = None , no_color : bool = None ) -> None :
142
- super ().__init__ (stream )
143
- self ._no_color = no_color
144
-
145
- if WINDOWS and colorama :
146
- self .stream = colorama .AnsiToWin32 (self .stream )
147
-
148
- def _using_stdout (self ) -> bool :
149
- """
150
- Return whether the handler is using sys.stdout.
151
- """
152
- if WINDOWS and colorama :
153
- # Then self.stream is an AnsiToWin32 object.
154
- stream = cast (colorama .AnsiToWin32 , self .stream )
155
- return stream .wrapped is sys .stdout
156
-
157
- return self .stream is sys .stdout
158
-
159
- def should_color (self ) -> bool :
160
- # Don't colorize things if we do not have colorama or if told not to
161
- if not colorama or self ._no_color :
162
- return False
163
-
164
- real_stream = (
165
- self .stream
166
- if not isinstance (self .stream , colorama .AnsiToWin32 )
167
- else self .stream .wrapped
121
+ @dataclass
122
+ class IndentedRenderable :
123
+ renderable : ConsoleRenderable
124
+ indent : int
125
+
126
+ def __rich_console__ (
127
+ self , console : Console , options : ConsoleOptions
128
+ ) -> RenderResult :
129
+ segments = console .render (self .renderable , options )
130
+ lines = Segment .split_lines (segments )
131
+ for line in lines :
132
+ yield Segment (" " * self .indent )
133
+ yield from line
134
+ yield Segment ("\n " )
135
+
136
+
137
+ class RichPipStreamHandler (RichHandler ):
138
+ KEYWORDS : ClassVar [Optional [List [str ]]] = []
139
+
140
+ def __init__ (self , stream : Optional [TextIO ], no_color : bool ) -> None :
141
+ super ().__init__ (
142
+ console = Console (file = stream , no_color = no_color , soft_wrap = True ),
143
+ show_time = False ,
144
+ show_level = False ,
145
+ show_path = False ,
146
+ highlighter = NullHighlighter (),
168
147
)
169
148
170
- # If the stream is a tty we should color it
171
- if hasattr (real_stream , "isatty" ) and real_stream .isatty ():
172
- return True
173
-
174
- # If we have an ANSI term we should color it
175
- if os .environ .get ("TERM" ) == "ANSI" :
176
- return True
177
-
178
- # If anything else we should not color it
179
- return False
180
-
181
- def format (self , record : logging .LogRecord ) -> str :
182
- msg = super ().format (record )
183
-
184
- if self .should_color ():
185
- for level , color in self .COLORS :
186
- if record .levelno >= level :
187
- msg = color (msg )
188
- break
189
-
190
- return msg
149
+ # Our custom override on rich's logger, to make things work as we need them to.
150
+ def emit (self , record : logging .LogRecord ) -> None :
151
+ # If we are given a diagnostic error to present, present it with indentation.
152
+ if record .msg == "[present-diagnostic]" and len (record .args ) == 1 :
153
+ diagnostic_error : DiagnosticPipError = record .args [0 ] # type: ignore[index]
154
+ assert isinstance (diagnostic_error , DiagnosticPipError )
155
+
156
+ renderable : ConsoleRenderable = IndentedRenderable (
157
+ diagnostic_error , indent = get_indentation ()
158
+ )
159
+ else :
160
+ message = self .format (record )
161
+ renderable = self .render_message (record , message )
162
+
163
+ try :
164
+ self .console .print (renderable , overflow = "ignore" , crop = False )
165
+ except Exception :
166
+ self .handleError (record )
191
167
192
- # The logging module says handleError() can be customized.
193
168
def handleError (self , record : logging .LogRecord ) -> None :
169
+ """Called when logging is unable to log some output."""
170
+
194
171
exc_class , exc = sys .exc_info ()[:2 ]
195
172
# If a broken pipe occurred while calling write() or flush() on the
196
173
# stdout stream in logging's Handler.emit(), then raise our special
@@ -199,7 +176,7 @@ def handleError(self, record: logging.LogRecord) -> None:
199
176
if (
200
177
exc_class
201
178
and exc
202
- and self ._using_stdout ()
179
+ and self .console . file is sys . stdout
203
180
and _is_broken_pipe_error (exc_class , exc )
204
181
):
205
182
raise BrokenStdoutLoggingError ()
@@ -275,7 +252,8 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
275
252
"stderr" : "ext://sys.stderr" ,
276
253
}
277
254
handler_classes = {
278
- "stream" : "pip._internal.utils.logging.ColorizedStreamHandler" ,
255
+ "subprocess" : "logging.StreamHandler" ,
256
+ "stream" : "pip._internal.utils.logging.RichPipStreamHandler" ,
279
257
"file" : "pip._internal.utils.logging.BetterRotatingFileHandler" ,
280
258
}
281
259
handlers = ["console" , "console_errors" , "console_subprocess" ] + (
@@ -332,8 +310,7 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
332
310
# from the "subprocessor" logger.
333
311
"console_subprocess" : {
334
312
"level" : level ,
335
- "class" : handler_classes ["stream" ],
336
- "no_color" : no_color ,
313
+ "class" : handler_classes ["subprocess" ],
337
314
"stream" : log_streams ["stderr" ],
338
315
"filters" : ["restrict_to_subprocess" ],
339
316
"formatter" : "indent" ,
0 commit comments