mypy
mypy copied to clipboard
Protocol for an instance method taking a `self` argument
Bug Report
We have several protocols we use to define @model_validator
in Pydantic V2, see here.
The problem is that the simplest use case fails. See below:
To Reproduce
from pydantic import BaseModel, model_validator
class UserModel(BaseModel):
username: str
password1: str
password2: str
@model_validator(mode="after")
def check_passwords_match(self) -> "UserModel":
if self.password1 != self.password2:
raise ValueError("passwords do not match")
return self
Expected Behavior
I expect no issues with mypy
. As I don't have any with pyright
.
Actual Behavior
a.py:9: error: Argument 1 has incompatible type "Callable[[UserModel], UserModel]"; expected "ModelAfterValidator | ModelAfterValidatorWithoutInfo" [arg-type]
Found 1 error in 1 file (checked 1 source file)
Your Environment
- Mypy version used: mypy 1.4.1 (compiled: yes)
- Mypy command-line flags: none
- Mypy configuration options from
mypy.ini
(and other config files): no config file provided. - Python version used: 3.11.1
Here's a self contained example:
from typing import Any, Callable, Protocol, TypeVar
T = TypeVar('T')
class ModelAfterValidator(Protocol):
@staticmethod
def __call__(
self: T,
) -> T:
...
def model_validator() -> Callable[[ModelAfterValidator], Any]:
raise NotImplementedError
class UserModel:
@model_validator()
def check_passwords_match(self) -> 'UserModel':
raise NotImplementedError
I guess the whole @staticmethod
thing on a Protocol with self: <type>
is to blame. Open to better suggestions.
@Kludex can you update the title to something along the lines of "Protocol for an instance method taking a self
argument"
I think we can just use Callable
for now, but is there any way to require a self
parameter as the first argument and that the return type be the same type (which is what I was attempting to do originally)?
I'm not able to repro the original bug in this bug report using mypy 1.5. Perhaps it was fixed in a recent release, or perhaps the pydantic library has changed since the original bug report.
Pydantic has changed but the self contained example in https://github.com/python/mypy/issues/15620#issuecomment-1625688906 should still be valid
I think mypy is correct to complain about https://github.com/python/mypy/issues/15620#issuecomment-1625688906 ; pyright does as well. check_passwords_match
does not have the right return type in the presence of UserModel
subclasses. If you change the annotation to def check_passwords_match(self: T) -> T:
, both mypy and pyright accept it
I guess then let me broaden the question: how can I type this decorator to enforce that it get applied to an instance method? In the real world model_validator
takes a mode={before,after}
parameter that uses overloading to satisfy two different signatures. The before
case must be a class method and the after
case must be an instance method. The classmethod isn't hard to enforce, it's instance methods that I'm struggling with. Making the first argument be self
seemed like a reasonable compromise.
I'm not sure exactly I understand, but in pydantic case I think you can try a self: BaseModel
annotation