8000 Fix `mro` of generic subclass by kc0506 · Pull Request #10100 · pydantic/pydantic · GitHub
[go: up one dir, main page]

Skip to content

Fix mro of generic subclass #10100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 10, 2024
Merged

Conversation

kc0506
Copy link
Contributor
@kc0506 kc0506 commented Aug 11, 2024

Change Summary

Override ModelMetaclass.mro such that indexed generic type can be inserted into __mro__ of its subclasses.

For example:

class A(BaseModel, Generic[T]): ...
class B(A[T]): ...
class C(B[bool]): ...

# before
assert C.__mro__ == (C, B[bool], B, A, BaseModel, Generic, object)
# after
assert C.__mro__ == (C, B[bool], B, A[bool], A, BaseModel, Generic, object)

This allow C() to be assigned to A[bool].

I have checked all testcases in test_generic.py. This change doesn't break existing tests. Also, I collected all the tests where __mro__ is affected by this change, and add them into new tests.

Related issue number

fix #10039

Checklist

  • The pull request title is a good summary of the changes - it will be used in the changelog
  • Unit tests for the changes exist
  • Tests pass on CI
  • Documentation reflects the changes where applicable
  • My PR is ready to review, please add a comment including the phrase "please review" to assign reviewers

@github-actions github-actions bot added the relnotes-fix Used for bugfixes. label Aug 11, 2024
@kc0506 kc0506 changed the title Fix generic mro Fix mro of generic subclass Aug 11, 2024
Copy link
codspeed-hq bot commented Aug 11, 2024

CodSpeed Performance Report

Merging #10100 will not alter performance

Comparing kc0506:fix-generic-mro (dc7cd94) with main (287c266)

Summary

✅ 49 untouched benchmarks

Copy link
Contributor
github-actions bot commented Aug 11, 2024

Coverage report

This PR does not seem to contain any modification to coverable code.

@sydney-runkle
Copy link
Contributor

Hmm, I think the way to solve this is actually going to be revalidating instances of classes that have a shared generic parent. Happy to think about this proposal more tomorrow.

@dmontagu, I haven't looked in depth, but this is up your alley, and might be worth a glance.

@sydney-runkle sydney-runkle requested a review from dmontagu August 15, 2024 00:46
Copy link
Contributor
@dmontagu dmontagu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think this may make sense in principle, but if we merge it I think we should try to do a better job of substituting the parameters in intermediate classes even when they aren’t the same.

E.g., if I have class A(BaseModel, Generic[T]):… and class B(A[T], Generic[S]): … we should have A[int] in the mro of B[int, str]. I don’t think it should be too hard to achieve this (or at least do a better job than exact equality checking), but if it is unreasonably hard to follow through the substitutions we don’t need to let perfect get in the way of better.

That said, I am not 100% sure if this is better than revalidating into the other generic type. But I think this will result in better behavior in the cases that are affected, so I’m inclined to merge this approach.

@kc0506
Copy link
Contributor Author
kc0506 commented Aug 15, 2024

@dmontagu Thanks for the review!
I made some changes, so now this can work:

class A(BaseModel, Generic[T1]): ...
class B1(A[T1], Generic[T1, T2]): ...

assert A[int] in B1[T1, str][int].__mro__
assert A[int] in B1[int, str].__mro__

Please let me know if there are still some cases I didn't think through.

@sydney-runkle sydney-runkle added relnotes-change Used for changes to existing functionality which don't have a better categorization. and removed relnotes-fix Used for bugfixes. labels Aug 15, 2024
# class B(A[int], Generic[T]): ...
# class C(B[str], Generic[T]): ...
#
key = '__pydantic_inserted_mro_origins__'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This __pydantic_inserted_mro_origins__ should be added as an annotated attribute on the BaseModel class if we are going to use it like this. I think it would also be reasonable to add it as a field in the __pydantic_generic_metadata__ if that makes sense, but if we are using a new attribute in BaseModel it should be added near this area:

__pydantic_generic_metadata__: ClassVar[_generics.PydanticGenericMetadata]

Copy link
Contributor
@dmontagu dmontagu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://github.com/pydantic/pydantic/pull/10100/files#r1721798498; once that is addressed I'm good with this PR.

@sydney-runkle sydney-runkle mentioned this pull request Aug 20, 2024
19 tasks
@sydney-runkle sydney-runkle added the awaiting author revision awaiting changes from the PR author label Aug 20, 2024
@kc0506
Copy link
Contributor Author
kc0506 commented Aug 20, 2024

@dmontagu I found that my current solution cannot handle this (very unlikely) case:

class A(BaseModel, Generic[T1]): ...
class B(A[T2], Generic[T2]): ...
class C(B[T3], Generic[T3]): ...
class D(C[T1], Generic[T1]): ...
class E(D[T2], Generic[T2]):...

because the MRO of D[T2] will be like

D[~T2], D,
C[~T2], C[~T1], C,
B[~T2], B[~T1], B[~T3], B,
A[~T2], A[~T1], A[~T3], A[~T2], A, ...

and the repeated A[T2] is invalid.

Do you think this is worth fixing, or we could mark it as an expected fail and address it in other PR?

@dmontagu
Copy link
Contributor
dmontagu commented Aug 20, 2024

I'd say it's at least worth adding an xfailing test, I don't think it's the craziest case but I also don't think getting that right needs to prevent releasing a partial fix that fixes cases that are much more likely to be encountered.

@sydney-runkle
Copy link
Contributor

@kc0506, if you're able to finish this up in the next couple of days, we can get this into our v2.9 release! Thanks for your awesome work here.

@kc0506
Copy link
Contributor Author
kc0506 commented Aug 22, 2024

@kc0506, if you're able to finish this up in the next couple of days, we can get this into our v2.9 release! Thanks for your awesome work here.

Sure, I will try to wrap it up by this weekend.

@kc0506
Copy link
Contributor Author
kc0506 commented Aug 26, 2024

I've updated the logic insdie mro. The new approach does not need an extra class variable and can solve the failed case mentioned above, as well as remove redundant bases (e.g. A[int], A[T2], A[T1], A, ... will now become simply A[int], A, ...).

I think this is ready to be merged. Looking forward to any feedbacks!

@kc0506 kc0506 requested a review from dmontagu August 26, 2024 15:37
Copy link
Contributor
@dmontagu dmontagu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't be surprised if this still ends up needing to be tweaked in some way in the future, but this is closer to what I had imagined the relevant code should look like, great improvement.

LGTM

@sydney-runkle
Copy link
Contributor
sydney-runkle commented Aug 27, 2024

@kc0506,

Awesome work here. I'm going to do another review here as well this afternoon, then we'll merge this after our v2.9 release (we've already released the beta, so can't introduce any major changes during this beta trial period).

Copy link
Contributor
@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work here. Thanks a bunch for your contribution!!

I'm a bit worried about a few of the unchecked dict accesses, so let's make sure no unexpected behavior is witnessed here leading up to our v2.10 release.

@sydney-runkle sydney-runkle enabled auto-merge (squash) September 10, 2024 17:03
@sydney-runkle sydney-runkle merged commit cb57c13 into pydantic:main Sep 10, 2024
57 checks passed
@kc0506 kc0506 deleted the fix-generic-mro branch October 14, 2024 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready for review relnotes-change Used for changes to existing functionality which don't have a better categorization.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Nested generic inheritance
3 participants
0