Skip to content

configure_tagged_union strategy does not support diamond inheritance #685

@danarmak

Description

@danarmak
import cattrs
from attrs import frozen
from cattrs import strategies

@frozen
class Base: pass
@frozen
class Mid1(Base): pass
@frozen
class Mid2(Base): pass
@frozen
class Sub(Mid1, Mid2): pass

converter = cattrs.Converter()
strategies.include_subclasses(Base, converter, union_strategy=strategies.configure_tagged_union)

Raises the error:

Traceback (most recent call last):
  File "/home/danarmak/sb/aoa/agentune-analyze/agentune/playground.py", line 15, in <module>
    strategies.include_subclasses(Base, converter, union_strategy=strategies.configure_tagged_union)
  File "/home/danarmak/.cache/pypoetry/virtualenvs/agentune-analyze-y3VXCvbo-py3.12/lib/python3.12/site-packages/cattrs/strategies/_subclasses.py", line 84, in include_subclasses
    _include_subclasses_with_union_strategy(
  File "/home/danarmak/.cache/pypoetry/virtualenvs/agentune-analyze-y3VXCvbo-py3.12/lib/python3.12/site-packages/cattrs/strategies/_subclasses.py", line 237, in _include_subclasses_with_union_strategy
    union_strategy(u, converter)
  File "/home/danarmak/.cache/pypoetry/virtualenvs/agentune-analyze-y3VXCvbo-py3.12/lib/python3.12/site-packages/cattrs/strategies/_unions.py", line 54, in configure_tagged_union
    args = union.__args__
           ^^^^^^^^^^^^^^
AttributeError: type object 'Sub' has no attribute '__args__'

Analysis: in _include_subclasses_with_union_strategy the (autogenerated) union_classes contains Sub2 twice (it has the value union_classes = (<class '__main__.Base'>, <class '__main__.Mid1'>, <class '__main__.Sub'>, <class '__main__.Mid2'>, <class '__main__.Sub'>)). When the loop for cl in union_classes gets to cl=Sub2 it creates subclasses=(Sub2, Sub2), passes the test len(subclasses) > 1, but then the Union automatically reduces this to a non-union Sub2 which has no __args__.

I think _make_subclasses_tree should return a distinct list that doesn't include any class twice.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions