123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
- from __future__ import annotations
- import errno
- import os
- import sys
- from contextlib import contextmanager
- from typing import IO, Iterator, TextIO
- __all__ = ["flush_stdout"]
- def flush_stdout(stdout: TextIO, data: str) -> None:
- # If the IO object has an `encoding` and `buffer` attribute, it means that
- # we can access the underlying BinaryIO object and write into it in binary
- # mode. This is preferred if possible.
- # NOTE: When used in a Jupyter notebook, don't write binary.
- # `ipykernel.iostream.OutStream` has an `encoding` attribute, but not
- # a `buffer` attribute, so we can't write binary in it.
- has_binary_io = hasattr(stdout, "encoding") and hasattr(stdout, "buffer")
- try:
- # Ensure that `stdout` is made blocking when writing into it.
- # Otherwise, when uvloop is activated (which makes stdout
- # non-blocking), and we write big amounts of text, then we get a
- # `BlockingIOError` here.
- with _blocking_io(stdout):
- # (We try to encode ourself, because that way we can replace
- # characters that don't exist in the character set, avoiding
- # UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.)
- # My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968'
- # for sys.stdout.encoding in xterm.
- if has_binary_io:
- stdout.buffer.write(data.encode(stdout.encoding or "utf-8", "replace"))
- else:
- stdout.write(data)
- stdout.flush()
- except OSError as e:
- if e.args and e.args[0] == errno.EINTR:
- # Interrupted system call. Can happen in case of a window
- # resize signal. (Just ignore. The resize handler will render
- # again anyway.)
- pass
- elif e.args and e.args[0] == 0:
- # This can happen when there is a lot of output and the user
- # sends a KeyboardInterrupt by pressing Control-C. E.g. in
- # a Python REPL when we execute "while True: print('test')".
- # (The `ptpython` REPL uses this `Output` class instead of
- # `stdout` directly -- in order to be network transparent.)
- # So, just ignore.
- pass
- else:
- raise
- @contextmanager
- def _blocking_io(io: IO[str]) -> Iterator[None]:
- """
- Ensure that the FD for `io` is set to blocking in here.
- """
- if sys.platform == "win32":
- # On Windows, the `os` module doesn't have a `get/set_blocking`
- # function.
- yield
- return
- try:
- fd = io.fileno()
- blocking = os.get_blocking(fd)
- except: # noqa
- # Failed somewhere.
- # `get_blocking` can raise `OSError`.
- # The io object can raise `AttributeError` when no `fileno()` method is
- # present if we're not a real file object.
- blocking = True # Assume we're good, and don't do anything.
- try:
- # Make blocking if we weren't blocking yet.
- if not blocking:
- os.set_blocking(fd, True)
- yield
- finally:
- # Restore original blocking mode.
- if not blocking:
- os.set_blocking(fd, blocking)
|