From 3b109bb93a71aaf6dd46eb9a73e4adea60f50b41 Mon Sep 17 00:00:00 2001 From: Yann Kaiser Date: Mon, 4 Dec 2017 08:16:55 -0800 Subject: [PATCH] Fix __repr__ crashing when a member is uninitialized or deleted --- changelog.d/308.breaking.rst | 20 ++++++++++++++++++++ src/attr/_make.py | 2 +- tests/test_dunders.py | 10 ++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 changelog.d/308.breaking.rst diff --git a/changelog.d/308.breaking.rst b/changelog.d/308.breaking.rst new file mode 100644 index 000000000..7c06f5a3b --- /dev/null +++ b/changelog.d/308.breaking.rst @@ -0,0 +1,20 @@ +The ``__repr__`` set by ``attrs`` +no longer produces an ``AttributeError`` +when the instance is missing some of the specified attributes +(either through deleting +or after using ``init=False`` on some attributes). + +This can break code +that relied on ``repr(attr_cls_instance)`` raising ``AttributeError`` +to check if any attr-specified members were unset. + +If you were using this, +you can implement a custom method for checking this:: + + def has_unset_members(self): + for field in attr.fields(type(self)): + try: + getattr(self, field.name) + except AttributeError: + return True + return False diff --git a/src/attr/_make.py b/src/attr/_make.py index 31c5f94ce..6436cbbef 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -849,7 +849,7 @@ def repr_(self): return "{0}({1})".format( class_name, ", ".join( - name + "=" + repr(getattr(self, name)) + name + "=" + repr(getattr(self, name, NOTHING)) for name in attr_names ) ) diff --git a/tests/test_dunders.py b/tests/test_dunders.py index 951faba2f..ddaf46708 100644 --- a/tests/test_dunders.py +++ b/tests/test_dunders.py @@ -200,6 +200,16 @@ class C(object): assert "C(_x=42)" == repr(i) + def test_repr_uninitialized_member(self): + """ + repr signals unset parameters + """ + C = make_class("C", { + "a": attr.ib(init=False), + }) + + assert "C(a=NOTHING)" == repr(C()) + @given(add_str=booleans(), slots=booleans()) def test_str(self, add_str, slots): """