'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
- Is there something missing from this implementation, meaning that values like
"Asia/Krasnoyarsk"
aren't being generated? From the documentation, examples likePaymentCardNumber
andEmailStr
build as expected. - Even when using
StrictStr
by itself, the resulting value is also an empty string. I tried to inherit fromstr
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 |