Closed ieaves closed 7 months ago
It looks like the offending change occurred here. In previous versions of multimethod bases
would be left as an empty tuple for Union's. Now, when every subtype of the Union inherits from the same base class multimethod identifies their shared base class and attempts to instantiate a new type from the same base.
Unfortunately, as far as I can tell, this will always fail when the shared base class inherits from a parent with a custom Meta, e.g.
class Meta(type):
def __new__(cls, name, bases, dct):
x = super().__new__(cls, name, bases, dct)
return x
class Thing(metaclass=Meta):
pass
class Thing2(Thing):
pass
class Thing3(Thing):
pass
Things = Union[Thing2, Thing3]
I'm not confident enough with the rest of the code base to know whether this would create undesirable side effects but one solution would be to remove bases which contain custom metaclasses. That could look something like
if origin is Union:
def has_custom_metaclass(cls):
for base in cls.__mro__:
if base.__class__ is not type:
return True
return False
counts = collections.Counter(cls for arg in args for cls in get_mro(arg))
bases = tuple(cls for cls in counts if counts[cls] == len(args) and not has_custom_metaclass(cls))[:1]
Thanks for taking a look at this @coady. I can confirm the fix in that commit will resolve the issue in all cases where we use multimethod. Thanks for your work on this amazing package.
In v1.11.1. Thanks for the report.
I'm still investigating where the regression was introduced but I wanted to bring it to your attention first.
We heavily utilize multimethod in conjunction with pydantic to dispatch on various data objects. Take the following
Prior to
1.11.0
this script would run without error. However, since 1.11.0 this now errors withThe issue specifically arises when the
Union
contains two objects inheriting from BaseModel. It does not trigger for something likeUnion[Thing, None]
orUnion[Thing, Thing3]
where Thing3's parent class is just object.