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
14
15from hypothesis.errors import InvalidArgument
16from hypothesis.internal.coverage import check_function
17
18
19@check_function
20def check_type(typ, arg, name):
21 if not isinstance(arg, typ):
22 if isinstance(typ, tuple):
23 assert len(typ) >= 2, "Use bare type instead of len-1 tuple"
24 typ_string = "one of " + ", ".join(t.__name__ for t in typ)
25 else:
26 typ_string = typ.__name__
27
28 if typ_string == "SearchStrategy":
29 from hypothesis.strategies import SearchStrategy
30
31 # Use hypothesis.strategies._internal.strategies.check_strategy
32 # instead, as it has some helpful "did you mean..." logic.
33 assert typ is not SearchStrategy, "use check_strategy instead"
34
35 raise InvalidArgument(
36 f"Expected {typ_string} but got {name}={arg!r} (type={type(arg).__name__})"
37 )
38
39
40@check_function
41def check_valid_integer(value, name):
42 """Checks that value is either unspecified, or a valid integer.
43
44 Otherwise raises InvalidArgument.
45 """
46 if value is None:
47 return
48 check_type(int, value, name)
49
50
51@check_function
52def check_valid_bound(value, name):
53 """Checks that value is either unspecified, or a valid interval bound.
54
55 Otherwise raises InvalidArgument.
56 """
57 if value is None or isinstance(value, (int, Rational)):
58 return
59 if not isinstance(value, (Real, decimal.Decimal)):
60 raise InvalidArgument(f"{name}={value!r} must be a real number.")
61 if math.isnan(value):
62 raise InvalidArgument(f"Invalid end point {name}={value!r}")
63
64
65@check_function
66def check_valid_magnitude(value, name):
67 """Checks that value is either unspecified, or a non-negative valid
68 interval bound.
69
70 Otherwise raises InvalidArgument.
71 """
72 check_valid_bound(value, name)
73 if value is not None and value < 0:
74 raise InvalidArgument(f"{name}={value!r} must not be negative.")
75 elif value is None and name == "min_magnitude":
76 raise InvalidArgument("Use min_magnitude=0 or omit the argument entirely.")
77
78
79@check_function
80def try_convert(typ, value, name):
81 if value is None:
82 return None
83 if isinstance(value, typ):
84 return value
85 try:
86 return typ(value)
87 except (TypeError, ValueError, ArithmeticError) as err:
88 raise InvalidArgument(
89 f"Cannot convert {name}={value!r} of type "
90 f"{type(value).__name__} to type {typ.__name__}"
91 ) from err
92
93
94@check_function
95def check_valid_size(value, name):
96 """Checks that value is either unspecified, or a valid non-negative size
97 expressed as an integer.
98
99 Otherwise raises InvalidArgument.
100 """
101 if value is None and name not in ("min_size", "size"):
102 return
103 check_type(int, value, name)
104 if value < 0:
105 raise InvalidArgument(f"Invalid size {name}={value!r} < 0")
106
107
108@check_function
109def check_valid_interval(lower_bound, upper_bound, lower_name, upper_name):
110 """Checks that lower_bound and upper_bound are either unspecified, or they
111 define a valid interval on the number line.
112
113 Otherwise raises InvalidArgument.
114 """
115 if lower_bound is None or upper_bound is None:
116 return
117 if upper_bound < lower_bound:
118 raise InvalidArgument(
119 f"Cannot have {upper_name}={upper_bound!r} < {lower_name}={lower_bound!r}"
120 )
121
122
123@check_function
124def check_valid_sizes(min_size, max_size):
125 check_valid_size(min_size, "min_size")
126 check_valid_size(max_size, "max_size")
127 check_valid_interval(min_size, max_size, "min_size", "max_size")