Do not delete attributes from class body if these is passed (#323)

Fixes #322
This commit is contained in:
Hynek Schlawack 2018-01-16 19:09:23 +01:00 committed by GitHub
parent 9af773bdf3
commit fc2062ea0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 36 additions and 19 deletions

View File

@ -0,0 +1 @@
If ``attr.s`` is passed a *these* argument, it will not attempt to remove attributes with the same name from the class body anymore.

View File

@ -0,0 +1 @@
If ``attr.s`` is passed a *these* argument, it will not attempt to remove attributes with the same name from the class body anymore.

View File

@ -364,7 +364,7 @@ class _ClassBuilder(object):
""" """
__slots__ = ( __slots__ = (
"_cls", "_cls_dict", "_attrs", "_super_names", "_attr_names", "_slots", "_cls", "_cls_dict", "_attrs", "_super_names", "_attr_names", "_slots",
"_frozen", "_has_post_init", "_frozen", "_has_post_init", "_delete_attribs",
) )
def __init__(self, cls, these, slots, frozen, auto_attribs): def __init__(self, cls, these, slots, frozen, auto_attribs):
@ -378,6 +378,7 @@ class _ClassBuilder(object):
self._slots = slots self._slots = slots
self._frozen = frozen or _has_frozen_superclass(cls) self._frozen = frozen or _has_frozen_superclass(cls)
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
self._delete_attribs = not bool(these)
self._cls_dict["__attrs_attrs__"] = self._attrs self._cls_dict["__attrs_attrs__"] = self._attrs
@ -407,6 +408,7 @@ class _ClassBuilder(object):
super_names = self._super_names super_names = self._super_names
# Clean class of attribute definitions (`attr.ib()`s). # Clean class of attribute definitions (`attr.ib()`s).
if self._delete_attribs:
for name in self._attr_names: for name in self._attr_names:
if name not in super_names and \ if name not in super_names and \
getattr(cls, name, None) is not None: getattr(cls, name, None) is not None:
@ -575,7 +577,7 @@ def attrs(maybe_cls=None, these=None, repr_ns=None,
Django models) or don't want to. Django models) or don't want to.
If *these* is not ``None``, ``attrs`` will *not* search the class body If *these* is not ``None``, ``attrs`` will *not* search the class body
for attributes. for attributes and will *not* remove any attributes from it.
:type these: :class:`dict` of :class:`str` to :func:`attr.ib` :type these: :class:`dict` of :class:`str` to :func:`attr.ib`
@ -658,11 +660,13 @@ def attrs(maybe_cls=None, these=None, repr_ns=None,
.. versionadded:: 16.0.0 *slots* .. versionadded:: 16.0.0 *slots*
.. versionadded:: 16.1.0 *frozen* .. versionadded:: 16.1.0 *frozen*
.. versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``. .. versionadded:: 16.3.0 *str*
.. versionchanged:: .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
17.1.0 *hash* supports ``None`` as value which is also the default .. versionchanged:: 17.1.0
now. *hash* supports ``None`` as value which is also the default now.
.. versionadded:: 17.3.0 *auto_attribs* .. versionadded:: 17.3.0 *auto_attribs*
.. versionchanged:: 18.1.0
If *these* is passed, no attributes are deleted from the class body.
""" """
def wrap(cls): def wrap(cls):
if getattr(cls, "__class__", None) is None: if getattr(cls, "__class__", None) is None:

View File

@ -269,6 +269,17 @@ class TestTransformAttrs(object):
simple_attr("x"), simple_attr("x"),
) == attrs ) == attrs
def test_these_leave_body(self):
"""
If these is passed, no attributes are removed from the body.
"""
@attr.s(init=False, these={"x": attr.ib()})
class C(object):
x = 5
assert 5 == C().x
assert "C(x=5)" == repr(C())
def test_multiple_inheritance(self): def test_multiple_inheritance(self):
""" """
Order of attributes doesn't get mixed up by multiple inheritance. Order of attributes doesn't get mixed up by multiple inheritance.