Skip to content

Commit

Permalink
fixed honoring custom repr for NamedTuple if assigned by partialmethod (
Browse files Browse the repository at this point in the history
#14466)

Fixes #14465.

- added test `test_custom_repr_namedtuple_partialmethod`
  • Loading branch information
Carreau committed Jun 25, 2024
2 parents 1454013 + 60967bb commit 0a293a6
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 24 deletions.
59 changes: 37 additions & 22 deletions IPython/core/tests/test_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def set_fp(p):

def test_for_type():
f = PlainTextFormatter()

# initial return, None
assert f.for_type(C, foo_printer) is None
# no func queries
Expand All @@ -116,9 +116,9 @@ def test_for_type():

def test_for_type_string():
f = PlainTextFormatter()

type_str = '%s.%s' % (C.__module__, 'C')

# initial return, None
assert f.for_type(type_str, foo_printer) is None
# no func queries
Expand All @@ -130,9 +130,9 @@ def test_for_type_string():

def test_for_type_by_name():
f = PlainTextFormatter()

mod = C.__module__

# initial return, None
assert f.for_type_by_name(mod, "C", foo_printer) is None
# no func queries
Expand All @@ -146,7 +146,7 @@ def test_for_type_by_name():

def test_lookup():
f = PlainTextFormatter()

f.for_type(C, foo_printer)
assert f.lookup(C()) is foo_printer
with pytest.raises(KeyError):
Expand All @@ -155,7 +155,7 @@ def test_lookup():
def test_lookup_string():
f = PlainTextFormatter()
type_str = '%s.%s' % (C.__module__, 'C')

f.for_type(type_str, foo_printer)
assert f.lookup(C()) is foo_printer
# should move from deferred to imported dict
Expand All @@ -173,16 +173,16 @@ def test_lookup_by_type_string():
f = PlainTextFormatter()
type_str = '%s.%s' % (C.__module__, 'C')
f.for_type(type_str, foo_printer)

# verify insertion
assert _mod_name_key(C) in f.deferred_printers
assert C not in f.type_printers

assert f.lookup_by_type(type_str) is foo_printer
# lookup by string doesn't cause import
assert _mod_name_key(C) in f.deferred_printers
assert C not in f.type_printers

assert f.lookup_by_type(C) is foo_printer
# should move from deferred to imported dict
assert _mod_name_key(C) not in f.deferred_printers
Expand Down Expand Up @@ -220,10 +220,10 @@ def test_pop():
def test_pop_string():
f = PlainTextFormatter()
type_str = '%s.%s' % (C.__module__, 'C')

with pytest.raises(KeyError):
f.pop(type_str)

f.for_type(type_str, foo_printer)
f.pop(type_str)
with pytest.raises(KeyError):
Expand All @@ -238,7 +238,7 @@ def test_pop_string():
with pytest.raises(KeyError):
f.pop(type_str)
assert f.pop(type_str, None) is None


def test_error_method():
f = HTMLFormatter()
Expand Down Expand Up @@ -341,14 +341,14 @@ def __getattr__(self, key):
assert text_hat._repr_html_ == "_repr_html_"
with capture_output() as captured:
result = f(text_hat)

assert result is None
assert "FormatterWarning" not in captured.stderr

class CallableMagicHat(object):
def __getattr__(self, key):
return lambda : key

call_hat = CallableMagicHat()
with capture_output() as captured:
result = f(call_hat)
Expand All @@ -358,11 +358,11 @@ def __getattr__(self, key):
class BadReprArgs(object):
def _repr_html_(self, extra, args):
return "html"

bad = BadReprArgs()
with capture_output() as captured:
result = f(bad)

assert result is None
assert "FormatterWarning" not in captured.stderr

Expand Down Expand Up @@ -406,13 +406,13 @@ def _ipython_display_(self):
class NotSelfDisplaying(object):
def __repr__(self):
return "NotSelfDisplaying"

def _ipython_display_(self):
raise NotImplementedError

save_enabled = f.ipython_display_formatter.enabled
f.ipython_display_formatter.enabled = True

yes = SelfDisplaying()
no = NotSelfDisplaying()

Expand Down Expand Up @@ -444,7 +444,7 @@ def _repr_png_(self):
return 'should-be-overwritten'
def _repr_html_(self):
return '<b>hi!</b>'

f = get_ipython().display_formatter
html_f = f.formatters['text/html']
save_enabled = html_f.enabled
Expand Down Expand Up @@ -507,7 +507,7 @@ def _repr_mimebundle_(self, include=None, exclude=None):
}
}
return (data, metadata)

f = get_ipython().display_formatter
obj = HasReprMimeMeta()
d, md = f.format(obj)
Expand All @@ -529,3 +529,18 @@ def _repr_mimebundle_(self, include=None, exclude=None):
obj = BadReprMime()
d, md = f.format(obj)
assert "text/plain" in d


def test_custom_repr_namedtuple_partialmethod():
from functools import partialmethod
from typing import NamedTuple

class Foo(NamedTuple):
...

Foo.__repr__ = partialmethod(lambda obj: "Hello World")
foo = Foo()

f = PlainTextFormatter()
assert f.pprint
assert f(foo) == "Hello World"
12 changes: 10 additions & 2 deletions IPython/lib/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,16 @@ def pretty(self, obj):
meth = cls._repr_pretty_
if callable(meth):
return meth(obj, self, cycle)
if cls is not object \
and callable(cls.__dict__.get('__repr__')):
if (
cls is not object
# check if cls defines __repr__
and "__repr__" in cls.__dict__
# check if __repr__ is callable.
# Note: we need to test getattr(cls, '__repr__')
# instead of cls.__dict__['__repr__']
# in order to work with descriptors like partialmethod,
and callable(_safe_getattr(cls, "__repr__", None))
):
return _repr_pprint(obj, self, cycle)

return _default_pprint(obj, self, cycle)
Expand Down

0 comments on commit 0a293a6

Please sign in to comment.