0

maybe someone can help me out with a pydantic issue. I have been reading up on some reports of this, but I still do not understand how to circumvent the problem:

In short:

Can I have an Optional constrained type - in my case, I want a field that is Optional[list[float] = None with the constraint that the list must have two elements or is None after validation.

Here is what I have so far...

from typing import Annotated, Optional

from pydantic import AfterValidator, BaseModel, Field, ValidationError, ValidationInfo


def some_eventual_validation(value, info: ValidationInfo):
    """Dummy validator that eventually will do something (compare value to min/max)."""
    return value


class OptionA(BaseModel):
    min: Optional[float] = Field(None)
    max: Optional[float] = Field(None)
    value: Annotated[
        Optional[list[float]],
        AfterValidator(some_eventual_validation),
        Field(
            None,
            min_length=2,
            max_length=2,
            validate_default=True,
        ),
    ]

try:
    OptionA()
except TypeError as e:
    print(e)
# In the case the default works, but it seems to apply the constraints from the Field annotation to the default value (which I believe is not the intended behavior for Optional fields)

class OptionB(BaseModel):
    min: Optional[float] = Field(None)
    max: Optional[float] = Field(None)
    value: Optional[
        Annotated[
            list[float],
            AfterValidator(some_eventual_validation),
            Field(
                None,
                min_length=2,
                max_length=2,
                validate_default=True,
            ),
        ]
    ]

print("====")
try:
    OptionB()
except ValidationError as e:
    print(e)
# In this case, the default does not work --> shouldn't the code be able to infer the default from the Field annotation?
0

2 Answers 2

0

It turns out the solution is conlist, although I am not sure why the Annotated pattern alone does not work...

class OptionC(BaseModel):
    min: Optional[float] = Field(None)
    max: Optional[float] = Field(None)
    value: Optional[
        Annotated[
            conlist(float, min_length=2, max_length=2),
            AfterValidator(some_eventual_validation),
        ]
    ] = Field(default=None, validate_default=True)
Sign up to request clarification or add additional context in comments.

Comments

0

What about

def validate_option_b_input(value: Optional[list[float]]):
    if value is not None and len(value) != 2:
        raise ValueError("Length must be 2")
    return value

class OptionB(BaseModel):
    min: Optional[float] = None
    max: Optional[float] = None
    value: Annotated[
        Optional[list[float]],
        AfterValidator(some_eventual_validation),
        AfterValidator(validate_option_b_input),
        Field(validate_default=True),
    ] = None

2 Comments

That could work, but it doesn't use any of the in-built pydantic list constrain functionality, which is nice to rely on
Well, you could also try it like this: Annotated[list[float], AfterValidator(some_eventual_validation), Len(2, 2)]. Maybe you will have more control over the execution order this way. I believe, that's what conlist does anyway.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.