Skip to content

Commit

Permalink
better handling of dataclasses
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Oct 6, 2021
1 parent b08be77 commit a2b4a95
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 14 deletions.
42 changes: 29 additions & 13 deletions rich/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
from array import array
from collections import Counter, defaultdict, deque, UserDict, UserList
import dataclasses
from dataclasses import dataclass, fields, is_dataclass
from inspect import isclass
from itertools import islice
Expand All @@ -29,16 +30,6 @@
_attr_module = None # type: ignore


def _is_attr_object(obj: Any) -> bool:
"""Check if an object was created with attrs module."""
return _attr_module is not None and _attr_module.has(type(obj))


def _get_attr_fields(obj: Any) -> Iterable["_attr_module.Attribute[Any]"]:
"""Get fields for an attrs object."""
return _attr_module.fields(type(obj)) if _attr_module is not None else []


from .highlighter import ReprHighlighter
from . import get_console
from ._loop import loop_last
Expand All @@ -64,6 +55,33 @@ def _get_attr_fields(obj: Any) -> Iterable["_attr_module.Attribute[Any]"]:
_re_jupyter_repr = re.compile(f"^_repr_.+_$")


def _is_attr_object(obj: Any) -> bool:
"""Check if an object was created with attrs module."""
return _attr_module is not None and _attr_module.has(type(obj))


def _get_attr_fields(obj: Any) -> Iterable["_attr_module.Attribute[Any]"]:
"""Get fields for an attrs object."""
return _attr_module.fields(type(obj)) if _attr_module is not None else []


def _is_dataclass_repr(obj: object) -> bool:
"""Check if an instance of a dataclass contains the default repr.
Args:
obj (object): A dataclass instance.
Returns:
bool: True if the default repr is used, False if there is a custom repr.
"""
# Digging in to a lot of internals here
# Catching all exceptions in case something is missing on a non CPython implementation
try:
return obj.__repr__.__code__.co_filename == dataclasses.__file__
except Exception:
return False


def install(
console: Optional["Console"] = None,
overflow: "OverflowMethod" = "ignore",
Expand Down Expand Up @@ -610,9 +628,7 @@ def iter_attrs() -> Iterable[
is_dataclass(obj)
and not isinstance(obj, type)
and not fake_attributes
and (
"__create_fn__" in obj.__repr__.__qualname__ or py_version == (3, 6)
) # Check if __repr__ wasn't overridden
and _is_dataclass_repr(obj)
):
obj_id = id(obj)
if obj_id in visited_ids:
Expand Down
59 changes: 58 additions & 1 deletion tests/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,26 @@
reason="rendered differently on py3.6",
)


skip_py37 = pytest.mark.skipif(
sys.version_info.minor == 7 and sys.version_info.major == 3,
reason="rendered differently on py3.7",
)

skip_py38 = pytest.mark.skipif(
sys.version_info.minor == 8 and sys.version_info.major == 3,
reason="rendered differently on py3.8",
)

skip_py39 = pytest.mark.skipif(
sys.version_info.minor == 9 and sys.version_info.major == 3,
reason="rendered differently on py3.9",
)

skip_py310 = pytest.mark.skipif(
sys.version_info.minor == 10 and sys.version_info.major == 3,
reason="rendered differently on py3.10",
)


def render(obj, methods=False, value=False, width=50) -> str:
console = Console(file=io.StringIO(), width=width, legacy_windows=False)
Expand Down Expand Up @@ -151,6 +165,45 @@ def test_inspect_integer_with_value():

@skip_py36
@skip_py37
@skip_py310
def test_inspect_integer_with_methods():

expected = (
"╭──────────────── <class 'int'> ─────────────────╮\n"
"│ int([x]) -> integer │\n"
"│ int(x, base=10) -> integer │\n"
"│ │\n"
"│ denominator = 1 │\n"
"│ imag = 0 │\n"
"│ numerator = 1 │\n"
"│ real = 1 │\n"
"│ as_integer_ratio = def as_integer_ratio(): │\n"
"│ Return integer ratio. │\n"
"│ bit_length = def bit_length(): Number of │\n"
"│ bits necessary to represent │\n"
"│ self in binary. │\n"
"│ conjugate = def conjugate(...) Returns │\n"
"│ self, the complex conjugate │\n"
"│ of any int. │\n"
"│ from_bytes = def from_bytes(bytes, │\n"
"│ byteorder, *, │\n"
"│ signed=False): Return the │\n"
"│ integer represented by the │\n"
"│ given array of bytes. │\n"
"│ to_bytes = def to_bytes(length, │\n"
"│ byteorder, *, │\n"
"│ signed=False): Return an │\n"
"│ array of bytes representing │\n"
"│ an integer. │\n"
"╰────────────────────────────────────────────────╯\n"
)
assert expected == render(1, methods=True)


@skip_py36
@skip_py37
@skip_py38
@skip_py39
def test_inspect_integer_with_methods():

expected = (
Expand All @@ -164,6 +217,10 @@ def test_inspect_integer_with_methods():
"│ real = 1 │\n"
"│ as_integer_ratio = def as_integer_ratio(): │\n"
"│ Return integer ratio. │\n"
"│ bit_count = def bit_count(): Number of │\n"
"│ ones in the binary │\n"
"│ representation of the │\n"
"│ absolute value of self. │\n"
"│ bit_length = def bit_length(): Number of │\n"
"│ bits necessary to represent │\n"
"│ self in binary. │\n"
Expand Down

0 comments on commit a2b4a95

Please sign in to comment.