Skip to content

Commit

Permalink
Revert recursive evolve (#806)
Browse files Browse the repository at this point in the history
* Revert "Recursively evolve nested attrs classes (#759)"

* Add regression test

* lol legacy python

* Add newsfragment

* Add a test to prevent inst -> dict replacement breaking
  • Loading branch information
hynek authored May 6, 2021
1 parent 24a2c1e commit f10d050
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 53 deletions.
1 change: 1 addition & 0 deletions changelog.d/806.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
We had to revert the recursive feature for ``attr.evolve()`` because it broke some use-cases -- sorry!
21 changes: 0 additions & 21 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -618,27 +618,6 @@ In Clojure that function is called `assoc <https://clojuredocs.org/clojure.core/
>>> i1 == i2
False

This functions also works for nested ``attrs`` classes.
Pass a (possibly nested) dictionary with changes for an attribute:

.. doctest::

>>> @attr.s(frozen=True)
... class Child(object):
... x = attr.ib()
... y = attr.ib()
>>> @attr.s(frozen=True)
... class Parent(object):
... child = attr.ib()
>>> i1 = Parent(Child(1, 2))
>>> i1
Parent(child=Child(x=1, y=2))
>>> i2 = attr.evolve(i1, child={"y": 3})
>>> i2
Parent(child=Child(x=1, y=3))
>>> i1 == i2, i1.child == i2.child
(False, False)


Other Goodies
-------------
Expand Down
10 changes: 2 additions & 8 deletions src/attr/_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,7 @@ def evolve(inst, **changes):
Create a new instance, based on *inst* with *changes* applied.
:param inst: Instance of a class with ``attrs`` attributes.
:param changes: Keyword changes in the new copy. Nested ``attrs`` classes
can be updated by passing (nested) dicts of values.
:param changes: Keyword changes in the new copy.
:return: A copy of inst with *changes* incorporated.
Expand All @@ -338,13 +337,8 @@ def evolve(inst, **changes):
continue
attr_name = a.name # To deal with private attributes.
init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
value = getattr(inst, attr_name)
if init_name not in changes:
# Add original value to changes
changes[init_name] = value
elif has(value):
# Evolve nested attrs classes
changes[init_name] = evolve(value, **changes[init_name])
changes[init_name] = getattr(inst, attr_name)

return cls(**changes)

Expand Down
55 changes: 31 additions & 24 deletions tests/test_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,43 +598,50 @@ class C(object):

assert evolve(C(1), a=2).a == 2

def test_recursive(self):
def test_regression_attrs_classes(self):
"""
evolve() recursively evolves nested attrs classes when a dict is
passed for an attribute.
evolve() can evolve fields that are instances of attrs classes.
Regression test for #804
"""

@attr.s
class N2(object):
e = attr.ib(type=int)
class Cls1(object):
param1 = attr.ib()

@attr.s
class N1(object):
c = attr.ib(type=N2)
d = attr.ib(type=int)
class Cls2(object):
param2 = attr.ib()

@attr.s
class C(object):
a = attr.ib(type=N1)
b = attr.ib(type=int)
obj2a = Cls2(param2="a")
obj2b = Cls2(param2="b")

c1 = C(N1(N2(1), 2), 3)
c2 = evolve(c1, a={"c": {"e": 23}}, b=42)
obj1a = Cls1(param1=obj2a)

assert c2 == C(N1(N2(23), 2), 42)
assert Cls1(param1=Cls2(param2="b")) == attr.evolve(
obj1a, param1=obj2b
)

def test_recursive_dict_val(self):
def test_dicts(self):
"""
evolve() only attempts recursion when the current value is an ``attrs``
class. Dictionaries as values can be replaced like any other value.
evolve() can replace an attrs class instance with a dict.
See #806
"""

@attr.s
class C(object):
a = attr.ib(type=dict)
b = attr.ib(type=int)
class Cls1(object):
param1 = attr.ib()

@attr.s
class Cls2(object):
param2 = attr.ib()

c1 = C({"spam": 1}, 2)
c2 = evolve(c1, a={"eggs": 2}, b=42)
obj2a = Cls2(param2="a")
obj2b = {"foo": 42, "param2": 42}

assert c2 == C({"eggs": 2}, 42)
obj1a = Cls1(param1=obj2a)

assert Cls1({"foo": 42, "param2": 42}) == attr.evolve(
obj1a, param1=obj2b
)

0 comments on commit f10d050

Please sign in to comment.