Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

robust handling of fileno #2678

Merged
merged 7 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bumped minimum Python version to 3.7 https://github.com/Textualize/rich/pull/2567
- Pretty-printing of "tagged" `__repr__` results is now greedy when matching tags https://github.com/Textualize/rich/pull/2565

### Fixed

- Handling of broken `fileno` made more robust. Fixes https://github.com/Textualize/rich/issues/2645

### Added

- Add type annotation for key_separator of pretty.Node https://github.com/Textualize/rich/issues/2625


## [12.6.0] - 2022-10-02

### Added
Expand Down
24 changes: 24 additions & 0 deletions rich/_fileno.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations

from typing import IO, Callable


def get_fileno(file_like: IO[str]) -> int | None:
"""Get fileno() from a file, accounting for poorly implemented file-like objects.
Args:
file_like (IO): A file-like object.
Returns:
int | None: The result of fileno if available, or None if operation failed.
"""
fileno: Callable[[], int] | None = getattr(file_like, "fileno", None)
if fileno is not None:
try:
return fileno()
except Exception:
# `fileno` is documented as potentially raising a OSError
# Alas, from the issues, there are so many poorly implemented file-like objects,
# that `fileno()` can raise just about anything.
return None
return None
9 changes: 4 additions & 5 deletions rich/console.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import inspect
import io
import os
import platform
import sys
Expand Down Expand Up @@ -48,6 +47,7 @@
from . import errors, themes
from ._emoji_replace import _emoji_replace
from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT
from ._fileno import get_fileno
from ._log_render import FormatTimeCallable, LogRender
from .align import Align, AlignMethod
from .color import ColorSystem, blend_rgb
Expand Down Expand Up @@ -2006,12 +2006,11 @@ def _check_buffer(self) -> None:
if WINDOWS:
use_legacy_windows_render = False
if self.legacy_windows:
try:
fileno = get_fileno(self.file)
if fileno is not None:
use_legacy_windows_render = (
self.file.fileno() in _STD_STREAMS_OUTPUT
fileno in _STD_STREAMS_OUTPUT
)
except (ValueError, io.UnsupportedOperation):
pass

if use_legacy_windows_render:
from rich._win32_console import LegacyWindowsTerm
Expand Down
25 changes: 25 additions & 0 deletions tests/test_getfileno.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from rich._fileno import get_fileno


def test_get_fileno():
class FileLike:
def fileno(self) -> int:
return 123

assert get_fileno(FileLike()) == 123


def test_get_fileno_missing():
class FileLike:
pass

assert get_fileno(FileLike()) is None


def test_get_fileno_broken():
class FileLike:
def fileno(self) -> int:
1 / 0
return 123

assert get_fileno(FileLike()) is None