@@ -151,6 +151,11 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix
151
151
152
152
_ports = Dict ()
153
153
154
+ _original_io = Any ()
155
+ _log_map = Any ()
156
+ _io_modified = Bool (False )
157
+ _blackhole = Any ()
158
+
154
159
subcommands = {
155
160
"install" : (
156
161
"ipykernel.kernelspec.InstallIPythonKernelSpecApp" ,
@@ -470,56 +475,93 @@ def log_connection_info(self):
470
475
471
476
def init_blackhole (self ):
472
477
"""redirects stdout/stderr to devnull if necessary"""
478
+ self ._save_io ()
473
479
if self .no_stdout or self .no_stderr :
474
- blackhole = open (os .devnull , "w" ) # noqa: SIM115
480
+ # keep reference around so that it would not accidentally close the pipe fds
481
+ self ._blackhole = open (os .devnull , "w" ) # noqa: SIM115
475
482
if self .no_stdout :
476
- sys .stdout = sys .__stdout__ = blackhole # type:ignore[misc]
483
+ if sys .stdout is not None :
484
+ sys .stdout .flush ()
485
+ sys .stdout = self ._blackhole
477
486
if self .no_stderr :
478
- sys .stderr = sys .__stderr__ = blackhole # type:ignore[misc]
487
+ if sys .stderr is not None :
488
+ sys .stderr .flush ()
489
+ sys .stderr = self ._blackhole
479
490
480
491
def init_io (self ):
481
492
"""Redirect input streams and set a display hook."""
493
+ self ._save_io ()
482
494
if self .outstream_class :
483
495
outstream_factory = import_item (str (self .outstream_class ))
484
- if sys .stdout is not None :
485
- sys .stdout .flush ()
486
496
487
- e_stdout = None if self .quiet else sys .__stdout__
488
- e_stderr = None if self .quiet else sys .__stderr__
497
+ e_stdout = None if self .quiet else sys .stdout
498
+ e_stderr = None if self .quiet else sys .stderr
489
499
490
500
if not self .capture_fd_output :
491
501
outstream_factory = partial (outstream_factory , watchfd = False )
492
502
503
+ if sys .stdout is not None :
504
+ sys .stdout .flush ()
493
505
sys .stdout = outstream_factory (self .session , self .iopub_thread , "stdout" , echo = e_stdout )
506
+
494
507
if sys .stderr is not None :
495
508
sys .stderr .flush ()
496
509
sys .stderr = outstream_factory (self .session , self .iopub_thread , "stderr" , echo = e_stderr )
510
+
497
511
if hasattr (sys .stderr , "_original_stdstream_copy" ):
498
512
for handler in self .log .handlers :
499
- if isinstance (handler , StreamHandler ) and (handler .stream .buffer .fileno () == 2 ):
513
+ if (
514
+ isinstance (handler , StreamHandler )
515
+ and (buffer := getattr (handler .stream , "buffer" , None ))
516
+ and (fileno := getattr (buffer , "fileno" , None ))
517
+ and fileno () == sys .stderr ._original_stdstream_fd # type:ignore[attr-defined]
518
+ ):
500
519
self .log .debug ("Seeing logger to stderr, rerouting to raw filedescriptor." )
501
-
502
- handler .stream = TextIOWrapper (
503
- FileIO (
504
- sys .stderr ._original_stdstream_copy ,
505
- "w" ,
506
- )
520
+ io_wrapper = TextIOWrapper (
521
+ FileIO (sys .stderr ._original_stdstream_copy , "w" , closefd = False )
507
522
)
523
+ self ._log_map [id (io_wrapper )] = handler .stream
524
+ handler .stream = io_wrapper
508
525
if self .displayhook_class :
509
526
displayhook_factory = import_item (str (self .displayhook_class ))
510
527
self .displayhook = displayhook_factory (self .session , self .iopub_socket )
511
528
sys .displayhook = self .displayhook
512
529
513
530
self .patch_io ()
514
531
532
+ def _save_io (self ):
533
+ if not self ._io_modified :
534
+ self ._original_io = sys .stdout , sys .stderr , sys .displayhook
535
+ self ._log_map = {}
536
+ self ._io_modified = True
537
+
515
538
def reset_io (self ):
516
539
"""restore original io
517
540
518
541
restores state after init_io
519
542
"""
520
- sys .stdout = sys .__stdout__
521
- sys .stderr = sys .__stderr__
522
- sys .displayhook = sys .__displayhook__
543
+ if not self ._io_modified :
544
+ return
545
+ stdout , stderr , displayhook = sys .stdout , sys .stderr , sys .displayhook
546
+ sys .stdout , sys .stderr , sys .displayhook = self ._original_io
547
+ self ._original_io = None
548
+ self ._io_modified = False
549
+ if finish_displayhook := getattr (displayhook , "finish_displayhook" , None ):
550
+ finish_displayhook ()
551
+ if hasattr (stderr , "_original_stdstream_copy" ):
552
+ for handler in self .log .handlers :
553
+ if orig_stream := self ._log_map .get (id (handler .stream )):
554
+ self .log .debug ("Seeing modified logger, rerouting back to stderr" )
555
+ handler .stream = orig_stream
556
+ self ._log_map = None
557
+ if self .outstream_class :
558
+ outstream_factory = import_item (str (self .outstream_class ))
559
+ if isinstance (stderr , outstream_factory ):
560
+ stderr .close ()
561
+ if isinstance (stdout , outstream_factory ):
562
+ stdout .close ()
563
+ if self ._blackhole :
564
+ self ._blackhole .close ()
523
565
524
566
def patch_io (self ):
525
567
"""Patch important libraries that can't handle sys.stdout forwarding"""
0 commit comments