Repository: pydantic/pydantic

Issue:
Generating Pydantic-specific types with Hypothesis
:wave: I'm a Hypothesis core developer, and I know there are lots of people who like generating Pydantic objects to test their code with Hypothesis.  Automatic generation already works really well for builtin and `typing` generic types - see e.g. https://github.com/samuelcolvin/pydantic/issues/1666, https://github.com/timothycrosley/hypothesis-auto/issues/10, https://github.com/HypothesisWorks/hypothesis/pull/2388, https://github.com/HypothesisWorks/hypothesis/pull/2580, and https://github.com/HypothesisWorks/hypothesis/pull/2640 (cc @MrMrRobat)

I'm proposing that Pydantic adds [a setuptools entry point](https://hypothesis.readthedocs.io/en/latest/strategies.html#registering-strategies-via-setuptools-entry-points) to [register custom strategies](https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.register_type_strategy) for [Pydantic-specific or constrained types](https://pydantic-docs.helpmanual.io/usage/types/#pydantic-types).  This is a nice way to offer optional integrations, because *Hypothesis* will load it at import-time without adding any runtime dependency or overhead to Pydantic.  Example code:

```python
# In `setup.py`
setup(
    ...,
    entry_points={"hypothesis": ["_ = pydantic._hypothesis_setup"]},
)

# In `pydantic._hypothesis_setup.py`
import pydantic
import hypothesis.strategies as st

st.register_type_strategy(pydantic.EmailStr, st.emails())
st.register_type_strategy(pydantic.UUID1, st.uuids(version=1))
...
```

Constrained types are a little trickier, since e.g. `confloat(ge=0, lt=1)` dynamically creates a subclass of `ConstrainedFloat`- we'll have to register a function *for* the parent class, which takes the child class and returns the strategy (e.g. `st.floats(min_value=0, max_value=1, exclude_max=True)`).  Fortunately this already works, because it's how we support parametrised generic types!

I'm happy to make a PR if @samuelcolvin or @PrettyWood think this is a good idea, though it might take me a while :slightly_smiling_face:


Discussion:
One late night later: https://github.com/samuelcolvin/pydantic/compare/master...Zac-HD:hypothesis-plugin

It doesn't have any tests yet so there are probably horrible bugs, but it's a solid proof of concept :grin: 
This looks great, happy to accept a PR.
Awesome!  I might take a few weeks to finish this off because it'll need quite a few tests and some Hypothesis-for-Pydantic-users documentation, but it's on my todo list :grin: 
great, no hurry.

Resolving patch:
diff --git a/pydantic/_hypothesis_plugin.py b/pydantic/_hypothesis_plugin.py
new file mode 100644
--- /dev/null
+++ b/pydantic/_hypothesis_plugin.py
@@ -0,0 +1,349 @@
+"""
+Register Hypothesis strategies for Pydantic custom types.
+
+This enables fully-automatic generation of test data for most Pydantic classes.
+
+Note that this module has *no* runtime impact on Pydantic itself; instead it
+is registered as a setuptools entry point and Hypothesis will import it if
+Pydantic is installed.  See also:
+
+https://hypothesis.readthedocs.io/en/latest/strategies.html#registering-strategies-via-setuptools-entry-points
+https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.register_type_strategy
+https://hypothesis.readthedocs.io/en/latest/strategies.html#interaction-with-pytest-cov
+https://pydantic-docs.helpmanual.io/usage/types/#pydantic-types
+
+Note that because our motivation is to *improve user experience*, the strategies
+are always sound (never generate invalid data) but sacrifice completeness for
+maintainability (ie may be unable to generate some tricky but valid data).
+
+Finally, this module makes liberal use of `# type: ignore[<code>]` pragmas.
+This is because Hypothesis annotates `register_type_strategy()` with
+`(T, SearchStrategy[T])`, but in most cases we register e.g. `ConstrainedInt`
+to generate instances of the builtin `int` type which match the constraints.
+"""
+
+import contextlib
+import ipaddress
+import json
+import math
+from fractions import Fraction
+from typing import Callable, Dict, Type, Union, cast, overload
+
+import hypothesis.strategies as st
+
+import pydantic
+import pydantic.color
+import pydantic.types
+
+# FilePath and DirectoryPath are explicitly unsupported, as we'd have to create
+# them on-disk, and that's unsafe in general without being told *where* to do so.
+#
+# URLs are unsupported because it's easy for users to define their own strategy for
+# "normal" URLs, and hard for us to define a general strategy which includes "weird"
+# URLs but doesn't also have unpredictable performance problems.
+#
+# conlist() and conset() are unsupported for now, because the workarounds for
+# Cython and Hypothesis to handle parametrized generic types are incompatible.
+# Once Cython can support 'normal' generics we'll revisit this.
+
+# Emails
+try:
+    import email_validator
+except ImportError:  # pragma: no cover
+    pass
+else:
+
+    def is_valid_email(s: str) -> bool:
+        # Hypothesis' st.emails() occasionally generates emails like 0@A0--0.ac
+        # that are invalid according to email-validator, so we filter those out.
+        try:
+            email_validator.validate_email(s, check_deliverability=False)
+            return True
+        except email_validator.EmailNotValidError:
+            return False
+
+    # Note that these strategies deliberately stay away from any tricky Unicode
+    # or other encoding issues; we're just trying to generate *something* valid.
+    st.register_type_strategy(pydantic.EmailStr, st.emails().filter(is_valid_email))  # type: ignore[arg-type]
+    st.register_type_strategy(
+        pydantic.NameEmail,
+        st.builds(
+            '{} <{}>'.format,  # type: ignore[arg-type]
+            st.from_regex('[A-Za-z0-9_]+( [A-Za-z0-9_]+){0,5}', fullmatch=True),
+            st.emails().filter(is_valid_email),
+        ),
+    )
+
+# PyObject - dotted names, in this case taken from the math module.
+st.register_type_strategy(
+    pydantic.PyObject,
+    st.sampled_from(
+        [cast(pydantic.PyObject, f'math.{name}') for name in sorted(vars(math)) if not name.startswith('_')]
+    ),
+)
+
+# CSS3 Colors; as name, hex, rgb(a) tuples or strings, or hsl strings
+_color_regexes = (
+    '|'.join(
+        (
+            pydantic.color.r_hex_short,
+            pydantic.color.r_hex_long,
+            pydantic.color.r_rgb,
+            pydantic.color.r_rgba,
+            pydantic.color.r_hsl,
+            pydantic.color.r_hsla,
+        )
+    )
+    # Use more precise regex patterns to avoid value-out-of-range errors
+    .replace(pydantic.color._r_sl, r'(?:(\d\d?(?:\.\d+)?|100(?:\.0+)?)%)')
+    .replace(pydantic.color._r_alpha, r'(?:(0(?:\.\d+)?|1(?:\.0+)?|\.\d+|\d{1,2}%))')
+    .replace(pydantic.color._r_255, r'(?:((?:\d|\d\d|[01]\d\d|2[0-4]\d|25[0-4])(?:\.\d+)?|255(?:\.0+)?))')
+)
+st.register_type_strategy(
+    pydantic.color.Color,
+    st.one_of(
+        st.sampled_from(sorted(pydantic.color.COLORS_BY_NAME)),
+        st.tuples(
+            st.integers(0, 255),
+            st.integers(0, 255),
+            st.integers(0, 255),
+            st.none() | st.floats(0, 1) | st.floats(0, 100).map('{}%'.format),
+        ),
+        st.from_regex(_color_regexes, fullmatch=True),
+    ),
+)
+
+
+# Card numbers, valid according to the Luhn algorithm
+
+
+def add_luhn_digit(card_number: str) -> str:
+    # See https://en.wikipedia.org/wiki/Luhn_algorithm
+    for digit in '0123456789':
+        with contextlib.suppress(Exception):
+            pydantic.PaymentCardNumber.validate_luhn_check_digit(card_number + digit)
+            return card_number + digit
+    raise AssertionError('Unreachable')  # pragma: no cover
+
+
+card_patterns = (
+    # Note that these patterns omit the Luhn check digit; that's added by the function above
+    '4[0-9]{14}',  # Visa
+    '5[12345][0-9]{13}',  # Mastercard
+    '3[47][0-9]{12}',  # American Express
+    '[0-26-9][0-9]{10,17}',  # other (incomplete to avoid overlap)
+)
+st.register_type_strategy(
+    pydantic.PaymentCardNumber,
+    st.from_regex('|'.join(card_patterns), fullmatch=True).map(add_luhn_digit),  # type: ignore[arg-type]
+)
+
+# UUIDs
+st.register_type_strategy(pydantic.UUID1, st.uuids(version=1))  # type: ignore[arg-type]
+st.register_type_strategy(pydantic.UUID3, st.uuids(version=3))  # type: ignore[arg-type]
+st.register_type_strategy(pydantic.UUID4, st.uuids(version=4))  # type: ignore[arg-type]
+st.register_type_strategy(pydantic.UUID5, st.uuids(version=5))  # type: ignore[arg-type]
+
+# Secrets
+st.register_type_strategy(pydantic.SecretBytes, st.binary().map(pydantic.SecretBytes))
+st.register_type_strategy(pydantic.SecretStr, st.text().map(pydantic.SecretStr))
+
+# IP addresses, networks, and interfaces
+st.register_type_strategy(pydantic.IPvAnyAddress, st.ip_addresses())
+st.register_type_strategy(
+    pydantic.IPvAnyInterface,
+    st.from_type(ipaddress.IPv4Interface) | st.from_type(ipaddress.IPv6Interface),
+)
+st.register_type_strategy(
+    pydantic.IPvAnyNetwork,
+    st.from_type(ipaddress.IPv4Network) | st.from_type(ipaddress.IPv6Network),
+)
+
+# We hook into the con***() functions and the ConstrainedNumberMeta metaclass,
+# so here we only have to register subclasses for other constrained types which
+# don't go via those mechanisms.  Then there are the registration hooks below.
+st.register_type_strategy(pydantic.StrictBool, st.booleans())
+st.register_type_strategy(pydantic.StrictStr, st.text())  # type: ignore[arg-type]
+
+
+# Constrained-type resolver functions
+#
+# For these ones, we actually want to inspect the type in order to work out a
+# satisfying strategy.  First up, the machinery for tracking resolver functions:
+
+RESOLVERS: Dict[type, Callable[[type], st.SearchStrategy]] = {}  # type: ignore[type-arg]
+
+
+@overload
+def _registered(typ: Type[pydantic.types.T]) -> Type[pydantic.types.T]:
+    pass
+
+
+@overload
+def _registered(typ: pydantic.types.ConstrainedNumberMeta) -> pydantic.types.ConstrainedNumberMeta:
+    pass
+
+
+def _registered(
+    typ: Union[Type[pydantic.types.T], pydantic.types.ConstrainedNumberMeta]
+) -> Union[Type[pydantic.types.T], pydantic.types.ConstrainedNumberMeta]:
+    # This function replaces the version in `pydantic.types`, in order to
+    # effect the registration of new constrained types so that Hypothesis
+    # can generate valid examples.
+    pydantic.types._DEFINED_TYPES.add(typ)
+    for supertype, resolver in RESOLVERS.items():
+        if issubclass(typ, supertype):
+            st.register_type_strategy(typ, resolver(typ))  # type: ignore
+            return typ
+    raise NotImplementedError(f'Unknown type {typ!r} has no resolver to register')  # pragma: no cover
+
+
+def resolves(
+    typ: Union[type, pydantic.types.ConstrainedNumberMeta]
+) -> Callable[[Callable[..., st.SearchStrategy]], Callable[..., st.SearchStrategy]]:  # type: ignore[type-arg]
+    def inner(f):  # type: ignore
+        assert f not in RESOLVERS
+        RESOLVERS[typ] = f
+        return f
+
+    return inner
+
+
+# Type-to-strategy resolver functions
+
+
+@resolves(pydantic.Json)
+@resolves(pydantic.JsonWrapper)
+def resolve_json(cls):  # type: ignore[no-untyped-def]
+    try:
+        inner = st.none() if cls.inner_type is None else st.from_type(cls.inner_type)
+    except Exception:
+        finite = st.floats(allow_infinity=False, allow_nan=False)
+        inner = st.recursive(
+            base=st.one_of(st.none(), st.booleans(), st.integers(), finite, st.text()),
+            extend=lambda x: st.lists(x) | st.dictionaries(st.text(), x),
+        )
+    return st.builds(
+        json.dumps,
+        inner,
+        ensure_ascii=st.booleans(),
+        indent=st.none() | st.integers(0, 16),
+        sort_keys=st.booleans(),
+    )
+
+
+@resolves(pydantic.ConstrainedBytes)
+def resolve_conbytes(cls):  # type: ignore[no-untyped-def]  # pragma: no cover
+    min_size = cls.min_length or 0
+    max_size = cls.max_length
+    if not cls.strip_whitespace:
+        return st.binary(min_size=min_size, max_size=max_size)
+    # Fun with regex to ensure we neither start nor end with whitespace
+    repeats = '{{{},{}}}'.format(
+        min_size - 2 if min_size > 2 else 0,
+        max_size - 2 if (max_size or 0) > 2 else '',
+    )
+    if min_size >= 2:
+        pattern = rf'\W.{repeats}\W'
+    elif min_size == 1:
+        pattern = rf'\W(.{repeats}\W)?'
+    else:
+        assert min_size == 0
+        pattern = rf'(\W(.{repeats}\W)?)?'
+    return st.from_regex(pattern.encode(), fullmatch=True)
+
+
+@resolves(pydantic.ConstrainedDecimal)
+def resolve_condecimal(cls):  # type: ignore[no-untyped-def]
+    min_value = cls.ge
+    max_value = cls.le
+    if cls.gt is not None:
+        assert min_value is None, 'Set `gt` or `ge`, but not both'
+        min_value = cls.gt
+    if cls.lt is not None:
+        assert max_value is None, 'Set `lt` or `le`, but not both'
+        max_value = cls.lt
+    s = st.decimals(min_value, max_value, allow_nan=False)
+    if cls.lt is not None:
+        s = s.filter(lambda d: d < cls.lt)
+    if cls.gt is not None:
+        s = s.filter(lambda d: cls.gt < d)
+    return s
+
+
+@resolves(pydantic.ConstrainedFloat)
+def resolve_confloat(cls):  # type: ignore[no-untyped-def]
+    min_value = cls.ge
+    max_value = cls.le
+    exclude_min = False
+    exclude_max = False
+    if cls.gt is not None:
+        assert min_value is None, 'Set `gt` or `ge`, but not both'
+        min_value = cls.gt
+        exclude_min = True
+    if cls.lt is not None:
+        assert max_value is None, 'Set `lt` or `le`, but not both'
+        max_value = cls.lt
+        exclude_max = True
+    return st.floats(min_value, max_value, exclude_min=exclude_min, exclude_max=exclude_max, allow_nan=False)
+
+
+@resolves(pydantic.ConstrainedInt)
+def resolve_conint(cls):  # type: ignore[no-untyped-def]
+    min_value = cls.ge
+    max_value = cls.le
+    if cls.gt is not None:
+        assert min_value is None, 'Set `gt` or `ge`, but not both'
+        min_value = cls.gt + 1
+    if cls.lt is not None:
+        assert max_value is None, 'Set `lt` or `le`, but not both'
+        max_value = cls.lt - 1
+
+    if cls.multiple_of is None or cls.multiple_of == 1:
+        return st.integers(min_value, max_value)
+
+    # These adjustments and the .map handle integer-valued multiples, while the
+    # .filter handles trickier cases as for confloat.
+    if min_value is not None:
+        min_value = math.ceil(Fraction(min_value) / Fraction(cls.multiple_of))
+    if max_value is not None:
+        max_value = math.floor(Fraction(max_value) / Fraction(cls.multiple_of))
+    return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of)
+
+
+@resolves(pydantic.ConstrainedStr)
+def resolve_constr(cls):  # type: ignore[no-untyped-def]  # pragma: no cover
+    min_size = cls.min_length or 0
+    max_size = cls.max_length
+
+    if cls.regex is None and not cls.strip_whitespace:
+        return st.text(min_size=min_size, max_size=max_size)
+
+    if cls.regex is not None:
+        strategy = st.from_regex(cls.regex)
+        if cls.strip_whitespace:
+            strategy = strategy.filter(lambda s: s == s.strip())
+    elif cls.strip_whitespace:
+        repeats = '{{{},{}}}'.format(
+            min_size - 2 if min_size > 2 else 0,
+            max_size - 2 if (max_size or 0) > 2 else '',
+        )
+        if min_size >= 2:
+            strategy = st.from_regex(rf'\W.{repeats}\W')
+        elif min_size == 1:
+            strategy = st.from_regex(rf'\W(.{repeats}\W)?')
+        else:
+            assert min_size == 0
+            strategy = st.from_regex(rf'(\W(.{repeats}\W)?)?')
+
+    if min_size == 0 and max_size is None:
+        return strategy
+    elif max_size is None:
+        return strategy.filter(lambda s: min_size <= len(s))
+    return strategy.filter(lambda s: min_size <= len(s) <= max_size)
+
+
+# Finally, register all previously-defined types, and patch in our new function
+for typ in pydantic.types._DEFINED_TYPES:
+    _registered(typ)
+pydantic.types._registered = _registered
diff --git a/pydantic/types.py b/pydantic/types.py
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -20,8 +20,10 @@
     TypeVar,
     Union,
     cast,
+    overload,
 )
 from uuid import UUID
+from weakref import WeakSet
 
 from . import errors
 from .utils import import_string, update_not_none
@@ -109,6 +111,30 @@
 
     ModelOrDc = Type[Union['BaseModel', 'Dataclass']]
 
+T = TypeVar('T')
+_DEFINED_TYPES: 'WeakSet[type]' = WeakSet()
+
+
+@overload
+def _registered(typ: Type[T]) -> Type[T]:
+    pass
+
+
+@overload
+def _registered(typ: 'ConstrainedNumberMeta') -> 'ConstrainedNumberMeta':
+    pass
+
+
+def _registered(typ: Union[Type[T], 'ConstrainedNumberMeta']) -> Union[Type[T], 'ConstrainedNumberMeta']:
+    # In order to generate valid examples of constrained types, Hypothesis needs
+    # to inspect the type object - so we keep a weakref to each contype object
+    # until it can be registered.  When (or if) our Hypothesis plugin is loaded,
+    # it monkeypatches this function.
+    # If Hypothesis is never used, the total effect is to keep a weak reference
+    # which has minimal memory usage and doesn't even affect garbage collection.
+    _DEFINED_TYPES.add(typ)
+    return typ
+
 
 class ConstrainedBytes(bytes):
     strip_whitespace = False
@@ -134,10 +160,7 @@ class StrictBytes(ConstrainedBytes):
 def conbytes(*, strip_whitespace: bool = False, min_length: int = None, max_length: int = None) -> Type[bytes]:
     # use kwargs then define conf in a dict to aid with IDE type hinting
     namespace = dict(strip_whitespace=strip_whitespace, min_length=min_length, max_length=max_length)
-    return type('ConstrainedBytesValue', (ConstrainedBytes,), namespace)
-
-
-T = TypeVar('T')
+    return _registered(type('ConstrainedBytesValue', (ConstrainedBytes,), namespace))
 
 
 # This types superclass should be List[T], but cython chokes on that...
@@ -272,7 +295,7 @@ def constr(
         curtail_length=curtail_length,
         regex=regex and re.compile(regex),
     )
-    return type('ConstrainedStrValue', (ConstrainedStr,), namespace)
+    return _registered(type('ConstrainedStrValue', (ConstrainedStr,), namespace))
 
 
 class StrictStr(ConstrainedStr):
@@ -344,7 +367,7 @@ def __new__(cls, name: str, bases: Any, dct: Dict[str, Any]) -> 'ConstrainedInt'
         if new_cls.lt is not None and new_cls.le is not None:
             raise errors.ConfigError('bounds lt and le cannot be specified at the same time')
 
-        return new_cls
+        return _registered(new_cls)  # type: ignore
 
 
 class ConstrainedInt(int, metaclass=ConstrainedNumberMeta):
@@ -616,7 +639,7 @@ class JsonWrapper:
 
 class JsonMeta(type):
     def __getitem__(self, t: Type[Any]) -> Type[JsonWrapper]:
-        return type('JsonWrapperValue', (JsonWrapper,), {'inner_type': t})
+        return _registered(type('JsonWrapperValue', (JsonWrapper,), {'inner_type': t}))
 
 
 class Json(metaclass=JsonMeta):
@@ -726,6 +749,8 @@ def get_secret_value(self) -> bytes:
 
 
 class PaymentCardBrand(str, Enum):
+    # If you add another card type, please also add it to the
+    # Hypothesis strategy in `pydantic._hypothesis_plugin`.
     amex = 'American Express'
     mastercard = 'Mastercard'
     visa = 'Visa'
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -114,6 +114,7 @@ def extra(self):
         'Operating System :: POSIX :: Linux',
         'Environment :: Console',
         'Environment :: MacOS X',
+        'Framework :: Hypothesis',
         'Topic :: Software Development :: Libraries :: Python Modules',
         'Topic :: Internet',
     ],
@@ -134,4 +135,5 @@ def extra(self):
         'dotenv': ['python-dotenv>=0.10.4'],
     },
     ext_modules=ext_modules,
+    entry_points={'hypothesis': ['_ = pydantic._hypothesis_plugin']},
 )


Test patch:
diff --git a/docs/examples/hypothesis_property_based_test.py b/docs/examples/hypothesis_property_based_test.py
new file mode 100644
--- /dev/null
+++ b/docs/examples/hypothesis_property_based_test.py
@@ -0,0 +1,24 @@
+import typing
+from hypothesis import given, strategies as st
+from pydantic import BaseModel, EmailStr, PaymentCardNumber, PositiveFloat
+
+
+class Model(BaseModel):
+    card: PaymentCardNumber
+    price: PositiveFloat
+    users: typing.List[EmailStr]
+
+
+@given(st.builds(Model))
+def test_property(instance):
+    # Hypothesis calls this test function many times with varied Models,
+    # so you can write a test that should pass given *any* instance.
+    assert 0 < instance.price
+    assert all('@' in email for email in instance.users)
+
+
+@given(st.builds(Model, price=st.floats(100, 200)))
+def test_with_discount(instance):
+    # This test shows how you can override specific fields,
+    # and let Hypothesis fill in any you don't care about.
+    assert 100 <= instance.price <= 200
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,6 +9,9 @@
 import pytest
 from _pytest.assertion.rewrite import AssertionRewritingHook
 
+# See https://hypothesis.readthedocs.io/en/latest/strategies.html#interaction-with-pytest-cov
+pytest_plugins = ['hypothesis.extra.pytestplugin']
+
 
 def _extract_source_code_from_function(function):
     if function.__code__.co_argcount:
diff --git a/tests/requirements-linting.txt b/tests/requirements-linting.txt
--- a/tests/requirements-linting.txt
+++ b/tests/requirements-linting.txt
@@ -1,6 +1,7 @@
 black==20.8b1
 flake8==3.8.4
 flake8-quotes==3.2.0
+hypothesis==5.44.0
 isort==5.7.0
 mypy==0.800
 pycodestyle==2.6.0
diff --git a/tests/requirements-testing.txt b/tests/requirements-testing.txt
--- a/tests/requirements-testing.txt
+++ b/tests/requirements-testing.txt
@@ -1,4 +1,5 @@
 coverage==5.4
+hypothesis==5.44.0
 # pin importlib-metadata as upper versions need typing-extensions to work if on python < 3.8
 importlib-metadata==3.1.0;python_version<"3.8"
 mypy==0.800
diff --git a/tests/test_hypothesis_plugin.py b/tests/test_hypothesis_plugin.py
new file mode 100644
--- /dev/null
+++ b/tests/test_hypothesis_plugin.py
@@ -0,0 +1,108 @@
+import typing
+
+import pytest
+from hypothesis import given, strategies as st
+
+import pydantic
+from pydantic.networks import import_email_validator
+
+
+def gen_models():
+    class MiscModel(pydantic.BaseModel):
+        # Each of these models contains a few related fields; the idea is that
+        # if there's a bug we have neither too many fields to dig through nor
+        # too many models to read.
+        obj: pydantic.PyObject
+        color: pydantic.color.Color
+        json_any: pydantic.Json
+
+    class StringsModel(pydantic.BaseModel):
+        card: pydantic.PaymentCardNumber
+        secbytes: pydantic.SecretBytes
+        secstr: pydantic.SecretStr
+
+    class UUIDsModel(pydantic.BaseModel):
+        uuid1: pydantic.UUID1
+        uuid3: pydantic.UUID3
+        uuid4: pydantic.UUID4
+        uuid5: pydantic.UUID5
+
+    class IPvAnyAddress(pydantic.BaseModel):
+        address: pydantic.IPvAnyAddress
+
+    class IPvAnyInterface(pydantic.BaseModel):
+        interface: pydantic.IPvAnyInterface
+
+    class IPvAnyNetwork(pydantic.BaseModel):
+        network: pydantic.IPvAnyNetwork
+
+    class StrictNumbersModel(pydantic.BaseModel):
+        strictbool: pydantic.StrictBool
+        strictint: pydantic.StrictInt
+        strictfloat: pydantic.StrictFloat
+        strictstr: pydantic.StrictStr
+
+    class NumbersModel(pydantic.BaseModel):
+        posint: pydantic.PositiveInt
+        negint: pydantic.NegativeInt
+        posfloat: pydantic.PositiveFloat
+        negfloat: pydantic.NegativeFloat
+        nonposint: pydantic.NonPositiveInt
+        nonnegint: pydantic.NonNegativeInt
+        nonposfloat: pydantic.NonPositiveFloat
+        nonnegfloat: pydantic.NonNegativeFloat
+
+    class JsonModel(pydantic.BaseModel):
+        json_any: pydantic.Json
+        json_int: pydantic.Json[int]
+        json_float: pydantic.Json[float]
+        json_str: pydantic.Json[str]
+        json_int_or_str: pydantic.Json[typing.Union[int, str]]
+        json_list_of_float: pydantic.Json[typing.List[float]]
+
+    class ConstrainedNumbersModel(pydantic.BaseModel):
+        conintt: pydantic.conint(gt=10, lt=100)
+        coninte: pydantic.conint(ge=10, le=100)
+        conintmul: pydantic.conint(ge=10, le=100, multiple_of=7)
+        confloatt: pydantic.confloat(gt=10, lt=100)
+        confloate: pydantic.confloat(ge=10, le=100)
+        condecimalt: pydantic.condecimal(gt=10, lt=100)
+        condecimale: pydantic.condecimal(ge=10, le=100)
+
+    yield from (
+        MiscModel,
+        StringsModel,
+        UUIDsModel,
+        IPvAnyAddress,
+        IPvAnyInterface,
+        IPvAnyNetwork,
+        StrictNumbersModel,
+        NumbersModel,
+        JsonModel,
+        ConstrainedNumbersModel,
+    )
+
+    try:
+        import_email_validator()
+    except ImportError:
+        pass
+    else:
+
+        class EmailsModel(pydantic.BaseModel):
+            email: pydantic.EmailStr
+            name_email: pydantic.NameEmail
+
+        yield EmailsModel
+
+
+@pytest.mark.parametrize('model', gen_models())
+@given(data=st.data())
+def test_can_construct_models_with_all_fields(data, model):
+    # The value of this test is to confirm that Hypothesis knows how to provide
+    # valid values for each field - otherwise, this would raise ValidationError.
+    instance = data.draw(st.from_type(model))
+
+    # We additionally check that the instance really is of type `model`, because
+    # an evil implementation could avoid ValidationError by means of e.g.
+    # `st.register_type_strategy(model, st.none())`, skipping the constructor.
+    assert isinstance(instance, model)