Skip to content

Commit

Permalink
Add better support for pretty-printing record types
Browse files Browse the repository at this point in the history
  • Loading branch information
DRMacIver committed Oct 9, 2024
1 parent a81d155 commit 714d5ac
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 0 deletions.
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RELEASE_TYPE: minor

This improves the formatting of dataclasses and attrs classes when printing
falsifying examples.
28 changes: 28 additions & 0 deletions hypothesis-python/src/hypothesis/vendor/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,20 @@ def pretty(self, obj):
meth = cls._repr_pretty_
if callable(meth):
return meth(obj, self, cycle)
if hasattr(cls, "__attrs_attrs__"):
return pprint_fields(
obj,
self,
cycle,
[at.name for at in cls.__attrs_attrs__],
)
if hasattr(cls, "__dataclass_fields__"):
return pprint_fields(
obj,
self,
cycle,
list(cls.__dataclass_fields__),
)
# Now check for object-specific printers which show how this
# object was constructed (a Hypothesis special feature).
printers = self.known_object_printers[IDKey(obj)]
Expand Down Expand Up @@ -718,6 +732,20 @@ def _repr_pprint(obj, p, cycle):
p.text(output_line)


def pprint_fields(obj, p, cycle, fields):
name = obj.__class__.__name__
if cycle:
return p.text(f"{name}(...)")
with p.group(1, name + "(", ")"):
for idx, field in enumerate(fields):
if idx:
p.text(",")
p.breakable()
p.text(field)
p.text("=")
p.pretty(getattr(obj, field))


def _function_pprint(obj, p, cycle):
"""Base pprint for all functions and builtin functions."""
from hypothesis.internal.reflection import get_pretty_function_description
Expand Down
88 changes: 88 additions & 0 deletions hypothesis-python/tests/cover/test_pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@
import struct
import warnings
from collections import Counter, OrderedDict, defaultdict, deque
from dataclasses import dataclass
from enum import Enum, Flag
from functools import partial

import attrs
import pytest

from hypothesis import given, strategies as st
Expand Down Expand Up @@ -758,3 +760,89 @@ def test_pprint_extremely_large_integers():
got = p.getvalue()
assert got == f"{x:#_x}" # hexadecimal with underscores
assert eval(got) == x


class ReprDetector:
def _repr_pretty_(self, p, cycle):
"""Exercise the IPython callback interface."""
p.text("GOOD")

def __repr__(self):
return "BAD"


@dataclass
class SomeDataClass:
x: object


def test_pretty_prints_data_classes():
assert pretty.pretty(SomeDataClass(ReprDetector())) == "SomeDataClass(x=GOOD)"


@attrs.define
class SomeAttrsClass:
x: ReprDetector


def test_pretty_prints_attrs_classes():
assert pretty.pretty(SomeAttrsClass(ReprDetector())) == "SomeAttrsClass(x=GOOD)"


@attrs.define
class SomeAttrsClassWithCustomPretty:
def _repr_pretty_(self, p, cycle):
"""Exercise the IPython callback interface."""
p.text("I am a banana")


def test_custom_pretty_print_method_overrides_field_printing():
assert pretty.pretty(SomeAttrsClassWithCustomPretty()) == "I am a banana"


@attrs.define
class SomeAttrsClassWithLotsOfFields:
a: int
b: int
c: int
d: int
e: int
f: int
g: int
h: int
i: int
j: int
k: int
l: int
m: int
n: int
o: int
p: int
q: int
r: int
s: int


def test_will_line_break_between_fields():
obj = SomeAttrsClassWithLotsOfFields(
**{
at.name: 12345678900000000000000001
for at in SomeAttrsClassWithLotsOfFields.__attrs_attrs__
}
)
assert "\n" in pretty.pretty(obj)


@attrs.define
class SomeDataClassWithNoFields: ...


def test_prints_empty_dataclass_correctly():
assert pretty.pretty(SomeDataClassWithNoFields()) == "SomeDataClassWithNoFields()"


def test_handles_cycles_in_dataclass():
x = SomeDataClass(x=1)
x.x = x

assert pretty.pretty(x) == "SomeDataClass(x=SomeDataClass(...))"

0 comments on commit 714d5ac

Please sign in to comment.