Skip to content
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

Inconsistency when overriding dataclass_transform in subclasses #6489

Closed
Viicos opened this issue Nov 18, 2023 · 5 comments
Closed

Inconsistency when overriding dataclass_transform in subclasses #6489

Viicos opened this issue Nov 18, 2023 · 5 comments
Labels
enhancement request New feature or request

Comments

@Viicos
Copy link

Viicos commented Nov 18, 2023

The two following snippets produce different results:

from typing import dataclass_transform


@dataclass_transform(kw_only_default=True)
class MyType(type):
    pass

class BaseModel(metaclass=MyType):
    pass

@dataclass_transform(kw_only_default=False)
class RootModel(BaseModel):
    pass

class IntRoot(RootModel):
    root: int

IntRoot(1)  # Expected 0 positional arguments

Code sample in pyright playground


from typing import dataclass_transform


@dataclass_transform(kw_only_default=True)
class BaseModel:
    pass

@dataclass_transform(kw_only_default=False)
class RootModel(BaseModel):
    pass

class IntRoot(RootModel):
    root: int

IntRoot(1)  # No problems have been detected.

Code sample in pyright playground

Unfortunately, the PEP does not describe the expected behavior when overriding dataclass_transform in subclasses. But I think it would be great for the first snippet to behave the same way as the second one.

Tested with mypy, it is behaving as expected (first snippet doesn't produce an error).

@Viicos Viicos added the bug Something isn't working label Nov 18, 2023
@Viicos Viicos changed the title Inconsistency when subclassing overriding dataclass_transform in subclasses Inconsistency when overriding dataclass_transform in subclasses Nov 18, 2023
@erictraut
Copy link
Collaborator

What you're doing in the first sample is highly suspect because it combines a metaclass-based and class-based dataclass implementation. Only one of these techniques would ever be used for a given class. If both were actually used in practice, it's not clear which implementation (the one in the metaclass or the one in the base class) would apply.

I'm curious why you're doing this. I'm guessing you're trying to work around some other limitation.

Pyright is the reference implementation for PEP 681, and it prefers the metaclass over the base class when both techniques are combined. I think that's a defensible behavior.

In general, when a PEP doesn't describe a behavior, type checkers should fall back on the reference implementation behavior unless there's good reason for them not to, so mypy should probably be changed to match pyright's behavior in this case. Then again, I think this is enough of an edge case that it probably doesn't matter. I recommend against relying on this behavior.

@erictraut erictraut added the as designed Not a bug, working as intended label Nov 18, 2023
@erictraut erictraut closed this as not planned Won't fix, can't repro, duplicate, stale Nov 18, 2023
@Viicos
Copy link
Author

Viicos commented Nov 18, 2023

If both were actually used in practice, it's not clear which implementation (the one in the metaclass or the one in the base class) would apply.

That's a good point, makes sense.

The use case is related to this PR: pydantic/pydantic#8163, where RootModel inherits BaseModel (BaseModel has a dataclass_transform decorated metaclass with kw_only_default=True).

I may have to take a look at the current __init__ method of this RootModel, or define a metaclass for RootModel inheriting the one from BaseModel, and override dataclass_transform here

@erictraut erictraut reopened this Nov 18, 2023
@erictraut
Copy link
Collaborator

Ah, I see. That's kind of hacky, but I understand the reasoning behind it. I can't think of a better solution than the one you've come up with.

Since it's highly unlikely that anyone else is relying on the current behavior in either mypy or pyright, I'm willing to make the proposed change in pyright.

@erictraut erictraut added enhancement request New feature or request and removed bug Something isn't working as designed Not a bug, working as intended labels Nov 18, 2023
@Viicos
Copy link
Author

Viicos commented Nov 18, 2023

Thanks! but pydantic recently introduced changes in the __init__ method that I missed, that could allow what I'm trying to achieve. Can I take a deeper look at it and come back here? It might not be necessary to "fix" this and keep the current behavior (that is, as you said, probably the expected one).

@erictraut
Copy link
Collaborator

OK, sounds good. I'm going to close this for now, but I'm willing to re-open it in the future based on additional input.

@erictraut erictraut closed this as not planned Won't fix, can't repro, duplicate, stale Nov 18, 2023
erictraut added a commit that referenced this issue Nov 18, 2023
…e metaclass and base class of a class. Previously, the metaclass was preferred. now the base class is preferred. This allows the metaclass defaults to be overridden by a child (base) class. This addresses #6489.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement request New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants