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

Recursive attr.evolve #634

Open
sveinse opened this issue Mar 25, 2020 · 9 comments · Fixed by #759
Open

Recursive attr.evolve #634

sveinse opened this issue Mar 25, 2020 · 9 comments · Fixed by #759
Assignees
Labels

Comments

@sveinse
Copy link

sveinse commented Mar 25, 2020

In the same manner as attr.asdict(..., recursive=True) exists, I have a use case for a similar option to attr.evolve(). I want all attr instances evolved, but with no need to deepcopy any non-attr attributes. I ended up implementing the code below. Could this be a consideration for addition into python-attrs?

def evolve_recursive(inst, **changes):
    """ Recursive attr.evolve() method, where any attr-based attributes
        will be evolved too.
    """
    cls = inst.__class__
    attrs = attr.fields(cls)
    for a in attrs:
        if not a.init:
            continue
        attr_name = a.name  # To deal with private attributes.
        init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
        if init_name not in changes:
            value = getattr(inst, attr_name)

            # New code
            if attr.has(value.__class__):
                value = evolve_recursive(value)

            changes[init_name] = value
    return cls(**changes)
@hynek
Copy link
Member

hynek commented Oct 19, 2020

Sorry for the late answer. Would you mind showing/elaborating some use cases for this?

@sscherfke
Copy link
Contributor

I’ve had a similar issue in typed settings when I want to updated nested settings and fixed it by serializing instances to dicts, calling a _set_path function and later converting the dicts back to attrs instances (which only works when using (auto) converters, though).

@hynek
Copy link
Member

hynek commented Oct 30, 2020

We have a volunteer! ✨

@sscherfke
Copy link
Contributor

Wat? 👀

@sscherfke
Copy link
Contributor

sscherfke commented Dec 13, 2020

attr.evolve() can be adjusted relatively easy to work recursively:

diff --git a/src/attr/_funcs.py b/src/attr/_funcs.py
index 56e3fcf..1cac98e 100644
--- a/src/attr/_funcs.py
+++ b/src/attr/_funcs.py
@@ -333,8 +333,13 @@ 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:
-            changes[init_name] = getattr(inst, attr_name)
+            # Add original value to changes
+            changes[init_name] = value
+        elif has(value):
+            # Evolve nested attrs classes
+            changes[init_name] = evolve(value, **changes[init_name])

     return cls(**changes)

I’m not sure whether this is a backwards incompatible change or rather a bug fix that prevents nested classes from being replaced by a dict. 🤷‍♂️

@sscherfke
Copy link
Contributor

@hynek What are your requirements regarding the public API?

Do you prefer a new function or would you rather update the behavior of the existing one as in the diff above?

@hynek
Copy link
Member

hynek commented Dec 21, 2020

I think if you can do it backward compatible, there's no need for a new function?

@sscherfke
Copy link
Contributor

I guess, it’ll be mostly backward compatible. *mostly, because handling of nested dicts will be different if they map to attributes with attrs classes. ;-)

But the above changes did not break any of the existing tests.

I’ll do a PR once I figure out how to proberly use hypothesis and nested classes for the tests.

sscherfke added a commit to sscherfke/attrs that referenced this issue Feb 14, 2021
sscherfke added a commit to sscherfke/attrs that referenced this issue Feb 14, 2021
sscherfke added a commit to sscherfke/attrs that referenced this issue Feb 14, 2021
hynek added a commit that referenced this issue Feb 19, 2021
* Recursively evolve nested attrs classes

Fixes: #634

* Apply suggestions from code review

Co-authored-by: Hynek Schlawack <[email protected]>

* Update tests for recursive evolve()

Co-authored-by: Hynek Schlawack <[email protected]>
@hynek hynek reopened this May 6, 2021
@hynek
Copy link
Member

hynek commented May 6, 2021

Unfortunately we had to revert the changes due to #804. I think we'll have to write a new function to make stuff unequivocal.

Sorry everyone! :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
4 participants