'pydantic custom hypothesis build

Problem in a nutshell

I am having issues with the hypothesis build strategy and custom pydantic data types (no values are returned when invoking the build strategy on my custom data type.

Problem in more detail

Given the following pydantic custom type, which just validates if a value is a timezone.

import pytz
from pydantic import StrictStr

TIMEZONES = pytz.common_timezones_set


class CountryTimeZone(StrictStr):
    """Validate a country timezone."""

    @classmethod
    def __get_validators__(cls):
        yield from super().__get_validators__()
        yield cls.validate_timezone

    @classmethod
    def validate_timezone(cls, v):
        breakpoint()
        if v not in TIMEZONES:
            raise ValueError(f"{v} is not a valid country timezone")

        return v

    @classmethod
    def __modify_schema__(cls, field_schema):
        field_schema.update(examples=TIMEZONES)

When I attempt to use this in some schema...

from pydantic import BaseModel


class Foo(BaseModel):
    bar: CountryTimeZone

and subsequently try to build an example in a test, using the pydantic hypothesis plugin like.

from hypothesis import given
from hypothesis import strategies as st

@given(st.builds(Foo))
def test_something_interesting(schema) -> None:
    # Some assertions
    ...

schema.bar is always "".

Questions

  1. Is there something missing from this implementation, meaning that values like "Asia/Krasnoyarsk" aren't being generated? From the documentation, examples like PaymentCardNumber and EmailStr build as expected.
  2. Even when using StrictStr by itself, the resulting value is also an empty string. I tried to inherit from str but still no luck.


Solution 1:[1]

Came across the same problem today. Seems like the wording in the hypothesis plugin docs give the wrong impression. Pydantic has written hypothesis integrations for their custom types, not that hypothesis supports custom pydantic types out of the box.

Here is a full example of creating a custom class, assigning it a test strategy and using it in a pydantic model.

import re
from hypothesis import given, strategies as st
from pydantic import BaseModel

CAPITAL_WORD = r"^[A-Z][a-z]+"
CAPITAL_WORD_REG = re.compile(CAPITAL_WORD)


class MustBeCapitalWord(str):
    """Custom class that validates the string is a single of only letters
    starting with a capital case letter."""

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

    @classmethod
    def __modify_schema__(cls, field_schema):
        # optional stuff, updates the schema if you choose to export the
        # pydantic schema
        field_schema.UPDATE(
            pattern=CAPITAL_WORD,
            examples=["Hello", "World"],
        )

    @classmethod
    def validate(cls, v):
        if not isinstance(v, str):
            raise TypeError("string required")
        if not v:
            raise ValueError("No capital letter found")
        elif CAPITAL_WORD_REG.match(v) is None:
            raise ValueError("Input is not a valid word starting with capital letter")
        return cls(v)

    def __repr__(self):
        return f"MustBeCapitalWord({super().__repr__()})"


# register a strategy for our custom type
st.register_type_strategy(
    MustBeCapitalWord,
    st.from_regex(CAPITAL_WORD, fullmatch=True),
)


# use our custom type in a pydantic model
class Model(BaseModel):
    word: MustBeCapitalWord


# test it all
@given(st.builds(Model))
def test_model(instance):
    assert instance.word[0].isupper()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 spacebear42