Open jdinunzio opened 2 years ago
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 https://github.com/python/mypy/issues/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
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. ;-)
Currently my feeling is I have to write a mypy plugin... 🤷♂️🤔
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.
@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)
About Partial[...]
, see https://github.com/python/mypy/issues/11501
(mypy currently does always expect __class_getitem__
to be used for generic types)
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 https://github.com/pydantic/pydantic/issues/1673#issuecomment-1557267229 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.
Issue
Using python 3.10.7, pydantic 4.3.2, pydantic-partial 0.3.2 and 0.3.3, mypy 0.971
running mypy will return:
(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?