Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/_pyrepl/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class Reader:
that we're done.
"""

console: console.Console
console: console.Console = field(repr=False)

## state
buffer: list[str] = field(default_factory=list)
Expand Down
93 changes: 92 additions & 1 deletion Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
multiline_input,
code_to_events,
)
from _pyrepl.console import Event
from typing import IO

from _pyrepl.console import Console, Event
from _pyrepl._module_completer import (
ImportParser,
ModuleCompleter,
Expand Down Expand Up @@ -1414,6 +1416,95 @@ def test_dumb_terminal_exits_cleanly(self):
self.assertNotIn("Traceback", output)


class TestConsoleRepr(TestCase):

class _StubConsole(Console):
def __init__(
self,
f_in: int | IO[bytes] = 0,
f_out: int | IO[bytes] = 1,
term: str = "",
encoding: str = "",
) -> None:
super().__init__(f_in, f_out, term, encoding)
self.height = 25
self.width = 80
self.screen = []

def refresh(self, screen: list[str], xy: tuple[int, int]) -> None:
pass

def prepare(self) -> None:
pass

def restore(self) -> None:
pass

def move_cursor(self, x: int, y: int) -> None:
pass

def set_cursor_vis(self, visible: bool) -> None:
pass

def getheightwidth(self) -> tuple[int, int]:
return self.height, self.width

def get_event(self, block: bool = True) -> Event | None:
return None

def push_char(self, char: int | bytes) -> None:
pass

def beep(self) -> None:
pass

def clear(self) -> None:
pass

def finish(self) -> None:
pass

def flushoutput(self) -> None:
pass

def forgetinput(self) -> None:
pass

def getpending(self) -> Event:
return Event("key", "", b"")

def wait(self, timeout: float | None = None) -> bool:
return False

def repaint(self) -> None:
pass

@property
def input_hook(self):
return None

def test_reader_repr_avoids_console_field(self):
console = self._StubConsole()
config = ReadlineConfig(readline_completer=None)

reader = ReadlineAlikeReader(console=console, config=config)

repr_str = repr(reader)

self.assertIsInstance(repr_str, str)
self.assertNotIn("console=", repr_str)

def test_wrapper_reader_repr_before_prepare(self):
with patch("_pyrepl.readline.Console", self._StubConsole), patch(
"_pyrepl.readline.os.dup",
side_effect=lambda fd: fd,
):
wrapper = _ReadlineWrapper()
reader = wrapper.get_reader()

self.assertIsInstance(repr(reader), str)


@skipUnless(pty, "requires pty")
@skipIf((os.environ.get("TERM") or "dumb") == "dumb", "can't use pyrepl in dumb terminal")
class TestMain(ReplTestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Avoid an ``AttributeError`` when calling :func:`repr` on
``_pyrepl.readline._wrapper.reader`` before the console has been prepared.
Loading