Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reproduction pytype issue #365

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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