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?
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:
Most helpful comment
At work I currently use this:
Then it'd look like:
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.