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
Fix typing of model_validator #6514
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -409,32 +409,17 @@ def __call__( # noqa: D102 | |
... | ||
|
||
|
||
class ModelAfterValidatorWithoutInfo(Protocol): | ||
"""A `@model_validator` decorated function signature. This is used when `mode='after'` and the function does not | ||
have info argument. | ||
""" | ||
|
||
@staticmethod | ||
def __call__( # noqa: D102 | ||
self: _ModelType, # type: ignore | ||
) -> _ModelType: | ||
... | ||
|
||
|
||
class ModelAfterValidator(Protocol): | ||
"""A `@model_validator` decorated function signature. This is used when `mode='after'`.""" | ||
|
||
@staticmethod | ||
def __call__( # noqa: D102 | ||
self: _ModelType, # type: ignore | ||
__info: _core_schema.ValidationInfo, | ||
) -> _ModelType: | ||
... | ||
ModelAfterValidatorWithoutInfo = Callable[[_ModelType], _ModelType] | ||
"""A `@model_validator` decorated function signature. This is used when `mode='after'` and the function does not | ||
have info argument. | ||
""" | ||
|
||
ModelAfterValidator = Callable[[_ModelType, _core_schema.ValidationInfo], _ModelType] | ||
"""A `@model_validator` decorated function signature. This is used when `mode='after'`.""" | ||
|
||
_AnyModelWrapValidator = Union[ModelWrapValidator, ModelWrapValidatorWithoutInfo] | ||
_AnyModeBeforeValidator = Union[ModelBeforeValidator, ModelBeforeValidatorWithoutInfo] | ||
_AnyModeAfterValidator = Union[ModelAfterValidator, ModelAfterValidatorWithoutInfo] | ||
_AnyModelAfterValidator = Union[ModelAfterValidator[_ModelType], ModelAfterValidatorWithoutInfo[_ModelType]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this reparametrization do anything? It looks like the same typevar is used in the original definition of this thing. Is this for pseudo-documentation reasons or does it type check differently? Should it make use of a TypeAlias? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's because generic aliases require the type var reparametrization |
||
|
||
|
||
@overload | ||
|
@@ -457,7 +442,9 @@ def model_validator( | |
def model_validator( | ||
*, | ||
mode: Literal['after'], | ||
) -> Callable[[_AnyModeAfterValidator], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo]]: | ||
) -> Callable[ | ||
[_AnyModelAfterValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] | ||
]: | ||
... | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, mypy didn't recognize the the previously annotated
Protocol
object ascallable
? (Even though it hadcall
method? By switching to explicitly annotating withCallable
, the annotations are correct?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that we're annotating callable functions as callable though. (Though
Protocol
is supposed to solve this for us, no?)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory you can use either, but it's a little bit weird because here we want to require the
self
argument, in particular because it helps distinguish from the other alternative signatures. But my hack to make that work does not seem to have been compatible with mypy so this is a simpler albeit less pedantic version that hopefully will be.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if I'm understanding your thought correctly.
But if those validators work with other signatures as well (e.g. with classmethod signatures) it would be misleading imo. The implementation as far as I saw supports static-, class- and instance-signatures and even decorators. Therefore, I don't think you have to enforce an instance-signature here (i.e. with self argument).
Actually, even the documentation suggests a classmethod-signature for
model_validator
withmode="after"
. Does the solution of this PR really work with classmethod-signatures (and maybe with a@classmethod
decorator) though, did you test it? If not, is anything wrong with doing it similar tofield_validator
? It seems to work there.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is precisely what we wanted to avoid, the docs are wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I tried around by myself but I really couldn't get a Protocol typing for instance methods to work together with the decorator. At least with mypy. Very sad, maybe it's possible in the future.
Btw, why do you even want to avoid classmethod signatures? Is there anywhere a discussion about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're getting an instance of the model. Of course
(cls: Type['Model'], v: 'Model')
and add an@classmethod
works but why do that instead of(self)
which works great with type checkers, etc.