Do not untructure the Singleton attrs.NOTHING at all (identity) instead of to 1 (int) (attrs >= 22.2 problem)#667
Conversation
1efa2f7 to
3867a46
Compare
The test could randomly fail as on upickling a python-set creates a new instance, and then randomly the order could be different then before and hence the test fails then. As this is not important for the test we simplify it to a one-element set.
|
I did a small update to the #666 unit tests and just use one element in the set of The test could randomly fail as on unpickling a python-set creates a new |
|
I don't really think this belongs in cattrs, especially since it's very easily overridden by users and I've never heard anyone else run into this issue. Which states cause attrs to assign NOTHING to instance variables? I'm curious since I maintain attrs as well.
Yeah, once we have the singleton PEP we can handle this more gracefully. In the meantime, you can probably have a code path that checks if |
Well there might be a better representation for By the way this MR restores the behaviour (from an abstract user perspective) of pre-attrs-22.2. To underline what I mean, I expect that a comparison of an attributes-field with its default value only returns @attrs.define()
class MyA:
a1: int
a2: bool
a3: int = 42
conv = cattrs.Converter()
print('NOTHING is `1`:', conv.unstructure(attrs.NOTHING))
mya1 = MyA(a1=1, a2=True)
mya1_dict = conv.unstructure(mya1)
print('mya1_dict', mya1_dict)
attribs = attrs.fields_dict(MyA)
print('')
print("a3 structured, should be True:", mya1.a3 == attribs['a3'].default)
print("a3 unstructured, should be True:", mya1_dict['a3'] == conv.unstructure(attribs['a3'].default))
print('')
print("a1 structured, should be False as there is no default:", mya1.a1 == attribs['a1'].default)
print(
"a1 unstructured, should be False as there is no default:",
mya1_dict['a1'] == conv.unstructure(attribs['a1'].default), # Default is NOTHING here
)
print('')
print("a2 structured, should be False as there is no default:", mya1.a1 == attribs['a2'].default)
print(
"a2 unstructured, should be False as there is no default:",
mya1_dict['a2'] == conv.unstructure(attribs['a2'].default), # Default is NOTHING here
)
Sorry what is overwritten?
Then I am the first! 😄
I looked through my Unit-Tests and I fear I did not found an example which documented this behaviour. I remember that I stumbled over this many years ago, and I kept in mind that I should be prepared for this case. If you are confused, then it was probably not an intended behaviour and was some wired edge case and might be gone already over the years ...
Sounds great to have a solution for that!
Don’t worry did already before I opened this MR as it is just an if-clause — still think this would improve the behaviour of cattrs as stated above. |
Ah, but this is simply not how it's designed or implemented at the moment. The logical type of print(
"a1 unstructured, should be False as there is no default:",
(
(mya1_dict["a1"] == conv.unstructure(attribs["a1"].default))
if attribs["a1"].default is not attrs.NOTHING
else False
),
)and even that logic can be improved a little bit: print(
"a1 unstructured, should be False as there is no default:",
(
(
mya1_dict["a1"]
== conv.unstructure(
attribs["a1"].default, unstructure_as=attribs["a1"].type
)
)
if attribs["a1"].default is not attrs.NOTHING
else False
),
)In any case, this seems too niche to include in cattrs. I need to weigh contributions against the maintenance burden to maintainers and the complexity burden to users, and this doesn't meet the bar, unfortunately. Thanks in any case! |
Since attrs >= 22.2 the
attrs.NOTHINGis not an object any more but implemented by anenum.Enum, see here.That causes the follow up-problem, since cattrs supports unstructuring
enum.Enum, it will unstructure it to1(int).but this is unintended as
attrs.NOTHINGshould not represent anintbut a special state in an attrs Attribute.That leads to two problems.
First,
attrsdo set the value of an class attribute toattrs.NOTHINGin some faulty states. When you now unstructure such a field with cattrs, it suddenly becomes an1.Second, when you create an attrs Attribute (with
attrs.fieldor type annotation) with no default the resulting object is anattrs.Attributewhichattrs.Attribute.defaultvalue is set toattrs.NOTHING. So in that context,attrs.NOTHINGmeans the attribute does not have any default value.However, I have now situations where I compare a value of an attribute with its default (i.e. comparing it with
attrs.Attribute.default). And if the attribute is an int-field and by chance was set to1it is now suddenly equal to its default value although it is defined to have no default at all — which leads to some errors in my case.Due to
attrs.NOTHING’s special meaning of being a faulty state or that no default value is set, I think it is better to not unstructure it (identity as I do in this PR) instead of unstructure it to the (unexpected) int-value of1.