Skip to content

Allow a base class to restrict the type of subclass's inner class #14767

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

Open
intgr opened this issue Feb 23, 2023 · 3 comments
Open

Allow a base class to restrict the type of subclass's inner class #14767

intgr opened this issue Feb 23, 2023 · 3 comments
Labels

Comments

@intgr
Copy link
Contributor

intgr commented Feb 23, 2023

Feature

For regular classvars, attributes and methods, mypy already checks that the types in a subclass match those declared in the superclass. However, this checking does not extend to inner classes in the subclass. It seems inner classes are ignored by mypy entirely.

For example, a framework could define an abstract class Model whose inner class Meta must implement a certain protocol:

# Defined in the framework
class InnerMetaProtocol(Protocol):
    example_attr: str

class Model:
    Meta: type[InnerMetaProtocol]


# An application developer using framework
class Widget(Model):
    class Meta:
        example_attr = 123  # This should be an error: int is not str

Class Widget implements Model, whose attribute Meta should conform to type[InnerMetaProtocol], meaning it requires attribute example_attr: str. But currently mypy ignores this constraint: Playground.

It's probably useful to support this with not just protocols, but also concrete classes (e.g. Meta: type[ConcreteClass]).

Pitch

This pattern of inner Meta classes is used quite extensively in the Django ecosystem, including for Django models, forms, Django REST Framework serializers, djangorestframework-dataclasses, etc. I'm sure there are other use cases as well, but I don't know off the top of my head.

@intgr intgr added the feature label Feb 23, 2023
@intgr
Copy link
Contributor Author

intgr commented Feb 23, 2023

There is a workaround, if the Meta class is defined separately (not as inner class), and then assigned to the attribute. But this feels very unnatural and clunky for people coming from the Django ecosystem. (Playground)

# An application developer using framework
class WidgetMeta:
    example_attr = 123

class Widget(Model):
    Meta = WidgetMeta  # E: Incompatible types in assignment (expression has type "Type[WidgetMeta]", base class "Model" defined the type as "Type[InnerMetaProtocol]")

@intgr
Copy link
Contributor Author

intgr commented Feb 23, 2023

Although this is only a piece of the puzzle: to take full advantage of this with Django, we would also need optional Protocol members, or something like #5504 (total=False for Protocols).

@jorenham
Copy link
Contributor

On mypy 1.9 with in strict mode, this is still an issue, even when all attributes are marked as ClassVar:
https://mypy-play.net/?mypy=1.9.0&python=3.12&gist=affc5c097b0fa5c25195f41516300626&flags=strict

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants