Skip to content

Commit

Permalink
Reproduction pytype issue
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 527212717
  • Loading branch information
Conchylicultor authored and The etils Authors committed Apr 26, 2023
1 parent 0249a70 commit 883d68f
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 67 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ Changelog follow https://keepachangelog.com/ format.
* Add `contextvars` option: Fields annotated as `edc.ContextVars[T]` are
wrapped in `contextvars.ContextVars`.
* Fix error when using `_: dataclasses.KW_ONLY`
* `__repr__` now also pretty-print nested dataclasses, list, dict,...
* `epy`:
* Better `epy.Lines.block` for custom pretty print classes, list,...
* Better `epy.Lines.make_block` for custom pretty print classes, list,...

## [1.2.0] - 2023-04-03

Expand Down
2 changes: 1 addition & 1 deletion etils/edc/dataclass_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def __repr__(self) -> str: # pylint: disable=invalid-name
return epy.Lines.make_block(
header=self.__class__.__name__,
content={
field.name: repr(getattr(self, field.name))
field.name: getattr(self, field.name)
for field in all_fields
if field.repr
},
Expand Down
23 changes: 5 additions & 18 deletions etils/edc/dataclass_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class KwOnly:
assert a.y == 2

with pytest.raises(TypeError, match='contructor is keyword-only.'):
_ = KwOnly(1, 2)
_ = KwOnly(1, 2) # pylint: disable=missing-kwoa


@edc.dataclass
Expand Down Expand Up @@ -104,18 +104,12 @@ class R1Field:


def test_repr():
assert repr(R(123, R11(y='abc'))) == epy.dedent(
"""
assert repr(R(123, R11(y='abc'))) == epy.dedent("""
R(
x=123,
y=R11(
x=None,
y='abc',
z=None,
),
)
"""
y=R11(x=None, y='abc', z=None),
)
""")

# Curstom __repr__
assert repr(R2()) == 'R2 repr'
Expand All @@ -129,11 +123,4 @@ def test_repr():
x = R()
x.x = x
assert repr(x) == edc.repr(x)
assert repr(x) == epy.dedent(
"""
R(
x=...,
y=None,
)
"""
)
assert repr(x) == 'R(x=..., y=None)'
11 changes: 3 additions & 8 deletions etils/edc/frozen_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,11 @@ def test_unfrozen_call_twice():
y = x.y
x.x = 123

assert repr(x) == epy.dedent(
"""
assert repr(x) == epy.dedent("""
_MutableProxy(A(
x=123,
y=A(
x=456,
y=None,
),
))"""
)
y=A(x=456, y=None),
))""")

# Attribute still accessible
assert x.not_a_dataclass_attr() == 123
Expand Down
42 changes: 37 additions & 5 deletions etils/epy/text_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import contextlib
import dataclasses
import textwrap
from typing import Iterable, Iterator, Union
from typing import Any, Iterable, Iterator, Union

_BRACE_TO_BRACES = {
'(': ('(', ')'),
Expand Down Expand Up @@ -131,11 +131,11 @@ def join(self, *, collapse: bool = False) -> str:
def make_block(
cls,
header: str = '',
content: str | dict[str, str] | list[str] | tuple[str, ...] = (),
content: str | dict[str, Any] | list[Any] | tuple[Any, ...] = (),
*,
braces: Union[str, tuple[str, str]] = '(',
equal: str = '=',
limit: int = 10,
limit: int = 20,
) -> str:
"""Util function to create a code block.
Expand Down Expand Up @@ -178,9 +178,9 @@ def make_block(
content = [content]

if isinstance(content, dict):
parts = [f'{k}{equal}{v}' for k, v in content.items()]
parts = [f'{k}{equal}{_repr_value(v)}' for k, v in content.items()]
elif isinstance(content, (list, tuple)):
parts = [f'{v}' for v in content]
parts = [f'{_repr_value(v)}' for v in content]
else:
raise TypeError(f'Invalid fields {type(content)}')

Expand All @@ -203,6 +203,38 @@ def make_block(

return lines.join(collapse=collapse)

@classmethod
def repr(cls, obj: Any) -> str:
"""Pretty print object."""
return _repr_value(obj)


def _repr_value(obj: Any) -> str:
"""Object representation, pretty-display for list, dict,..."""
from etils import edc # pylint: disable=g-import-not-at-top

if isinstance(obj, str):
return repr(obj)
elif type(obj) in (list, tuple): # Skip sub-class as could have custom repr
return Lines.make_block(
content=obj,
braces='[' if isinstance(obj, list) else '(',
)
elif type(obj) is dict: # pylint: disable=unidiomatic-typecheck
return Lines.make_block(
content={repr(k): v for k, v in obj.items()},
braces='{',
equal=': ',
)
elif (
not isinstance(obj, type)
and dataclasses.is_dataclass(obj)
and edc.dataclass_utils.has_default_repr(type(obj))
):
return edc.repr(obj)
else:
return repr(obj)


def dedent(text: str) -> str:
r"""Wrapper around `textwrap.dedent` which also `strip()` the content.
Expand Down
81 changes: 48 additions & 33 deletions etils/epy/text_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

"""Tests for text_utils."""

from __future__ import annotations

import dataclasses
import textwrap

from etils import epy
Expand Down Expand Up @@ -60,49 +63,61 @@ def test_lines():
def test_lines_block():
assert epy.Lines.make_block('A', {}) == 'A()'
assert epy.Lines.make_block('A', {}, braces='[') == 'A[]'
assert epy.Lines.make_block('A', {'x': '1'}) == 'A(x=1)'
assert epy.Lines.make_block('A', {'x': '1'}, braces=('<', '>')) == 'A<x=1>'
assert (
epy.Lines.make_block('', {'x': '1'}, braces='{', equal=': ') == '{x: 1}'
)
assert epy.Lines.make_block('A', {'x': 1}) == 'A(x=1)'
assert epy.Lines.make_block('A', {'x': 1}, braces=('<', '>')) == 'A<x=1>'
assert epy.Lines.make_block('', {'x': 1}, braces='{', equal=': ') == '{x: 1}'
assert epy.Lines.make_block(
'A',
{
'x': '111',
'y': '222',
'z': '333',
'x': 111,
'y': 222,
'z': 333,
},
) == epy.dedent(
"""
A(
x=111,
y=222,
z=333,
)
A(x=111, y=222, z=333)
"""
)
assert epy.Lines.make_block(
'A',
epy.Lines.make_block(
'A',
{
'x': '111',
'y': '222',
'z': '333',
},

assert epy.Lines.make_block('', ['a', 'b'], braces='[') == "['a', 'b']"


def test_lines_std():
@dataclasses.dataclass
class B:
x: int = 1
y: int = 2

@dataclasses.dataclass
class A:
t: tuple[str, ...] = ()
l: list[int] = dataclasses.field(default_factory=list)
d: dict[str, int] = dataclasses.field(default_factory=dict)
dc: B = dataclasses.field(default_factory=B)
s: str = 'aaa'

a = A(
t=('aaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbb'),
l=[1, 2, 3, 4],
d={'aaaaaaaaaaaaaaaaaaaa': 1, 'bbbbbbbbbbbbbbbbbbbb': 1},
)

repr_ = epy.Lines.repr(a)
assert repr_ == epy.dedent("""
A(
t=(
'aaaaaaaaaaaaaaaaaaaa',
'bbbbbbbbbbbbbbbbbbbb',
),
) == epy.dedent(
"""
A(
A(
x=111,
y=222,
z=333,
),
)
"""
l=[1, 2, 3, 4],
d={
'aaaaaaaaaaaaaaaaaaaa': 1,
'bbbbbbbbbbbbbbbbbbbb': 1,
},
dc=B(x=1, y=2),
s='aaa',
)
assert epy.Lines.make_block('', ['a', 'b'], braces='[') == '[a, b]'
""")


def test_lines_nested_indent():
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ ecolab = [
"etils[epy]",
]
edc = [
"typing_extensions",
# Do not add anything here. `edc` is an alias for `epy`
"etils[epy]",
]
enp = [
Expand Down

0 comments on commit 883d68f

Please sign in to comment.