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

v2. Accessing a function's 'fields' and 'values' when using validate_call (possible bug) #6345

Closed
maread99 opened this issue Jul 1, 2023 · 4 comments · Fixed by #7542
Closed
Assignees
Labels
unconfirmed Bug not yet confirmed as valid/applicable

Comments

@maread99
Copy link

maread99 commented Jul 1, 2023

Hi. v2 migration issue. I use pydantic to validate function inputs and I've hit a bit of wall trying to access the 'fields' and 'values' of the function being validated.

In v1 this can be done...

import pydantic

class ACustomType:

    @classmethod
    def __get_validators__(cls):
        yield cls._validate

    @classmethod
    def _validate(cls, v, field, values):
        if v == 1:
            msg = f"`{field.name}` cannot be 1"
            raise ValueError(msg)

        if v is None:
            v = values["self"].get_a_value()

        return v

class ACls:

    def get_a_value():
        return 3
    
    @pydantic.validate_arguments
    def add_one(self, a_validated_value: ACustomType):
        return a_validated_value + 1

such that:

>>> inst = ACls()
>>> inst.add_one(8)
9
>>> inst.add_one(1)
ValidationError                           Traceback (most recent call last)
<blah blah>
ValidationError: 1 validation error for AddOne
a_validated_value
  `a_validated_value` cannot be 1 (type=value_error)

Having gone through the docs, this is how I would have thought the same could be done in v2:

import pydantic
from pydantic_core import core_schema
from typing import Any

class ACustomType:

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.field_after_validator_function(
            cls._validate,
            core_schema.any_schema(),
        )

    @classmethod
    def _validate(cls, v, info: core_schema.FieldValidationInfo):
        if v == 1:
            msg = f"`{info.field_name}` cannot be 1"
            raise ValueError(msg)

        if v is None:
            v = info.data["self"].get_a_value()

        return v

class ACls:

    def get_a_value():
        return 3
    
    @pydantic.validate_call
    def add_one(self, a_validated_value: ACustomType):
        return a_validated_value + 1

However...

>>> inst = ACls()
>>> inst.add_one(1)
RuntimeError                              Traceback (most recent call last)
Input In [2], in <cell line: 2>()
      1 inst = ACls()
----> 2 inst.add_one(1)

File ~\venv\lib\site-packages\pydantic\_internal\_validate_call.py:71, in ValidateCallWrapper.__call__(self, *args, **kwargs)
     70 def __call__(self, *args: Any, **kwargs: Any) -> Any:
---> 71     return self.__pydantic_validator__.validate_python(pydantic_core.ArgsKwargs(args, kwargs))

RuntimeError: This validator expected to be run inside the context of a model field but no model field was found

Is it still possible to access the function's 'fields' and 'values' in v2? If so, does this error have its roots in a bug or am I just not doing it right?

(the actual custom types I'm struggling with are here)

Thanks for the excellent library!

sys info from python -c "import pydantic.version; print(pydantic.version.version_info())"
pydantic version: 2.0
pydantic-core version: 2.0.1 release build profile
install path: \venv\Lib\site-packages\pydantic
python version: 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
platform: Windows-10-10.0.22621-SP0
optional deps. installed: ['typing-extensions']

Selected Assignee: @lig

@pydantic-hooky pydantic-hooky bot added the unconfirmed Bug not yet confirmed as valid/applicable label Jul 1, 2023
@maread99
Copy link
Author

@lig, any chance you could have a quick look at this and advise if there's a bug here or whether it's no longer possible to access a validated function's 'fields' and 'values' in v2?

Thank you.

@lig
Copy link
Contributor

lig commented Jul 17, 2023

@maread99 core_schema.field_after_validator_function must be used with the field name as the argument. This doesn't make sense using it in a type like this.

The following code works

    import pydantic
    from pydantic_core import core_schema
    from typing import Any

    class ACustomType:
        @classmethod
        def __get_pydantic_core_schema__(
            cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
        ) -> core_schema.CoreSchema:
            return core_schema.general_after_validator_function(
                cls._validate,
                core_schema.any_schema(),
            )

        @classmethod
        def _validate(cls, v, info: core_schema.ValidationInfo):
            print(info)
            if v == 1:
                msg = f"`{cls.__qualname__}` cannot be 1"
                raise ValueError(msg)
            return v

    class ACls:
        def get_a_value():
            return 3

        @pydantic.validate_call
        def add_one(self, a_validated_value: ACustomType):
            return a_validated_value + 1

    inst = ACls()
    inst.add_one(1)

However, I think that it would be better to define a model and a field of your custom type. It's not clear to me at the moment what you're trying to achieve.

@samuelcolvin
Copy link
Member

@lig is right, you can't access field_name unless you're using a field_validator validator on a model or dataclass.

You shouldn't need to include the field name in the error message - it's included anyway when the ValidationError is displayed.

In you're case you'll get a ValidationInfo as the info paramter, not a FieldValidationInfo.

@maread99
Copy link
Author

Thank you for the replies.

It's not clear to me at the moment what you're trying to achieve.

@lig, all I'd like to know is how to use @validate_call in v2 so that validation of later parameters can depend on the values received by an earlier parameter (in the same way that a model's validators can access the values of earlier fields). This could be done in v1 although I can't find a way in v2. This pretty much explains what I'm after...

from pydantic import BeforeValidator, validate_call
from typing import Annotated

def validator_a(a) -> str:
    # validates something
    return a

def validator_b(b) -> str:
    # validates something according to the value received to func's 'a' parameter
    # i.e, how can I access in this scope the object returned by `validator_a`
    return b

@validate_call
def func(
    a: Annotated[str, BeforeValidator(validator_a)],
    b: Annotated[str, BeforeValidator(validator_b)],
):
    return a + b

This example uses the Annotated approach while the earlier one explored custom types. I'm not so concerned which approach to use as simply being able to do this in some way.

Possibly related, this part of the docs notes that:

These names (together with "args" and "kwargs") may or may not (depending on the function's signature) appear as fields on the internal Pydantic model accessible via .model

...but from where might .model be accessible?

Thanks again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
unconfirmed Bug not yet confirmed as valid/applicable
Projects
None yet
3 participants