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

Partial classes cannot be used in type annotations #2

Open
jdinunzio opened this issue Sep 24, 2022 · 7 comments
Open

Partial classes cannot be used in type annotations #2

jdinunzio opened this issue Sep 24, 2022 · 7 comments
Labels
wontfix This will not be worked on

Comments

@jdinunzio
Copy link

Issue

Using python 3.10.7, pydantic 4.3.2, pydantic-partial 0.3.2 and 0.3.3, mypy 0.971

from pydantic import BaseModel
from pydantic_partial import PartialModelMixin

class Foo(PartialModelMixin, BaseModel):
    id: int

PartialFoo = Foo.as_partial()

reveal_type(Foo())
reveal_type(PartialFoo())

def something(x: PartialFoo):
    return x.dict()

running mypy will return:

tmp/foo.py:10: note: Revealed type is "foo.Foo"
tmp/foo.py:11: note: Revealed type is "foo.Foo"
tmp/foo.py:13: error: Variable "foo.PartialFoo" is not valid as a type
tmp/foo.py:13: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
tmp/foo.py:14: error: PartialFoo? has no attribute "dict"

(revealed type for PartialFoo() is "Any" in 0.3.2 and "foo.Foo" in 0.3.3).

Question / Request

How to use partials as type annotations? If there's a way to do it, could it be documented?

@ddanier
Copy link
Member

ddanier commented Sep 24, 2022

I initially thought that adding a real class will fix this, but mypy cannot resolve the type then:

from pydantic import BaseModel
from pydantic_partial import PartialModelMixin

class Foo(PartialModelMixin, BaseModel):
    id: int

class PartialFoo(Foo.as_partial()):
    pass

reveal_type(Foo())
reveal_type(PartialFoo())

def something(x: PartialFoo):
    return x.dict()

...this will still give you test.py:7: error: Unsupported dynamic base class "Foo.as_partial" as an error.

The main issue is, that we are constructing a type during runtime. This dynamic type is not supported by mypy at all.
(see python/mypy#2477 for some background)

I tried to mark PartialModelMixin.as_partial() and create_partial_model(...) to return the same type, so for the type checker the class PartialFoo is basically the same as Foo. This is due to the fact that the dynamically changed type cannot be defined in the Python typing system. Sadly this seems to not be enough for mypy.

You can work around this issue by tricking mypy into not seeing the conversion to partial at all. Note however that this still means all partial model instances will just be seen as instanced of Foo.

See the following example code:

from typing import TYPE_CHECKING

from pydantic import BaseModel
from pydantic_partial import PartialModelMixin

class Foo(PartialModelMixin, BaseModel):
    id: int

if TYPE_CHECKING:
    PartialFoo = Foo
else:
    PartialFoo = Foo.as_partial()

reveal_type(Foo())
reveal_type(PartialFoo())

def something(x: PartialFoo):
    return x.dict()

Which will produce the following mypy output:

test.py:14: note: Revealed type is "test.Foo"
test.py:15: note: Revealed type is "test.Foo"
Success: no issues found in 1 source file

@ddanier
Copy link
Member

ddanier commented Sep 24, 2022

Side note: If anyone knows a better way to solve this or how to define the types in this library, please feel free to send me some suggestions or even a pull request. ;-)

@ddanier
Copy link
Member

ddanier commented Sep 27, 2022

Currently my feeling is I have to write a mypy plugin... 🤷‍♂️🤔

@jdinunzio
Copy link
Author

The ideal solution in my mind would look something like

PartialFoo = Partial[Foo]

if only typing.Generic would allow being sub-classed into a meta-class, but sadly that seems not to be an option.

@ddanier
Copy link
Member

ddanier commented Sep 29, 2022

@jdinunzio Yeah, that would be the best syntax. Sadly using __class_getitem__ will confuse mypy and at least PyCharm. But I will see what I can do when thinking about a plugin. Have to read into this first though....so this will probably take some time.

If anyone else is interested in solving this a PR would be very much appreciated. 👍
(but please drop me a note here - as I will do when I start producing some real code)

@ddanier
Copy link
Member

ddanier commented Sep 29, 2022

About Partial[...], see python/mypy#11501
(mypy currently does always expect __class_getitem__ to be used for generic types)

@ddanier
Copy link
Member

ddanier commented Jul 13, 2023

Note: pydantic itself thought about adding partial support, but then decided to not do this for now. Reason is - like with this ticket - that there is no good way to get the typing definition done, as there is no partial equivalent in the python typing system now. As of this I will do the same and kind of ignore the fact that pydantic-partial will not (and kind of cannot) produce partial models in a way type checkers could recognise. There is just no base typing mechanism to support this.

See pydantic/pydantic#1673 (comment) for reference.

Note: I will keep this issue open to have this documented. It still is an open issue - but just one we cannot resolve in a good way.

@ddanier ddanier added the wontfix This will not be worked on label Jul 13, 2023
@team23 team23 locked as resolved and limited conversation to collaborators Jul 29, 2023
@ddanier ddanier pinned this issue Jul 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

2 participants