0

I decorated a class using attrs.define and added all important attributes/fields.

import attrs

@attrs.define
class MyAttrsClass:
    important1 = attrs.field(type = int)
    important2 = attrs.field(type = str)
    [...]

Now I would like to initialize instances of that class from a dict. So what I do is calling the contructor with a dict-unpacking.

test_dict = {'important1': 100, 'important2': 'foo'}
ci = MyAttrsClass(**test_dict)

This works in that simple example case, because I specify the same fields in the dict as well as in the MyAttrsClass class.

However, in the real scenario the dict is a deserialized response from a web-api (using json.loads) and it returns many key-value-pairs I'm not interested in, despite the important ones. Then, I get the following error TypeError: MyAttrsClass.__init__() got an unexpected keyword argument 'XYZ'.

Is is possible to initialize an instance of MyAttrsClass, though? I would like to discard the superfluous elements. Do I need to manually remove entries from the input dict or is there a better way using attrs.

  • I told that I wanted to discard the superfluous elements of the input dict. However, if it is easier to keep them, this would not be a problem. – Wör Du Schnaffzig Jul 13 '23 at 13:31
  • 1
    It's *syntactically* convenient to want to do that, but it assumes that it is the job of a class to somehow pick out the keyword arguments it *wants* from an arbitrary and overly broad set of arguments. Be explicit. If you want something similar, define a class method like `def from_dict(cls, d): return cls(d['important1'], d['important2'])`. – chepner Jul 13 '23 at 13:33
  • 1
    (Posting as a comment because I don't know if `attrs` does, in fact, provide a way to do this. But it's not something I *expect* it to provide.) – chepner Jul 13 '23 at 13:34
  • You can filter them out with something like `[a.name for a in MyAttrsClass.__attrs_attrs__]`. – user2390182 Jul 13 '23 at 13:50
  • I like the class method approach but in order to avoid repetition I would like to pick out the important entries more generically. Maybe it's possible to add some reflection on `MyAttrsClass`? – Wör Du Schnaffzig Jul 13 '23 at 14:01
  • Just found this thread: https://stackoverflow.com/questions/18554012/intersecting-two-dictionaries. Maybe this could help in extracting only the important items from the input. – Wör Du Schnaffzig Jul 13 '23 at 14:12

2 Answers2

0

You can use the documented attrs.fields function:

big_dic = {"important1": 3, "important2": "abc", "spuriouskey": "dontcare"}
fields = {a.name for a in attrs.fields(MyAttrsClass)}
init_kwargs = {k: v for k, v in big_dic.items() if k in fields}

MyAttrsClass(**init_kwargs)
# MyAttrsClass(important1=3, important2='abc')
user2390182
  • 72,016
  • 6
  • 67
  • 89
0

I'm an attrs maintainer and the author of cattrs. You should probably just use cattrs since it was designed for cases like this.

from cattrs import structure

structure(big_dict, MyAttrsClass)

Otherwise, you'll have to do a little preprocessing yourself, as others have mentioned.

Tin Tvrtković
  • 531
  • 4
  • 4