Reputation: 1811
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.
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 ""
.
"Asia/Krasnoyarsk"
aren't being generated? From the documentation, examples like PaymentCardNumber
and EmailStr
build as expected.StrictStr
by itself, the resulting value is also an empty string. I tried to inherit from str
but still no luck.Upvotes: 3
Views: 1104
Reputation: 31
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()
Upvotes: 3