attrs
attrs copied to clipboard
asdict filter exclude list matches on name instead of attribute
Hi, first of all, love the project and use it nearly every day for my job.
I have come across some behaviour that is unexpected. Please consider this minimal worked example:
import attr
from typing import List
@attr.s(auto_attribs=True)
class Cop:
my_field: str
repeated_field_name: str
@attr.s(auto_attribs=True)
class Robber:
my_other_field: str
repeated_field_name: str
@attr.s(auto_attribs=True)
class Payload:
cops: List[Cop]
robbers: List[Robber]
if __name__ == "__main__":
cop = Cop("abc", "shared1")
robber = Robber("def", "shared2")
payload = Payload([cop], [robber])
print(attr.asdict(payload, recurse=True))
"""
{
'cops': [{'my_field': 'abc', 'repeated_field_name': 'shared1'}],
'robbers': [{'my_other_field': 'def', 'repeated_field_name': 'shared2'}]
}
"""
print(
attr.asdict(
payload,
recurse=True,
filter=attr.filters.exclude(attr.fields(Cop).repeated_field_name)
)
)
"""
{
'cops': [{'my_field': 'abc'}],
'robbers': [{'my_other_field': 'def'}]
}
"""
When the asdict filter argument is passed a variadic collection of attributes (available from attr.fields), and you have a range of attrs models that are recursively asdict-ified that may share a field name (in this example repeated_field_name) then the returned dictionary excludes this attribute for ALL classes, not just the one passed to fields.
This is understandable to a degree given assert attr.fields(Cop).repeated_field_name == attr.fields(Robber).repeated_field_name holds, but still a little unexpected given the method explicitly does not accept field names as strings.
As it happens, this behaviour is what I wanted 😄 though again I found it unexpected and wanted to confirm it is not a bug before I move forward with the implementation.
If this is a bug, a suggestion would be to permit string field names
OK this is a fun one. Because it's not comparing strings, but Attribute instances. However, Attribute instances are independent from the classes they're defined on – that's clearly a bug.
Try running attr.fields(Robber).repeated_field_name == attr.fields(Cop).repeated_field_name then add e.g. add attr.field(metadata={"foo":42}) to one of the repeated_field_name and run it again.
(to be clearer: technically, this is not a bug. the code does exactly what it says on the tin. however it's absolutely not what people would expect it to do :D)
Nice! Thanks @hynek
My use case is about dropping a metadata type field from a collection of classes. So e.g. repeated_field_name might be on 10 classes, but I want to optionally drop this from all of my serialised data. It would be nice to somehow support this, though what are your feelings on the use case?
Given how trivial the code currently is, I think it's best if you just implement it yourself for now: https://github.com/python-attrs/attrs/blob/main/src/attr/filters.py