diff options
author | arcadia-devtools <arcadia-devtools@yandex-team.ru> | 2022-04-06 18:18:01 +0300 |
---|---|---|
committer | arcadia-devtools <arcadia-devtools@yandex-team.ru> | 2022-04-06 18:18:01 +0300 |
commit | 01fbacb386809436dfa331780875aed72cb76118 (patch) | |
tree | 04c911ad96ff0523bd4d3e7a45c23cf2f2d7607d /contrib/python/prompt-toolkit/py3/prompt_toolkit/input | |
parent | 48fb997d7f820a474b9094a72d9798a95ec612b7 (diff) | |
download | ydb-01fbacb386809436dfa331780875aed72cb76118.tar.gz |
intermediate changes
ref:b4f892f3c2b06a356c155f73c27efc5661a7fb89
Diffstat (limited to 'contrib/python/prompt-toolkit/py3/prompt_toolkit/input')
7 files changed, 111 insertions, 30 deletions
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py index 421d4ccdf4..dc319769ef 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/__init__.py @@ -1,9 +1,10 @@ -from .base import DummyInput, Input +from .base import DummyInput, Input, PipeInput from .defaults import create_input, create_pipe_input __all__ = [ # Base. "Input", + "PipeInput", "DummyInput", # Defaults. "create_input", diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py index 9885a37bc2..313622de5a 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/base.py @@ -9,6 +9,7 @@ from prompt_toolkit.key_binding import KeyPress __all__ = [ "Input", + "PipeInput", "DummyInput", ] @@ -104,6 +105,9 @@ class PipeInput(Input): class DummyInput(Input): """ Input for use in a `DummyApplication` + + If used in an actual application, it will make the application render + itself once and exit immediately, due to an `EOFError`. """ def fileno(self) -> int: @@ -117,6 +121,8 @@ class DummyInput(Input): @property def closed(self) -> bool: + # This needs to be true, so that the dummy input will trigger an + # `EOFError` immediately in the application. return True def raw_mode(self) -> ContextManager[None]: diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py index 347f8c6ad3..197dcb9a60 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/defaults.py @@ -1,7 +1,5 @@ import sys -from typing import Optional, TextIO - -from prompt_toolkit.utils import is_windows +from typing import ContextManager, Optional, TextIO from .base import DummyInput, Input, PipeInput @@ -23,7 +21,7 @@ def create_input( `sys.stdin`. (We can open `stdout` or `stderr` for reading, this is how a `$PAGER` works.) """ - if is_windows(): + if sys.platform == "win32": from .win32 import Win32Input # If `stdin` was assigned `None` (which happens with pythonw.exe), use @@ -48,16 +46,24 @@ def create_input( return Vt100Input(stdin) -def create_pipe_input() -> PipeInput: +def create_pipe_input() -> ContextManager[PipeInput]: """ Create an input pipe. This is mostly useful for unit testing. + + Usage:: + + with create_pipe_input() as input: + input.send_text('inputdata') + + Breaking change: In prompt_toolkit 3.0.28 and earlier, this was returning + the `PipeInput` directly, rather than through a context manager. """ - if is_windows(): + if sys.platform == "win32": from .win32_pipe import Win32PipeInput - return Win32PipeInput() + return Win32PipeInput.create() else: from .posix_pipe import PosixPipeInput - return PosixPipeInput() + return PosixPipeInput.create() diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py index 22dd7be6b5..1e7dec77fd 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/posix_pipe.py @@ -1,5 +1,10 @@ +import sys + +assert sys.platform != "win32" + import os -from typing import ContextManager, TextIO, cast +from contextlib import contextmanager +from typing import ContextManager, Iterator, TextIO, cast from ..utils import DummyContext from .base import PipeInput @@ -10,6 +15,36 @@ __all__ = [ ] +class _Pipe: + "Wrapper around os.pipe, that ensures we don't double close any end." + + def __init__(self) -> None: + self.read_fd, self.write_fd = os.pipe() + self._read_closed = False + self._write_closed = False + + def close_read(self) -> None: + "Close read-end if not yet closed." + if self._read_closed: + return + + os.close(self.read_fd) + self._read_closed = True + + def close_write(self) -> None: + "Close write-end if not yet closed." + if self._write_closed: + return + + os.close(self.write_fd) + self._write_closed = True + + def close(self) -> None: + "Close both read and write ends." + self.close_read() + self.close_write() + + class PosixPipeInput(Vt100Input, PipeInput): """ Input that is send through a pipe. @@ -18,14 +53,15 @@ class PosixPipeInput(Vt100Input, PipeInput): Usage:: - input = PosixPipeInput() - input.send_text('inputdata') + with PosixPipeInput.create() as input: + input.send_text('inputdata') """ _id = 0 - def __init__(self, text: str = "") -> None: - self._r, self._w = os.pipe() + def __init__(self, _pipe: _Pipe, _text: str = "") -> None: + # Private constructor. Users should use the public `.create()` method. + self.pipe = _pipe class Stdin: encoding = "utf-8" @@ -34,21 +70,30 @@ class PosixPipeInput(Vt100Input, PipeInput): return True def fileno(stdin) -> int: - return self._r + return self.pipe.read_fd super().__init__(cast(TextIO, Stdin())) - self.send_text(text) + self.send_text(_text) # Identifier for every PipeInput for the hash. self.__class__._id += 1 self._id = self.__class__._id + @classmethod + @contextmanager + def create(cls, text: str = "") -> Iterator["PosixPipeInput"]: + pipe = _Pipe() + try: + yield PosixPipeInput(_pipe=pipe, _text=text) + finally: + pipe.close() + def send_bytes(self, data: bytes) -> None: - os.write(self._w, data) + os.write(self.pipe.write_fd, data) def send_text(self, data: str) -> None: "Send text to the input." - os.write(self._w, data.encode("utf-8")) + os.write(self.pipe.write_fd, data.encode("utf-8")) def raw_mode(self) -> ContextManager[None]: return DummyContext() @@ -58,12 +103,11 @@ class PosixPipeInput(Vt100Input, PipeInput): def close(self) -> None: "Close pipe fds." - os.close(self._r) - os.close(self._w) - - # We should assign `None` to 'self._r` and 'self._w', - # The event loop still needs to know the the fileno for this input in order - # to properly remove it from the selectors. + # Only close the write-end of the pipe. This will unblock the reader + # callback (in vt100.py > _attached_input), which eventually will raise + # `EOFError`. If we'd also close the read-end, then the event loop + # won't wake up the corresponding callback because of this. + self.pipe.close_write() def typeahead_hash(self) -> str: """ diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py index 639d372609..45ce37208a 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/vt100.py @@ -1,6 +1,9 @@ +import sys + +assert sys.platform != "win32" + import contextlib import io -import sys import termios import tty from asyncio import AbstractEventLoop diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py index c59375b3d4..db3fa2badd 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32.py @@ -7,6 +7,8 @@ from prompt_toolkit.eventloop import get_event_loop from ..utils import SPHINX_AUTODOC_RUNNING +assert sys.platform == "win32" + # Do not import win32-specific stuff when generating documentation. # Otherwise RTD would be unable to generate docs for this module. if not SPHINX_AUTODOC_RUNNING: diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py index fdbcb8ee83..ebee2075ed 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/input/win32_pipe.py @@ -1,6 +1,11 @@ +import sys + +assert sys.platform == "win32" + +from contextlib import contextmanager from ctypes import windll from ctypes.wintypes import HANDLE -from typing import Callable, ContextManager, List +from typing import Callable, ContextManager, Iterator, List from prompt_toolkit.eventloop.win32 import create_win32_event @@ -31,7 +36,7 @@ class Win32PipeInput(_Win32InputBase, PipeInput): _id = 0 - def __init__(self) -> None: + def __init__(self, _event: HANDLE) -> None: super().__init__() # Event (handle) for registering this input in the event loop. # This event is set when there is data available to read from the pipe. @@ -50,6 +55,15 @@ class Win32PipeInput(_Win32InputBase, PipeInput): self.__class__._id += 1 self._id = self.__class__._id + @classmethod + @contextmanager + def create(cls) -> Iterator["Win32PipeInput"]: + event = create_win32_event() + try: + yield Win32PipeInput(_event=event) + finally: + windll.kernel32.CloseHandle(event) + @property def closed(self) -> bool: return self._closed @@ -87,7 +101,9 @@ class Win32PipeInput(_Win32InputBase, PipeInput): self._buffer = [] # Reset event. - windll.kernel32.ResetEvent(self._event) + if not self._closed: + # (If closed, the event should not reset.) + windll.kernel32.ResetEvent(self._event) return result @@ -111,6 +127,9 @@ class Win32PipeInput(_Win32InputBase, PipeInput): def send_text(self, text: str) -> None: "Send text to the input." + if self._closed: + raise ValueError("Attempt to write into a closed pipe.") + # Pass it through our vt100 parser. self.vt100_parser.feed(text) @@ -124,9 +143,9 @@ class Win32PipeInput(_Win32InputBase, PipeInput): return DummyContext() def close(self) -> None: - "Close pipe handles." - windll.kernel32.CloseHandle(self._event) + "Close write-end of the pipe." self._closed = True + windll.kernel32.SetEvent(self._event) def typeahead_hash(self) -> str: """ |