Attrs: create nested attrs classes

Created on 2 Feb 2017  路  10Comments  路  Source: python-attrs/attrs

I'd like to do something like this, which is a MWE of the very common task, "take a YAML config file and suck it in". I'd like to use attrs to break the anti-pattern of having a config file dict as a god object that's hard to refactor, and instead have a nested structure of lightweight classes (which might still be a god object, but at least it's easier to reason about and IDEs can handle code completion on it!)

import attr
from attr.validators import instance_of

@attr.s
class Foo:
    x = attr.ib(validator=instance_of(int)

@attr.s
class Bar:
    foo = attr.ib(convert=Foo, validator=instance_of(Foo))


if __name__ == "__main__""

    bar_from_yaml = {'Foo':{'x':5}}
    bar = Bar(**bar_from_yaml)

As written, that ought to raise TypeError: __init__() missing 1 required positional arguments: 'x'.

I understand that to mean that the kwargs aren't getting unwrapped in a way that the converter understands.

What's the right way to handle this with attrs?

Feature

Most helpful comment

At work I currently use this:

def ensure_cls(cl):     
    """If the attribute is an instance of cls, pass, else try constructing."""     
    def converter(val):         
        if isinstance(val, cl):             
            return val         
        else:             
            return cl(**val)     
    return converter

Then it'd look like:

@attr.s
class Foo:
    x = attr.ib(validator=instance_of(int)

@attr.s
class Bar:
    foo = attr.ib(convert=ensure_cls(Foo))

I plan on solving this a different way with http://cattrs.readthedocs.io/en/latest/, but alas with work and other stuff I haven't had much time to really finish it.

All 10 comments

I think I have a semi-ready example of something similar to what you wanna do: https://github.com/hynek/environ_config

It's based on attrs and metadata. It should be easy to adapt to consume a regular dict as yaml gives you.

Does that help?

That looks like it has a lot of promise. I bet it'd go from your example code if the explicit dict in the .to_config() call is instead the result of yaml deserialization.

The high level case i'm thinking of here is that it would be good for attrs to act almost like an ORM whose data source is a dictionary (or other python primitive data structure like a list or tuple). If i make a pile of @attr.s decorated classes whose attribute names match the keys in a dict, some of whose entries are themselves dicts, i'd love to be able to say "give me this dict of dicts as a tree of classes".

This would (for me, anyway) solve a lot of my frustrations when it comes to handling config files for python scripts. Right now, I inevitably wind up with a pile of dicts with magic strings, and it's not a good smell.

I'll give environ_config a shot. thanks for your work on attrs, it's awesome! 馃憤

At work I currently use this:

def ensure_cls(cl):     
    """If the attribute is an instance of cls, pass, else try constructing."""     
    def converter(val):         
        if isinstance(val, cl):             
            return val         
        else:             
            return cl(**val)     
    return converter

Then it'd look like:

@attr.s
class Foo:
    x = attr.ib(validator=instance_of(int)

@attr.s
class Bar:
    foo = attr.ib(convert=ensure_cls(Foo))

I plan on solving this a different way with http://cattrs.readthedocs.io/en/latest/, but alas with work and other stuff I haven't had much time to really finish it.

@Tinche 5 points to ravenclaw. That looks just delicious. :+1:

I might be open to adding ensure_cls to attrs.

very related to @Tinche :

def ensure_enum(cl):
    """If the attribute is an instance of cls, pass, else try constructing."""
    def converter(val):
        if isinstance(val, cl):
            return val
        else:
            return cl[val]
    return converter

I can make a pull request, no probs.

@gvoysey I actually have that exact one, but on further reflection, I think just `convert=MyEnum' would suffice and probably even be faster. Well, it depends on how you dump your enum in the first place.

@Tinche i was thinking particularly about converting from string values in a dict, As far as I know, that means you'd need to pass the value into the Enum as if it was a dictionary key.

from enum import Enum 
import attr

foo = Enum('foo','bar, baz')

@attr.s
class Container:
    x = attr.ib(convert=foo)

result = Container(**{'x':'bar'})

That will fail with ValueError: 'bar' is not a valid foo.

Thanks @hynek and co. for this great library.

I am posting here because when I googled-upon this thread a couple of weeks ago, it inspired me to throw away a bunch of my code from my library and replace it with attrs as the foundation. By doing so, there is a lot less code and a bunch more capability.

You can check out the library I created below:
https://github.com/genomoncology/related

I tried to provide proper credit to this project. Let me know if there are any concerns.

-ian

I鈥檓 gonna close this because:

  1. it鈥檚 a special case of #12
  2. there have been some suggestions on how to work around it
Was this page helpful?
0 / 5 - 0 ratings

Related issues

RonnyPfannschmidt picture RonnyPfannschmidt  路  7Comments

2mol picture 2mol  路  5Comments

madig picture madig  路  3Comments

Tinche picture Tinche  路  15Comments

sky-code picture sky-code  路  7Comments