diff --git a/rich/_fileno.py b/rich/_fileno.py new file mode 100644 index 00000000..b17ee651 --- /dev/null +++ b/rich/_fileno.py @@ -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 diff --git a/tests/test_getfileno.py b/tests/test_getfileno.py new file mode 100644 index 00000000..7f664ddb --- /dev/null +++ b/tests/test_getfileno.py @@ -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