1# This file is part of Hypothesis, which may be found at
2# https://github.com/HypothesisWorks/hypothesis/
3#
4# Copyright the Hypothesis Authors.
5# Individual contributors are listed in AUTHORS.rst and the git log.
6#
7# This Source Code Form is subject to the terms of the Mozilla Public License,
8# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9# obtain one at https://mozilla.org/MPL/2.0/.
10
11import decimal
12import math
13from numbers import Rational, Real
14from typing import Union
15
16from hypothesis.errors import InvalidArgument
17from hypothesis.internal.coverage import check_function
18
19
20@check_function
21def check_type(typ: Union[type, tuple[type, ...]], arg: object, name: str) -> None:
22 if not isinstance(arg, typ):
23 if isinstance(typ, tuple):
24 assert len(typ) >= 2, "Use bare type instead of len-1 tuple"
25 typ_string = "one of " + ", ".join(t.__name__ for t in typ)
26 else:
27 typ_string = typ.__name__
28
29 if typ_string == "SearchStrategy":
30 from hypothesis.strategies import SearchStrategy
31
32 # Use hypothesis.strategies._internal.strategies.check_strategy
33 # instead, as it has some helpful "did you mean..." logic.
34 assert typ is not SearchStrategy, "use check_strategy instead"
35
36 raise InvalidArgument(
37 f"Expected {typ_string} but got {name}={arg!r} (type={type(arg).__name__})"
38 )
39
40
41@check_function
42def check_valid_integer(value, name):
43 """Checks that value is either unspecified, or a valid integer.
44
45 Otherwise raises InvalidArgument.
46 """
47 if value is None:
48 return
49 check_type(int, value, name)
50
51
52@check_function
53def check_valid_bound(value, name):
54 """Checks that value is either unspecified, or a valid interval bound.
55
56 Otherwise raises InvalidArgument.
57 """
58 if value is None or isinstance(value, (int, Rational)):
59 return
60 if not isinstance(value, (Real, decimal.Decimal)):
61 raise InvalidArgument(f"{name}={value!r} must be a real number.")
62 if math.isnan(value):
63 raise InvalidArgument(f"Invalid end point {name}={value!r}")
64
65
66@check_function
67def check_valid_magnitude(value, name):
68 """Checks that value is either unspecified, or a non-negative valid
69 interval bound.
70
71 Otherwise raises InvalidArgument.
72 """
73 check_valid_bound(value, name)
74 if value is not None and value < 0:
75 raise InvalidArgument(f"{name}={value!r} must not be negative.")
76 elif value is None and name == "min_magnitude":
77 raise InvalidArgument("Use min_magnitude=0 or omit the argument entirely.")
78
79
80@check_function
81def try_convert(typ, value, name):
82 if value is None:
83 return None
84 if isinstance(value, typ):
85 return value
86 try:
87 return typ(value)
88 except (TypeError, ValueError, ArithmeticError) as err:
89 raise InvalidArgument(
90 f"Cannot convert {name}={value!r} of type "
91 f"{type(value).__name__} to type {typ.__name__}"
92 ) from err
93
94
95@check_function
96def check_valid_size(value, name):
97 """Checks that value is either unspecified, or a valid non-negative size
98 expressed as an integer.
99
100 Otherwise raises InvalidArgument.
101 """
102 if value is None and name not in ("min_size", "size"):
103 return
104 check_type(int, value, name)
105 if value < 0:
106 raise InvalidArgument(f"Invalid size {name}={value!r} < 0")
107
108
109@check_function
110def check_valid_interval(lower_bound, upper_bound, lower_name, upper_name):
111 """Checks that lower_bound and upper_bound are either unspecified, or they
112 define a valid interval on the number line.
113
114 Otherwise raises InvalidArgument.
115 """
116 if lower_bound is None or upper_bound is None:
117 return
118 if upper_bound < lower_bound:
119 raise InvalidArgument(
120 f"Cannot have {upper_name}={upper_bound!r} < {lower_name}={lower_bound!r}"
121 )
122
123
124@check_function
125def check_valid_sizes(min_size, max_size):
126 check_valid_size(min_size, "min_size")
127 check_valid_size(max_size, "max_size")
128 check_valid_interval(min_size, max_size, "min_size", "max_size")