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

Mypy typing errors with "reusable" model_validator #8272

Closed
1 task done
vicchi opened this issue Dec 1, 2023 · 1 comment · Fixed by #8285
Closed
1 task done

Mypy typing errors with "reusable" model_validator #8272

vicchi opened this issue Dec 1, 2023 · 1 comment · Fixed by #8285
Assignees
Labels
bug V2 Bug related to Pydantic V2 mypy related to mypy

Comments

@vicchi
Copy link

vicchi commented Dec 1, 2023

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

I'm at the final stages of migrating a v1 code base to v2. Functionally it's complete, the code runs as expected without any issues (the v1 to v2 migration guide was really useful). But I'm getting mypy errors when using my attempt to migrate "reused" v1 root_validator instances to v2 model_validator instances.

When I use a decorated model_validator inline in the model definition all is well. The model validates as expected and mypy is content and happy.

When I use my attempt at a reusable model_validator the model still validates as expected but mypy is not happy at all.

The "inline" test case I've put together, plus the output from mypy is:

#!/usr/bin/env python
# pylint: disable=all

from typing import Any, Dict, Optional

from pydantic import BaseModel, ConfigDict, model_validator

def normalise_known_invalids(values: Dict[str, Any]) -> Dict[str, Any]:
    if values:
        for key, value in values.items():
            value = value.strip()
            if value:
                if value.lower() in ['', 'n/a', '-']:
                    values[key] = None
                else:
                    values[key] = value

            else:
                values[key] = None

    return values


class InlineValidatorModel(BaseModel):
    fld: Optional[str] = None

    @model_validator(mode='before')
    @classmethod
    def normaliser(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        return normalise_known_invalids(values)

    model_config = ConfigDict(str_strip_whitespace=True, extra='forbid')

def main():
    model = InlineValidatorModel()
    print(f'empty inline model: {model}')

    model = InlineValidatorModel(fld='OK')
    print(f'valid input inline model: {model}')

    model = InlineValidatorModel(fld='n/a ')
    print(f'invalid input inline model: {model}')

if __name__ == '__main__':
    main()
$ mypy model_test.py
Success: no issues found in 1 source file

But the mypy output from the "reused" variant, code in the example below, is:

$ mypy model_test2.py
model_test2.py:29: error: Argument 1 has incompatible type "Callable[[dict[str, Any]], dict[str, Any]]"; expected "ModelBeforeValidator | ModelBeforeValidatorWithoutInfo"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Also, this is possibly related to #6693.

Example Code

#!/usr/bin/env python
# pylint: disable=all

from typing import Any, Dict, Optional

from pydantic import BaseModel, ConfigDict, model_validator

def normalise_known_invalids(values: Dict[str, Any]) -> Dict[str, Any]:
    if values:
        for key, value in values.items():
            value = value.strip()
            if value:
                if value.lower() in ['', 'n/a', '-']:
                    values[key] = None
                else:
                    values[key] = value

            else:
                values[key] = None

    return values

class ReusedValidatorModel(BaseModel):
    fld: Optional[str] = None

    _normaliser = model_validator(mode='before')(normalise_known_invalids)
    model_config = ConfigDict(str_strip_whitespace=True, extra='forbid')


def main():
    model = ReusedValidatorModel()
    print(f'empty reused model: {model}')

    model = ReusedValidatorModel(fld='OK')
    print(f'valid input reused model: {model}')

    model = ReusedValidatorModel(fld='n/a ')
    print(f'invalid inputreused  model: {model}')

if __name__ == '__main__':
    main()

Python, Pydantic & OS Version

pydantic version: 2.5.2
        pydantic-core version: 2.14.5
          pydantic-core build: profile=release pgo=true
                 install path: /home/gary/scratch/.direnv/python-3.10.12/lib/python3.10/site-packages/pydantic
               python version: 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
                     platform: Linux-6.2.0-37-generic-x86_64-with-glibc2.35
             related packages: mypy-1.7.1 pydantic-settings-2.0.3 typing_extensions-4.8.0
@vicchi vicchi added bug V2 Bug related to Pydantic V2 pending Awaiting a response / confirmation labels Dec 1, 2023
@sydney-runkle sydney-runkle removed the pending Awaiting a response / confirmation label Dec 1, 2023
@sydney-runkle sydney-runkle self-assigned this Dec 1, 2023
@sydney-runkle
Copy link
Member

@vicchi,

Good call. This is something we can fix pretty easily! I'll have a PR up shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V2 Bug related to Pydantic V2 mypy related to mypy
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants