Repository: pydantic/pydantic

Issue:
remove currently hypothesis code



Discussion:

Resolving patch:
diff --git a/pydantic/_hypothesis_plugin.py b/pydantic/_hypothesis_plugin.py
deleted file mode 100644
--- a/pydantic/_hypothesis_plugin.py
+++ /dev/null
@@ -1,387 +0,0 @@
-# type: ignore
-"""
-THIS IS A MESS AND NEEDS TO BE REWRITTEN, SORRY ZAC.
-
-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://docs.pydantic.dev/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 datetime
-import ipaddress
-import json
-import math
-from fractions import Fraction
-from typing import Callable, Dict, Type, TypeVar, Union, overload
-
-import hypothesis.strategies as st
-
-import pydantic
-import pydantic.color
-import pydantic.types
-
-from ._internal._utils import lenient_issubclass
-
-# 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:  # pragma: no cover
-            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,  # type: ignore[arg-type]
-#     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_hsl,
-        )
-    )
-    # 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))
-# st.register_type_strategy(pydantic.UUID3, st.uuids(version=3))
-# st.register_type_strategy(pydantic.UUID4, st.uuids(version=4))
-# st.register_type_strategy(pydantic.UUID5, st.uuids(version=5))
-
-# 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())  # type: ignore[arg-type]
-st.register_type_strategy(
-    pydantic.IPvAnyInterface,
-    st.from_type(ipaddress.IPv4Interface) | st.from_type(ipaddress.IPv6Interface),  # type: ignore[arg-type]
-)
-st.register_type_strategy(
-    pydantic.IPvAnyNetwork,
-    st.from_type(ipaddress.IPv4Network) | st.from_type(ipaddress.IPv6Network),  # type: ignore[arg-type]
-)
-
-# 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())
-
-
-# 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]
-T = TypeVar('T')
-
-
-@overload
-def _registered(typ: Type[T]) -> Type[T]:
-    pass
-
-
-@overload
-def _registered(typ: pydantic.types) -> pydantic.types:
-    pass
-
-
-def _registered(typ: Union[Type[T]]) -> Union[Type[T]]:
-    # 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))
-            return typ
-    raise NotImplementedError(f'Unknown type {typ!r} has no resolver to register')  # pragma: no cover
-
-
-def resolves(
-    typ: Union[type],
-) -> 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.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:  # pragma: no cover
-        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),  # type: ignore
-        )
-    inner_type = getattr(cls, 'inner_type', None)
-    return st.builds(
-        cls.inner_type.json if lenient_issubclass(inner_type, pydantic.BaseModel) else 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, places=cls.decimal_places)
-    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
-
-    if cls.multiple_of is None:
-        return st.floats(min_value, max_value, exclude_min=exclude_min, exclude_max=exclude_max, allow_nan=False)
-
-    if min_value is not None:
-        min_value = math.ceil(min_value / cls.multiple_of)
-        if exclude_min:
-            min_value = min_value + 1
-    if max_value is not None:
-        assert max_value >= cls.multiple_of, 'Cannot build model with max value smaller than multiple of'
-        max_value = math.floor(max_value / cls.multiple_of)
-        if exclude_max:
-            max_value = max_value - 1
-
-    return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of)
-
-
-# @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.ConstrainedDate)
-def resolve_condate(cls):  # type: ignore[no-untyped-def]
-    if cls.ge is not None:
-        assert cls.gt is None, 'Set `gt` or `ge`, but not both'
-        min_value = cls.ge
-    elif cls.gt is not None:
-        min_value = cls.gt + datetime.timedelta(days=1)
-    else:
-        min_value = datetime.date.min
-    if cls.le is not None:
-        assert cls.lt is None, 'Set `lt` or `le`, but not both'
-        max_value = cls.le
-    elif cls.lt is not None:
-        max_value = cls.lt - datetime.timedelta(days=1)
-    else:
-        max_value = datetime.date.max
-    return st.dates(min_value, max_value)
-
-
-# @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 list(pydantic.types._DEFINED_TYPES):
-#     _registered(typ)
-pydantic.types._registered = _registered
-st.register_type_strategy(pydantic.Json, resolve_json)
diff --git a/pydantic/types.py b/pydantic/types.py
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -571,8 +571,6 @@ def _display(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'


Test patch:
diff --git a/tests/test_docs.py b/tests/test_docs.py
--- a/tests/test_docs.py
+++ b/tests/test_docs.py
@@ -25,11 +25,6 @@ def skip_docs_tests():
     if platform.python_implementation() != 'CPython':
         return 'not cpython'
 
-    try:
-        import hypothesis  # noqa: F401
-    except ImportError:
-        return 'hypothesis not installed'
-
     try:
         import devtools  # noqa: F401
     except ImportError:
diff --git a/tests/test_hypothesis_plugin.py b/tests/test_hypothesis_plugin.py
deleted file mode 100644
--- a/tests/test_hypothesis_plugin.py
+++ /dev/null
@@ -1,132 +0,0 @@
-import typing
-from datetime import date
-
-import pytest
-
-import pydantic
-from pydantic.networks import import_email_validator
-
-try:
-    from hypothesis import HealthCheck, given, settings
-    from hypothesis import strategies as st
-except ImportError:
-    from unittest import mock
-
-    # pass-through decorator
-    given = settings = lambda *a, **kw: (lambda f: f)
-    HealthCheck = st = mock.Mock()
-
-    pytestmark = pytest.mark.skipif(True, reason='"hypothesis" not installed')
-
-
-def gen_models():
-    # TODO fix and remove this return
-    return
-
-    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.
-        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_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]]
-        json_pydantic_model: pydantic.Json[pydantic.BaseModel]
-
-    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)
-        confloatemul: pydantic.confloat(ge=10, le=100, multiple_of=4.2)
-        confloattmul: pydantic.confloat(gt=10, lt=100, multiple_of=10)
-        condecimalt: pydantic.condecimal(gt=10, lt=100)
-        condecimale: pydantic.condecimal(ge=10, le=100)
-        condecimaltplc: pydantic.condecimal(gt=10, lt=100, decimal_places=5)
-        condecimaleplc: pydantic.condecimal(ge=10, le=100, decimal_places=2)
-
-    class ConstrainedDateModel(pydantic.BaseModel):
-        condatet: pydantic.condate(gt=date(1980, 1, 1), lt=date(2180, 12, 31))
-        condatee: pydantic.condate(ge=date(1980, 1, 1), le=date(2180, 12, 31))
-
-    yield from (
-        MiscModel,
-        StringsModel,
-        UUIDsModel,
-        IPvAnyAddress,
-        IPvAnyInterface,
-        IPvAnyNetwork,
-        StrictNumbersModel,
-        NumbersModel,
-        JsonModel,
-        ConstrainedNumbersModel,
-        ConstrainedDateModel,
-    )
-
-    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())
-@settings(suppress_health_check={HealthCheck.too_slow}, deadline=None)
-@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)