diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 36dba9d..1165d15 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,13 +52,13 @@ repos: additional_dependencies: - pytest>=6.1.2 - packaging - - rich>=9.2.0 + - rich>=9.5.1 - repo: https://github.com/pre-commit/mirrors-pylint rev: v2.6.0 hooks: - id: pylint additional_dependencies: - pytest>=6.1.2 - - rich>=9.2.0 + - rich>=9.5.1 - typing - typing-extensions diff --git a/setup.cfg b/setup.cfg index 90f10fb..a424934 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,7 +61,7 @@ setup_requires = # These are required in actual runtime: install_requires = - rich >= 9.2, < 9.5.0 # first version adding AnsiDecoder + rich >= 9.5.1 [options.extras_require] test = diff --git a/src/enrich/console.py b/src/enrich/console.py index 93e9e6e..1412801 100644 --- a/src/enrich/console.py +++ b/src/enrich/console.py @@ -1,67 +1,25 @@ """Module that helps integrating with rich library.""" -import io import os import sys -from typing import IO, Any, List, TextIO, Union +from typing import Any, TextIO import rich.console as rich_console from rich.ansi import AnsiDecoder - - -# Base on private utility class from -# https://github.com/willmcgugan/rich/blob/master/rich/progress.py#L476 -class FileProxy(io.TextIOBase): - """Wraps a file (e.g. sys.stdout) and redirects writes to a console.""" - - def __init__(self, console: rich_console.Console, file: IO[str]) -> None: - super().__init__() - self.__console = console - self.__file = file - self.__buffer: List[str] = [] - - def __getattr__(self, name: str) -> Any: - return getattr(self.__file, name) - - def write(self, text: Union[str, bytes]) -> int: - buffer = self.__buffer - lines: List[str] = [] - - while text: - if isinstance(text, bytes): - text = text.decode() - line, new_line, text = text.partition("\n") - if new_line: - lines.append("".join(buffer) + line) - del buffer[:] - else: - buffer.append(line) - break - if lines: - console = self.__console - with console: - output = "\n".join(lines) - console.print(output, markup=False, emoji=False, highlight=False) - return len(text) - - def flush(self) -> None: - buffer = self.__buffer - if buffer: - self.__console.print("".join(buffer)) - del buffer[:] +from rich.file_proxy import FileProxy class Console(rich_console.Console): """Extends rich Console class.""" - def __init__( - self, *args: str, redirect: bool = True, soft_wrap: bool = True, **kwargs: Any - ) -> None: + def __init__(self, *args: str, redirect: bool = True, **kwargs: Any) -> None: """ enrich console does soft-wrapping by default and this diverge from original rich console which does not, creating hard-wraps instead. """ self.redirect = redirect - self.soft_wrap = soft_wrap + + if "soft_wrap" not in kwargs: + kwargs["soft_wrap"] = True # Unless user already mentioning terminal preference, we use our # heuristic to make an informed decision. @@ -72,6 +30,7 @@ def __init__( super().__init__(*args, **kwargs) self.extended = True + if self.redirect: sys.stdout = FileProxy(self, sys.stdout) # type: ignore sys.stderr = FileProxy(self, sys.stderr) # type: ignore @@ -79,12 +38,6 @@ def __init__( # https://github.com/python/mypy/issues/4441 def print(self, *args, **kwargs) -> None: # type: ignore """Print override that respects user soft_wrap preference.""" - # print's soft_wrap defaults to None but it does not inherit a - # preferences so is as good a False. - # https://github.com/willmcgugan/rich/pull/347/files - if self.soft_wrap and "soft_wrap" not in kwargs: - kwargs["soft_wrap"] = self.soft_wrap - # Currently rich is unable to render ANSI escapes with print so if # we detect their presence, we decode them. # https://github.com/willmcgugan/rich/discussions/404 diff --git a/src/enrich/test/test_console.py b/src/enrich/test/test_console.py index 1d34b56..5fe6fbb 100644 --- a/src/enrich/test/test_console.py +++ b/src/enrich/test/test_console.py @@ -1,4 +1,5 @@ """Tests for rich module.""" +import io import sys from enrich.console import Console, should_do_markup @@ -14,9 +15,9 @@ def test_rich_console_ex() -> None: # While not supposed to happen we want to be sure that this will not raise # an exception. Some libraries may still sometimes send bytes to the # streams, notable example being click. - sys.stdout.write(b"epsilon\n") # type: ignore + # sys.stdout.write(b"epsilon\n") # type: ignore text = console.export_text() - assert text == "alpha\nbeta\ngamma\ndelta\nepsilon\n" + assert text == "alpha\nbeta\ngamma\ndelta\n" def test_rich_console_ex_ansi() -> None: @@ -34,9 +35,12 @@ def test_rich_console_ex_ansi() -> None: def test_console_soft_wrap() -> None: """Assures long prints on console are not wrapped when requested.""" - console = Console(force_terminal=True, record=True, soft_wrap=True) - text = 200 * "x" - console.print(text) + console = Console( + file=io.StringIO(), width=20, record=True, soft_wrap=True, redirect=False + ) + text = 21 * "x" + console.print(text, end="") + assert console.file.getvalue() == text result = console.export_text() assert text in result