hypothesis 6.135.7__py3-none-any.whl → 6.135.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hypothesis/extra/_patching.py +90 -35
- hypothesis/internal/constants_ast.py +5 -1
- hypothesis/version.py +1 -1
- {hypothesis-6.135.7.dist-info → hypothesis-6.135.9.dist-info}/METADATA +1 -1
- {hypothesis-6.135.7.dist-info → hypothesis-6.135.9.dist-info}/RECORD +9 -9
- {hypothesis-6.135.7.dist-info → hypothesis-6.135.9.dist-info}/WHEEL +0 -0
- {hypothesis-6.135.7.dist-info → hypothesis-6.135.9.dist-info}/entry_points.txt +0 -0
- {hypothesis-6.135.7.dist-info → hypothesis-6.135.9.dist-info}/licenses/LICENSE.txt +0 -0
- {hypothesis-6.135.7.dist-info → hypothesis-6.135.9.dist-info}/top_level.txt +0 -0
hypothesis/extra/_patching.py
CHANGED
@@ -26,9 +26,11 @@ import re
|
|
26
26
|
import sys
|
27
27
|
import types
|
28
28
|
from ast import literal_eval
|
29
|
+
from collections.abc import Sequence
|
29
30
|
from contextlib import suppress
|
30
31
|
from datetime import date, datetime, timedelta, timezone
|
31
32
|
from pathlib import Path
|
33
|
+
from typing import Any, Optional
|
32
34
|
|
33
35
|
import libcst as cst
|
34
36
|
from libcst import matchers as m
|
@@ -43,11 +45,11 @@ try:
|
|
43
45
|
except ImportError:
|
44
46
|
black = None # type: ignore
|
45
47
|
|
46
|
-
HEADER =
|
48
|
+
HEADER = """\
|
47
49
|
From HEAD Mon Sep 17 00:00:00 2001
|
48
|
-
From:
|
49
|
-
Date: {
|
50
|
-
Subject: [PATCH] {
|
50
|
+
From: {author}
|
51
|
+
Date: {when:%a, %d %b %Y %H:%M:%S}
|
52
|
+
Subject: [PATCH] {msg}
|
51
53
|
|
52
54
|
---
|
53
55
|
"""
|
@@ -56,7 +58,7 @@ _space_only_re = re.compile("^ +$", re.MULTILINE)
|
|
56
58
|
_leading_space_re = re.compile("(^[ ]*)(?:[^ \n])", re.MULTILINE)
|
57
59
|
|
58
60
|
|
59
|
-
def dedent(text):
|
61
|
+
def dedent(text: str) -> tuple[str, str]:
|
60
62
|
# Simplified textwrap.dedent, for valid Python source code only
|
61
63
|
text = _space_only_re.sub("", text)
|
62
64
|
prefix = min(_leading_space_re.findall(text), key=len)
|
@@ -70,7 +72,14 @@ def indent(text: str, prefix: str) -> str:
|
|
70
72
|
class AddExamplesCodemod(VisitorBasedCodemodCommand):
|
71
73
|
DESCRIPTION = "Add explicit examples to failing tests."
|
72
74
|
|
73
|
-
def __init__(
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
context: CodemodContext,
|
78
|
+
fn_examples: dict[str, list[tuple[cst.Call, str]]],
|
79
|
+
strip_via: tuple[str, ...] = (),
|
80
|
+
decorator: str = "example",
|
81
|
+
width: int = 88,
|
82
|
+
):
|
74
83
|
"""Add @example() decorator(s) for failing test(s).
|
75
84
|
|
76
85
|
`code` is the source code of the module where the test functions are defined.
|
@@ -79,9 +88,11 @@ class AddExamplesCodemod(VisitorBasedCodemodCommand):
|
|
79
88
|
assert fn_examples, "This codemod does nothing without fn_examples."
|
80
89
|
super().__init__(context)
|
81
90
|
|
82
|
-
self.decorator_func = cst.parse_expression(
|
91
|
+
self.decorator_func = cst.parse_expression(decorator)
|
83
92
|
self.line_length = width
|
84
|
-
value_in_strip_via = m.MatchIfTrue(
|
93
|
+
value_in_strip_via: Any = m.MatchIfTrue(
|
94
|
+
lambda x: literal_eval(x.value) in strip_via
|
95
|
+
)
|
85
96
|
self.strip_matching = m.Call(
|
86
97
|
m.Attribute(m.Call(), m.Name("via")),
|
87
98
|
[m.Arg(m.SimpleString() & value_in_strip_via)],
|
@@ -89,11 +100,17 @@ class AddExamplesCodemod(VisitorBasedCodemodCommand):
|
|
89
100
|
|
90
101
|
# Codemod the failing examples to Call nodes usable as decorators
|
91
102
|
self.fn_examples = {
|
92
|
-
k: tuple(
|
103
|
+
k: tuple(
|
104
|
+
d
|
105
|
+
for (node, via) in nodes
|
106
|
+
if (d := self.__call_node_to_example_dec(node, via))
|
107
|
+
)
|
93
108
|
for k, nodes in fn_examples.items()
|
94
109
|
}
|
95
110
|
|
96
|
-
def __call_node_to_example_dec(
|
111
|
+
def __call_node_to_example_dec(
|
112
|
+
self, node: cst.Call, via: str
|
113
|
+
) -> Optional[cst.Decorator]:
|
97
114
|
# If we have black installed, remove trailing comma, _unless_ there's a comment
|
98
115
|
node = node.with_changes(
|
99
116
|
func=self.decorator_func,
|
@@ -112,7 +129,7 @@ class AddExamplesCodemod(VisitorBasedCodemodCommand):
|
|
112
129
|
else node.args
|
113
130
|
),
|
114
131
|
)
|
115
|
-
via = cst.Call(
|
132
|
+
via: cst.BaseExpression = cst.Call(
|
116
133
|
func=cst.Attribute(node, cst.Name("via")),
|
117
134
|
args=[cst.Arg(cst.SimpleString(repr(via)))],
|
118
135
|
)
|
@@ -127,7 +144,9 @@ class AddExamplesCodemod(VisitorBasedCodemodCommand):
|
|
127
144
|
via = cst.parse_expression(pretty.strip())
|
128
145
|
return cst.Decorator(via)
|
129
146
|
|
130
|
-
def leave_FunctionDef(
|
147
|
+
def leave_FunctionDef(
|
148
|
+
self, _original_node: cst.FunctionDef, updated_node: cst.FunctionDef
|
149
|
+
) -> cst.FunctionDef:
|
131
150
|
return updated_node.with_changes(
|
132
151
|
# TODO: improve logic for where in the list to insert this decorator
|
133
152
|
decorators=tuple(
|
@@ -140,30 +159,59 @@ class AddExamplesCodemod(VisitorBasedCodemodCommand):
|
|
140
159
|
)
|
141
160
|
|
142
161
|
|
143
|
-
def get_patch_for(
|
144
|
-
|
162
|
+
def get_patch_for(
|
163
|
+
func: Any,
|
164
|
+
examples: Sequence[tuple[str, str]],
|
165
|
+
*,
|
166
|
+
strip_via: tuple[str, ...] = (),
|
167
|
+
) -> Optional[tuple[str, str, str]]:
|
168
|
+
# Skip this if we're unable to find the location of this function.
|
145
169
|
try:
|
146
170
|
module = sys.modules[func.__module__]
|
147
|
-
|
148
|
-
before = inspect.getsource(func)
|
171
|
+
file_path = Path(module.__file__) # type: ignore
|
149
172
|
except Exception:
|
150
173
|
return None
|
151
174
|
|
175
|
+
fname = (
|
176
|
+
file_path.relative_to(Path.cwd())
|
177
|
+
if file_path.is_relative_to(Path.cwd())
|
178
|
+
else file_path
|
179
|
+
)
|
180
|
+
patch = _get_patch_for(
|
181
|
+
func, examples, strip_via=strip_via, namespace=module.__dict__
|
182
|
+
)
|
183
|
+
if patch is None:
|
184
|
+
return None
|
185
|
+
|
186
|
+
(before, after) = patch
|
187
|
+
return (str(fname), before, after)
|
188
|
+
|
189
|
+
|
190
|
+
# split out for easier testing of patches in hypofuzz, where the function to
|
191
|
+
# apply the patch to may not be loaded in sys.modules.
|
192
|
+
def _get_patch_for(
|
193
|
+
func: Any,
|
194
|
+
examples: Sequence[tuple[str, str]],
|
195
|
+
*,
|
196
|
+
strip_via: tuple[str, ...] = (),
|
197
|
+
namespace: dict[str, Any],
|
198
|
+
) -> Optional[tuple[str, str]]:
|
199
|
+
try:
|
200
|
+
before = inspect.getsource(func)
|
201
|
+
except Exception: # pragma: no cover
|
202
|
+
return None
|
203
|
+
|
152
204
|
modules_in_test_scope = sorted(
|
153
|
-
(
|
154
|
-
(k, v)
|
155
|
-
for (k, v) in module.__dict__.items()
|
156
|
-
if isinstance(v, types.ModuleType)
|
157
|
-
),
|
205
|
+
((k, v) for (k, v) in namespace.items() if isinstance(v, types.ModuleType)),
|
158
206
|
key=lambda kv: len(kv[1].__name__),
|
159
207
|
)
|
160
208
|
|
161
209
|
# The printed examples might include object reprs which are invalid syntax,
|
162
210
|
# so we parse here and skip over those. If _none_ are valid, there's no patch.
|
163
|
-
call_nodes = []
|
164
|
-
for ex, via in set(
|
211
|
+
call_nodes: list[tuple[cst.Call, str]] = []
|
212
|
+
for ex, via in set(examples):
|
165
213
|
with suppress(Exception):
|
166
|
-
node = cst.parse_module(ex)
|
214
|
+
node: Any = cst.parse_module(ex)
|
167
215
|
the_call = node.body[0].body[0].value
|
168
216
|
assert isinstance(the_call, cst.Call), the_call
|
169
217
|
# Check for st.data(), which doesn't support explicit examples
|
@@ -180,7 +228,7 @@ def get_patch_for(func, failing_examples, *, strip_via=()):
|
|
180
228
|
isinstance(anode, ast.Name)
|
181
229
|
and isinstance(anode.ctx, ast.Load)
|
182
230
|
and anode.id not in names
|
183
|
-
and anode.id not in
|
231
|
+
and anode.id not in namespace
|
184
232
|
):
|
185
233
|
for k, v in modules_in_test_scope:
|
186
234
|
if anode.id in v.__dict__:
|
@@ -194,14 +242,15 @@ def get_patch_for(func, failing_examples, *, strip_via=()):
|
|
194
242
|
with suppress(Exception):
|
195
243
|
wrapper = cst.metadata.MetadataWrapper(node)
|
196
244
|
kwarg_names = {
|
197
|
-
|
245
|
+
node.keyword # type: ignore
|
246
|
+
for node in m.findall(wrapper, m.Arg(keyword=m.Name()))
|
198
247
|
}
|
199
248
|
node = m.replace(
|
200
249
|
wrapper,
|
201
250
|
m.Name(value=m.MatchIfTrue(names.__contains__))
|
202
251
|
& m.MatchMetadata(ExpressionContextProvider, ExpressionContext.LOAD)
|
203
|
-
& m.MatchIfTrue(lambda n, k=kwarg_names: n not in k),
|
204
|
-
replacement=lambda node, _, ns=names: ns[node.value],
|
252
|
+
& m.MatchIfTrue(lambda n, k=kwarg_names: n not in k), # type: ignore
|
253
|
+
replacement=lambda node, _, ns=names: ns[node.value], # type: ignore
|
205
254
|
)
|
206
255
|
node = node.body[0].body[0].value
|
207
256
|
assert isinstance(node, cst.Call), node
|
@@ -211,8 +260,8 @@ def get_patch_for(func, failing_examples, *, strip_via=()):
|
|
211
260
|
return None
|
212
261
|
|
213
262
|
if (
|
214
|
-
|
215
|
-
and "given" not in
|
263
|
+
namespace.get("hypothesis") is sys.modules["hypothesis"]
|
264
|
+
and "given" not in namespace # more reliably present than `example`
|
216
265
|
):
|
217
266
|
decorator_func = "hypothesis.example"
|
218
267
|
else:
|
@@ -229,22 +278,28 @@ def get_patch_for(func, failing_examples, *, strip_via=()):
|
|
229
278
|
CodemodContext(),
|
230
279
|
fn_examples={func.__name__: call_nodes},
|
231
280
|
strip_via=strip_via,
|
232
|
-
|
281
|
+
decorator=decorator_func,
|
233
282
|
width=88 - len(prefix), # to match Black's default formatting
|
234
283
|
).transform_module(node)
|
235
|
-
return (
|
284
|
+
return (before, indent(after.code, prefix=prefix))
|
236
285
|
|
237
286
|
|
238
|
-
def make_patch(
|
287
|
+
def make_patch(
|
288
|
+
triples: Sequence[tuple[str, str, str]],
|
289
|
+
*,
|
290
|
+
msg: str = "Hypothesis: add explicit examples",
|
291
|
+
when: Optional[datetime] = None,
|
292
|
+
author: str = f"Hypothesis {__version__} <no-reply@hypothesis.works>",
|
293
|
+
) -> str:
|
239
294
|
"""Create a patch for (fname, before, after) triples."""
|
240
295
|
assert triples, "attempted to create empty patch"
|
241
296
|
when = when or datetime.now(tz=timezone.utc)
|
242
297
|
|
243
|
-
by_fname = {}
|
298
|
+
by_fname: dict[Path, list[tuple[str, str]]] = {}
|
244
299
|
for fname, before, after in triples:
|
245
300
|
by_fname.setdefault(Path(fname), []).append((before, after))
|
246
301
|
|
247
|
-
diffs = [HEADER.format(msg=msg, when=when)]
|
302
|
+
diffs = [HEADER.format(msg=msg, when=when, author=author)]
|
248
303
|
for fname, changes in sorted(by_fname.items()):
|
249
304
|
source_before = source_after = fname.read_text(encoding="utf-8")
|
250
305
|
for before, after in changes:
|
@@ -192,6 +192,10 @@ def _constants_from_source(source: Union[str, bytes], *, limit: bool) -> Constan
|
|
192
192
|
return visitor.constants
|
193
193
|
|
194
194
|
|
195
|
+
def _constants_file_str(constants: Constants) -> str:
|
196
|
+
return str(sorted(constants, key=lambda v: (str(type(v)), v)))
|
197
|
+
|
198
|
+
|
195
199
|
@lru_cache(4096)
|
196
200
|
def constants_from_module(module: ModuleType, *, limit: bool = True) -> Constants:
|
197
201
|
try:
|
@@ -236,7 +240,7 @@ def constants_from_module(module: ModuleType, *, limit: bool = True) -> Constant
|
|
236
240
|
# somewhat arbitrary sort order. The cache file doesn't *have* to be
|
237
241
|
# stable... but it is aesthetically pleasing, and means we could rely
|
238
242
|
# on it in the future!
|
239
|
-
+
|
243
|
+
+ _constants_file_str(constants),
|
240
244
|
encoding="utf-8",
|
241
245
|
)
|
242
246
|
except Exception: # pragma: no cover
|
hypothesis/version.py
CHANGED
@@ -14,10 +14,10 @@ hypothesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
hypothesis/reporting.py,sha256=f-jhl1JfAi5_tG8dsUd2qDjGcPdvxEzfF6hXmpTFQ1g,1761
|
15
15
|
hypothesis/stateful.py,sha256=vQ8wDO7YW-8nGlBGLfR9ariwmRCS9jxJy-Ky3swXjDE,42861
|
16
16
|
hypothesis/statistics.py,sha256=kZ5mc0fAg7gnSO6EmDo82fyz8DYhIiJ_mHe7srxOeQ0,5438
|
17
|
-
hypothesis/version.py,sha256=
|
17
|
+
hypothesis/version.py,sha256=2cgiH3RVZ2YHrP3mS9FXI4dZXTKhr9HPeF4V-9yoL_A,498
|
18
18
|
hypothesis/extra/__init__.py,sha256=gx4ENVDkrzBxy5Lv3Iyfs3tvMGdWMbiHfi95B7t61CY,415
|
19
19
|
hypothesis/extra/_array_helpers.py,sha256=PLmFckBfQpzQ4Q3dFJQqMmrbm7Qdvqxf1t9LHDCuSp0,27627
|
20
|
-
hypothesis/extra/_patching.py,sha256=
|
20
|
+
hypothesis/extra/_patching.py,sha256=A5s5EAf81itr--w4SAFyzuecSZm4eT397jM7BvbnQXU,12385
|
21
21
|
hypothesis/extra/array_api.py,sha256=bv_wisQS56wvwg-LiC26DxvzmPxcNLzlbzmltFWZ_Jo,42609
|
22
22
|
hypothesis/extra/cli.py,sha256=HOH_BGosyUvS4yNDsWF4Dfe672tEFGT4BBPBuZamm1s,12885
|
23
23
|
hypothesis/extra/codemods.py,sha256=i_iM5aSnrNjSZ_uNW40yShxXo7qaXuCrDHy8OznOa7o,11224
|
@@ -39,7 +39,7 @@ hypothesis/internal/cache.py,sha256=5GJ92S7HNjefuiOOSUchp3IYw1XxLVRmYoFHEoL7eVk,
|
|
39
39
|
hypothesis/internal/cathetus.py,sha256=q6t16mT2jzlJmBddhRlqC2wg_w7FggVDRAVkzs04W-U,2258
|
40
40
|
hypothesis/internal/charmap.py,sha256=dIsRCxa6r1r-rF_NUy04Z-afFa2b6fCFEjWTMYsAgXY,11781
|
41
41
|
hypothesis/internal/compat.py,sha256=CexhnEVndODX3E-iJmBzt_svqLmADF_6vWkd7lXLRQI,10970
|
42
|
-
hypothesis/internal/constants_ast.py,sha256=
|
42
|
+
hypothesis/internal/constants_ast.py,sha256=VcItL6O6LbNd-NpunBuonKE9rLNVVxNlOfTDY8Lqci0,10189
|
43
43
|
hypothesis/internal/coverage.py,sha256=u2ALnaYgwd2jSkZkA5aQYA1uMnk7gwaasMJbq_l3iIg,3380
|
44
44
|
hypothesis/internal/detection.py,sha256=v_zf0GYsnfJKQMNw78qYv61Dh-YaXXfgqKpVIOsBvfw,1228
|
45
45
|
hypothesis/internal/entropy.py,sha256=wgNeddIM0I3SD1yydz12Lsop6Di5vsle4e9u5fWAxIs,8014
|
@@ -105,9 +105,9 @@ hypothesis/utils/terminal.py,sha256=IxGYDGaE4R3b_vMfz5buWbN18XH5qVP4IxqAgNAU5as,
|
|
105
105
|
hypothesis/vendor/__init__.py,sha256=gx4ENVDkrzBxy5Lv3Iyfs3tvMGdWMbiHfi95B7t61CY,415
|
106
106
|
hypothesis/vendor/pretty.py,sha256=WEZC-UV-QQgCjUf2Iz1WWaWnbgT7Hc3s-GWEVxq-Qz0,36114
|
107
107
|
hypothesis/vendor/tlds-alpha-by-domain.txt,sha256=W9hYvpu2BMmNgE-SfPp8-GTzEVjw0HJUviqlvHwpZu8,9588
|
108
|
-
hypothesis-6.135.
|
109
|
-
hypothesis-6.135.
|
110
|
-
hypothesis-6.135.
|
111
|
-
hypothesis-6.135.
|
112
|
-
hypothesis-6.135.
|
113
|
-
hypothesis-6.135.
|
108
|
+
hypothesis-6.135.9.dist-info/licenses/LICENSE.txt,sha256=rIkDe6xjVQZE3OjPMsZ2Xl-rncGhzpS4n4qAXzQaZ1A,17141
|
109
|
+
hypothesis-6.135.9.dist-info/METADATA,sha256=Y5tNxfqmY2oL2NkcTrxP3ytDT-iZnoZT5kfz1XIUKa8,5637
|
110
|
+
hypothesis-6.135.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
111
|
+
hypothesis-6.135.9.dist-info/entry_points.txt,sha256=JDoUs9w1bYme7aG_eJ1cCtstRTWD71BzG8iRi-G2eHE,113
|
112
|
+
hypothesis-6.135.9.dist-info/top_level.txt,sha256=ReGreaueiJ4d1I2kEiig_CLeA0sD4QCQ4qk_8kH1oDc,81
|
113
|
+
hypothesis-6.135.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|