pytest-regtest 2.2.0a2__py2.py3-none-any.whl → 2.2.1__py2.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.
@@ -6,17 +6,18 @@ import re
6
6
  import shutil
7
7
  import sys
8
8
  import tempfile
9
+ from collections.abc import Callable
9
10
  from hashlib import sha512
10
11
  from io import StringIO
12
+ from typing import Optional
11
13
 
14
+ import _pytest
12
15
  import pytest
13
16
  from _pytest._code.code import TerminalRepr
14
17
  from _pytest._io import TerminalWriter
15
18
 
16
- # from .numpy_handler import NumpyHandler # noqa: F401
17
- # from .pandas_handler import DataFrameHandler # noqa: F401
18
19
  from .snapshot_handler import PythonObjectHandler # noqa: F401
19
- from .snapshot_handler import snapshot_handlers
20
+ from .snapshot_handler import SnapshotHandlerRegistry
20
21
 
21
22
  IS_WIN = sys.platform == "win32"
22
23
 
@@ -36,90 +37,90 @@ class RegtestException(Exception):
36
37
  pass
37
38
 
38
39
 
39
- class RecordedOutputException(Exception):
40
+ class RecordedOutputException(RegtestException):
40
41
  pass
41
42
 
42
43
 
43
- class SnapshotException(Exception):
44
+ class SnapshotException(RegtestException):
44
45
  pass
45
46
 
46
47
 
47
- class PytestRegtestPlugin:
48
+ class PytestRegtestCommonHooks:
48
49
  def __init__(self):
50
+ self._reset_snapshots = []
49
51
  self._reset_regtest_outputs = []
52
+ self._failed_snapshots = []
50
53
  self._failed_regtests = []
51
54
 
52
- @pytest.hookimpl(trylast=True)
53
- def pytest_runtest_call(self, item):
54
- if not hasattr(item, "regtest_stream"):
55
- return
56
-
57
- output_exception = self.check_recorded_output(item)
58
- snapshot_exception = self.check_snapshots(item)
59
-
60
- if output_exception is not None or snapshot_exception is not None:
61
- raise RegtestException(output_exception, snapshot_exception)
62
-
63
- if item.get_closest_marker("xfail") and item.config.getvalue("--regtest-reset"):
64
- # enforce fail
65
- assert False
66
-
67
- def check_snapshots(self, item):
68
- results = []
69
-
70
- any_failed = False
71
- for idx, snapshot in enumerate(item.regtest_stream.snapshots):
72
- is_recorded, ok, msg = self.check_snapshot(idx, item, snapshot)
73
- if not ok:
74
- any_failed = True
75
- results.append((ok, snapshot, is_recorded, msg))
76
-
77
- if any_failed:
78
- return SnapshotException(results)
55
+ @pytest.hookimpl(hookwrapper=False)
56
+ def pytest_terminal_summary(self, terminalreporter, exitstatus, config):
57
+ tr = terminalreporter
58
+ tr.ensure_newline()
59
+ tr.section("pytest-regtest report", sep="-", blue=True, bold=True)
60
+ tr.write("total number of failed regression tests: ", bold=True)
61
+ tr.line(str(len(self._failed_regtests)))
62
+ tr.write("total number of failed snapshot tests : ", bold=True)
63
+ tr.line(str(len(self._failed_snapshots)))
79
64
 
80
- def check_snapshot(self, idx, item, snapshot):
81
- obj, handler_options, _ = snapshot
82
- handler = get_snapshot_handler(obj, handler_options, item.config)
65
+ if config.getvalue("--regtest-reset"):
66
+ if config.option.verbose:
67
+ tr.line("the following output files have been reset:", bold=True)
68
+ for path in self._reset_regtest_outputs:
69
+ rel_path = os.path.relpath(path)
70
+ tr.line(" " + rel_path)
71
+ for path in self._reset_snapshots:
72
+ rel_path = os.path.relpath(path)
73
+ tr.line(" " + rel_path)
74
+ else:
75
+ tr.write("total number of reset output files: ", bold=True)
76
+ tr.line(
77
+ str(len(self._reset_regtest_outputs) + len(self._reset_snapshots))
78
+ )
83
79
 
84
- test_folder = item.fspath.dirname
85
- identifier = str(idx)
86
- config = item.config
80
+ @pytest.hookimpl(hookwrapper=True)
81
+ def pytest_pyfunc_call(self, pyfuncitem):
82
+ stdout = sys.stdout
83
+ if "regtest_all" in pyfuncitem.fixturenames and hasattr(
84
+ pyfuncitem, "regtest_stream"
85
+ ):
86
+ sys.stdout = pyfuncitem.regtest_stream
87
+ yield
88
+ sys.stdout = stdout
87
89
 
88
- orig_identifer, recorded_output_path = result_file_paths(
89
- test_folder, item.nodeid, identifier
90
- )
90
+ @pytest.hookimpl(hookwrapper=True)
91
+ def pytest_report_teststatus(self, report, config):
92
+ outcome = yield
93
+ if report.when == "call" and "uses-regtest" in report.keywords:
94
+ if config.getvalue("--regtest-reset"):
95
+ result = outcome.get_result()
96
+ if result[0] != "failed":
97
+ outcome.force_result((result[0], "R", "RESET"))
91
98
 
92
- reset = config.getvalue("--regtest-reset")
93
99
 
94
- if reset:
95
- os.makedirs(recorded_output_path, exist_ok=True)
96
- handler.save(recorded_output_path, obj)
97
- if orig_identifer is not None:
98
- self._reset_regtest_outputs.append(recorded_output_path + ".item")
99
- with open(recorded_output_path + ".item", "w") as fh:
100
- print(orig_identifer, file=fh)
101
- self._reset_regtest_outputs.append(recorded_output_path)
102
- return True, True, None
100
+ class PytestRegtestPlugin:
101
+ def __init__(self, recorder):
102
+ self.recorder = recorder
103
103
 
104
- has_markup = item.config.get_terminal_writer().hasmarkup
105
- if os.path.exists(recorded_output_path):
106
- recorded_obj = handler.load(recorded_output_path)
107
- ok = handler.compare(obj, recorded_obj)
108
- if ok:
109
- return True, True, None
110
- msg = handler.show_differences(obj, recorded_obj, has_markup)
111
- return True, False, msg
104
+ @pytest.hookimpl(trylast=True)
105
+ def pytest_runtest_call(self, item):
106
+ if hasattr(item, "regtest_stream"):
107
+ output_exception = self.check_recorded_output(item)
108
+ if output_exception is not None:
109
+ raise output_exception
112
110
 
113
- msg = handler.show(obj)
114
- return False, False, msg
111
+ if item.get_closest_marker("xfail") and item.config.getvalue("--regtest-reset"):
112
+ # enforce consistency with xfail:
113
+ assert False
115
114
 
116
115
  def check_recorded_output(self, item):
117
116
  test_folder = item.fspath.dirname
118
117
  regtest_stream = item.regtest_stream
119
- identifier = regtest_stream.identifier
118
+ version = regtest_stream.version or regtest_stream.identifier
119
+ if not isinstance(regtest_stream, RegtestStream):
120
+ return
120
121
 
121
122
  orig_identifer, recorded_output_path = result_file_paths(
122
- test_folder, item.nodeid, identifier
123
+ test_folder, item.nodeid, version
123
124
  )
124
125
  config = item.config
125
126
 
@@ -131,10 +132,12 @@ class PytestRegtestPlugin:
131
132
  with open(recorded_output_path + ".out", "w", encoding="utf-8") as fh:
132
133
  fh.write("".join(regtest_stream.get_lines()))
133
134
  if orig_identifer is not None:
134
- self._reset_regtest_outputs.append(recorded_output_path + ".item")
135
+ self.recorder._reset_regtest_outputs.append(
136
+ recorded_output_path + ".item"
137
+ )
135
138
  with open(recorded_output_path + ".item", "w") as fh:
136
139
  print(orig_identifer, file=fh)
137
- self._reset_regtest_outputs.append(recorded_output_path + ".out")
140
+ self.recorder._reset_regtest_outputs.append(recorded_output_path + ".out")
138
141
  return
139
142
 
140
143
  if os.path.exists(recorded_output_path + ".out"):
@@ -154,7 +157,7 @@ class PytestRegtestPlugin:
154
157
  tobe = [line.rstrip() for line in tobe]
155
158
 
156
159
  if current != tobe:
157
- self._failed_regtests.append(item)
160
+ self.recorder._failed_regtests.append(item)
158
161
  return RecordedOutputException(
159
162
  current,
160
163
  tobe,
@@ -163,44 +166,6 @@ class PytestRegtestPlugin:
163
166
  recorded_output_file_exists,
164
167
  )
165
168
 
166
- @pytest.hookimpl(hookwrapper=True)
167
- def pytest_pyfunc_call(self, pyfuncitem):
168
- stdout = sys.stdout
169
- if "regtest_all" in pyfuncitem.fixturenames and hasattr(
170
- pyfuncitem, "regtest_stream"
171
- ):
172
- sys.stdout = pyfuncitem.regtest_stream
173
- yield
174
- sys.stdout = stdout
175
-
176
- @pytest.hookimpl(hookwrapper=True)
177
- def pytest_report_teststatus(self, report, config):
178
- outcome = yield
179
- if report.when == "call" and "uses-regtest" in report.keywords:
180
- if config.getvalue("--regtest-reset"):
181
- result = outcome.get_result()
182
- if result[0] != "failed":
183
- outcome.force_result((result[0], "R", "RESET"))
184
-
185
- def pytest_terminal_summary(self, terminalreporter, exitstatus, config):
186
- terminalreporter.ensure_newline()
187
- terminalreporter.section("pytest-regtest report", sep="-", blue=True, bold=True)
188
- terminalreporter.write("total number of failed regression tests: ", bold=True)
189
- terminalreporter.line(str(len(self._failed_regtests)))
190
- if config.getvalue("--regtest-reset"):
191
- if config.option.verbose:
192
- terminalreporter.line(
193
- "the following output files have been reset:", bold=True
194
- )
195
- for path in self._reset_regtest_outputs:
196
- rel_path = os.path.relpath(path)
197
- terminalreporter.line(" " + rel_path)
198
- else:
199
- terminalreporter.write(
200
- "total number of reset output files: ", bold=True
201
- )
202
- terminalreporter.line(str(len(self._reset_regtest_outputs)))
203
-
204
169
  @pytest.hookimpl(hookwrapper=True)
205
170
  def pytest_runtest_makereport(self, item, call):
206
171
  outcome = yield
@@ -216,57 +181,187 @@ class PytestRegtestPlugin:
216
181
  line = line.ljust(tw.fullwidth, "-")
217
182
  tw.line(line, green=True)
218
183
  tw.write(item.regtest_stream.get_output() + "\n", cyan=True)
219
-
220
- snapshots = item.regtest_stream.snapshots
221
- if snapshots:
222
- tw.line()
223
- line = "recorded snapshots: "
224
- line = line.ljust(tw.fullwidth, "-")
184
+ line = "-" * tw.fullwidth
225
185
  tw.line(line, green=True)
226
- path = item.fspath.relto(item.session.fspath)
227
- code_lines = item.fspath.readlines()
228
-
229
- for obj, handler_options, line_no in snapshots:
230
- info = code_lines[line_no - 1].strip()
231
- tw.line(f"> {path} +{line_no}")
232
- tw.line(f"> {info}")
233
- handler = get_snapshot_handler(
234
- obj, handler_options, item.config
235
- )
236
- lines = handler.show(obj)
237
- for line in lines:
238
- tw.line(line, cyan=True)
239
- if output or snapshots:
240
- tw.line("-" * tw.fullwidth, green=True)
241
- tw.line()
242
- tw.flush()
243
186
 
244
- if call.when != "call" or not hasattr(item, "regtest_stream"):
187
+ if call.when != "call" or not getattr(item, "regtest", False):
245
188
  return
246
189
 
247
190
  result.keywords["uses-regtest"] = True
248
191
 
249
192
  if call.excinfo is not None:
250
- if call.excinfo.type is RegtestException:
251
- output_exception, snapshot_exception = call.excinfo.value.args
252
- all_lines, all_colors = [], []
193
+ all_lines, all_colors = [], []
194
+ if call.excinfo.type is RecordedOutputException:
195
+ output_exception = call.excinfo
253
196
  if output_exception is not None:
254
197
  lines, colors = self._handle_regtest_exception(
255
- item, output_exception.args, result
198
+ item, output_exception.value.args, result
256
199
  )
257
200
  all_lines.extend(lines)
258
201
  all_colors.extend(colors)
202
+
203
+ else:
204
+ return
205
+
206
+ result.longrepr = CollectErrorRepr(all_lines, all_colors)
207
+
208
+ def _handle_regtest_exception(self, item, exc_args, result):
209
+ (
210
+ current,
211
+ recorded,
212
+ recorded_output_path,
213
+ regtest_stream,
214
+ recorded_output_file_exists,
215
+ ) = exc_args
216
+
217
+ nodeid = item.nodeid + (
218
+ "" if regtest_stream.version is None else "__" + regtest_stream.version
219
+ )
220
+ if not recorded_output_file_exists:
221
+ msg = "\nregression test output not recorded yet for {}:\n".format(nodeid)
222
+ return (
223
+ [msg] + current,
224
+ [dict()] + len(current) * [dict(red=True, bold=True)],
225
+ )
226
+
227
+ nodiff = item.config.getvalue("--regtest-nodiff")
228
+ diffs = list(
229
+ difflib.unified_diff(current, recorded, "current", "expected", lineterm="")
230
+ )
231
+
232
+ msg = "\nregression test output differences for {}:\n".format(nodeid)
233
+
234
+ if nodiff:
235
+ msg_diff = f" {len(diffs)} lines in diff"
236
+ else:
237
+ recorded_output_path = os.path.relpath(recorded_output_path)
238
+ msg += f" (recorded output from {recorded_output_path})\n"
239
+ msg_diff = " > " + "\n > ".join(diffs)
240
+
241
+ return [msg, msg_diff + "\n"], [dict(), dict(red=True, bold=True)]
242
+
243
+
244
+ class SnapshotPlugin:
245
+ def __init__(self, recorder):
246
+ self.recorder = recorder
247
+
248
+ @pytest.hookimpl(trylast=True)
249
+ def pytest_runtest_call(self, item):
250
+ if hasattr(item, "snapshot"):
251
+ snapshot_exception = self.check_snapshots(item)
252
+ if snapshot_exception is not None:
253
+ raise snapshot_exception
254
+
255
+ if item.get_closest_marker("xfail") and item.config.getvalue("--regtest-reset"):
256
+ # enforce fail
257
+ assert False
258
+
259
+ def check_snapshots(self, item):
260
+ results = []
261
+
262
+ any_failed = False
263
+ for idx, snapshot in enumerate(item.snapshot.snapshots):
264
+ is_recorded, ok, msg = self.check_snapshot(idx, item, snapshot)
265
+ if not ok:
266
+ any_failed = True
267
+ results.append((ok, snapshot, is_recorded, msg))
268
+
269
+ if any_failed:
270
+ self.recorder._failed_snapshots.append(item)
271
+ return SnapshotException(results)
272
+
273
+ def check_snapshot(self, idx, item, snapshot):
274
+ obj, version, handler_options, _ = snapshot
275
+ handler = get_snapshot_handler(obj, handler_options, item.config)
276
+
277
+ test_folder = item.fspath.dirname
278
+ if version is not None:
279
+ identifier = str(version) + "__" + str(idx)
280
+ else:
281
+ identifier = str(idx)
282
+
283
+ config = item.config
284
+
285
+ orig_identifer, recorded_output_path = result_file_paths(
286
+ test_folder, item.nodeid, identifier
287
+ )
288
+
289
+ reset = config.getvalue("--regtest-reset")
290
+
291
+ if reset:
292
+ os.makedirs(recorded_output_path, exist_ok=True)
293
+ handler.save(recorded_output_path, obj)
294
+ if orig_identifer is not None:
295
+ self.recorder._reset_snapshots.append(recorded_output_path + ".item")
296
+ with open(recorded_output_path + ".item", "w") as fh:
297
+ print(orig_identifer, file=fh)
298
+ self.recorder._reset_snapshots.append(recorded_output_path)
299
+ return True, True, None
300
+
301
+ has_markup = item.config.get_terminal_writer().hasmarkup
302
+ if os.path.exists(recorded_output_path):
303
+ recorded_obj = handler.load(recorded_output_path)
304
+ ok = handler.compare(obj, recorded_obj)
305
+ if ok:
306
+ return True, True, None
307
+ msg = handler.show_differences(obj, recorded_obj, has_markup)
308
+ return True, False, msg
309
+
310
+ msg = handler.show(obj)
311
+ return False, False, msg
312
+
313
+ @pytest.hookimpl(hookwrapper=True)
314
+ def pytest_runtest_makereport(self, item, call):
315
+ outcome = yield
316
+ result = outcome.get_result()
317
+ if call.when == "teardown" and hasattr(item, "snapshot"):
318
+ if item.config.getvalue("--regtest-tee"):
319
+ tw = TerminalWriter()
320
+ snapshots = item.snapshot.snapshots
321
+ if not snapshots:
322
+ return
323
+
324
+ tw.line()
325
+ line = "recorded snapshots: "
326
+ line = line.ljust(tw.fullwidth, "-")
327
+ tw.line(line, green=True)
328
+ path = item.fspath.relto(item.session.fspath)
329
+ code_lines = item.fspath.readlines()
330
+
331
+ for obj, version, handler_options, line_no in snapshots:
332
+ info = code_lines[line_no - 1].strip()
333
+ tw.line(f"> {path} +{line_no}")
334
+ tw.line(f"> {info}")
335
+ handler = get_snapshot_handler(obj, handler_options, item.config)
336
+ lines = handler.show(obj)
337
+ for line in lines:
338
+ tw.line(line, cyan=True)
339
+ tw.line("-" * tw.fullwidth, green=True)
340
+ tw.line()
341
+ tw.flush()
342
+
343
+ if call.when != "call" or not hasattr(item, "snapshot"):
344
+ return
345
+
346
+ result.keywords["uses-regtest"] = True
347
+
348
+ if call.excinfo is not None:
349
+ all_lines, all_colors = [], []
350
+ if call.excinfo.type is SnapshotException:
351
+ snapshot_exception = call.excinfo
259
352
  if snapshot_exception is not None:
260
353
  lines, colors = self._handle_snapshot_exception(
261
- item, snapshot_exception.args, result
354
+ item, snapshot_exception.value.args, result
262
355
  )
263
356
  all_lines.extend(lines)
264
357
  all_colors.extend(colors)
358
+ else:
359
+ return
265
360
 
266
- result.longrepr = CollectErrorRepr(all_lines, all_colors)
361
+ result.longrepr = CollectErrorRepr(all_lines, all_colors)
267
362
 
268
363
  def _handle_snapshot_exception(self, item, exc_args, result):
269
- regtest_stream = item.regtest_stream
364
+ snapshot = item.snapshot
270
365
  lines = []
271
366
  colors = []
272
367
 
@@ -276,17 +371,12 @@ class PytestRegtestPlugin:
276
371
  RED = dict(red=True, bold=True)
277
372
  GREEN = dict(green=True, bold=False)
278
373
 
279
- nodeid = item.nodeid + (
280
- ""
281
- if regtest_stream.identifier is None
282
- else "__" + regtest_stream.identifier
283
- )
284
- headline = "\nsnapshot error(s) for {}:".format(nodeid)
374
+ headline = "\nsnapshot error(s) for {}:".format(item.nodeid)
285
375
  lines.append(headline)
286
376
  colors.append(NO_COLOR)
287
377
 
288
378
  for ok, snapshot, is_recorded, msg in exc_args[0]:
289
- obj, kw, line_no = snapshot
379
+ obj, version, kw, line_no = snapshot
290
380
  info = code_lines[line_no - 1].strip()
291
381
 
292
382
  path = item.fspath.relto(item.session.fspath)
@@ -322,45 +412,8 @@ class PytestRegtestPlugin:
322
412
 
323
413
  return lines, colors
324
414
 
325
- def _handle_regtest_exception(self, item, exc_args, result):
326
- (
327
- current,
328
- recorded,
329
- recorded_output_path,
330
- regtest_stream,
331
- recorded_output_file_exists,
332
- ) = exc_args
333
415
 
334
- nodeid = item.nodeid + (
335
- ""
336
- if regtest_stream.identifier is None
337
- else "__" + regtest_stream.identifier
338
- )
339
- if not recorded_output_file_exists:
340
- msg = "\nregression test output not recorded yet for {}:\n".format(nodeid)
341
- return (
342
- [msg] + current,
343
- [dict()] + len(current) * [dict(red=True, bold=True)],
344
- )
345
-
346
- nodiff = item.config.getvalue("--regtest-nodiff")
347
- diffs = list(
348
- difflib.unified_diff(current, recorded, "current", "expected", lineterm="")
349
- )
350
-
351
- msg = "\nregression test output differences for {}:\n".format(nodeid)
352
-
353
- if nodiff:
354
- msg_diff = f" {len(diffs)} lines in diff"
355
- else:
356
- recorded_output_path = os.path.relpath(recorded_output_path)
357
- msg += f" (recorded output from {recorded_output_path})\n"
358
- msg_diff = " > " + "\n > ".join(diffs)
359
-
360
- return [msg, msg_diff + "\n"], [dict(), dict(red=True, bold=True)]
361
-
362
-
363
- def result_file_paths(test_folder, nodeid, identifier):
416
+ def result_file_paths(test_folder, nodeid, version):
364
417
  file_path, __, test_function_name = nodeid.partition("::")
365
418
  file_name = os.path.basename(file_path)
366
419
 
@@ -381,8 +434,8 @@ def result_file_paths(test_folder, nodeid, identifier):
381
434
 
382
435
  test_function_name = test_function_name.replace(" ", "_")
383
436
  stem, __ = os.path.splitext(file_name)
384
- if identifier is not None:
385
- output_file_name = stem + "." + test_function_name + "__" + identifier
437
+ if version is not None:
438
+ output_file_name = stem + "." + test_function_name + "__" + str(version)
386
439
  else:
387
440
  output_file_name = stem + "." + test_function_name
388
441
 
@@ -394,16 +447,14 @@ def result_file_paths(test_folder, nodeid, identifier):
394
447
  class RegtestStream:
395
448
  def __init__(self, request):
396
449
  request.node.regtest_stream = self
450
+ request.node.regtest = True
397
451
  self.request = request
398
452
  self.buffer = StringIO()
453
+ self.version = None
399
454
  self.identifier = None
400
455
 
401
456
  self.snapshots = []
402
457
 
403
- def check(self, obj, **kw):
404
- line_no = inspect.currentframe().f_back.f_lineno
405
- self.snapshots.append((obj, kw, line_no))
406
-
407
458
  def write(self, what):
408
459
  self.buffer.write(what)
409
460
 
@@ -430,6 +481,20 @@ class RegtestStream:
430
481
  return False # dont suppress exception
431
482
 
432
483
 
484
+ class Snapshot:
485
+ def __init__(self, request):
486
+ request.node.snapshot = self
487
+ request.node.regtest = True
488
+ self.request = request
489
+ self.buffer = StringIO()
490
+
491
+ self.snapshots = []
492
+
493
+ def check(self, obj, *, version=None, **kw):
494
+ line_no = inspect.currentframe().f_back.f_lineno
495
+ self.snapshots.append((obj, version, kw, line_no))
496
+
497
+
433
498
  def cleanup(output, request):
434
499
  for converter in _converters_pre:
435
500
  output = converter(output, request)
@@ -461,7 +526,8 @@ _converters_pre = []
461
526
  _converters_post = []
462
527
 
463
528
 
464
- def clear_converters():
529
+ def clear_converters() -> None:
530
+ """Unregisters all converters, including the builtin converters."""
465
531
  _converters_pre.clear()
466
532
  _converters_post.clear()
467
533
 
@@ -474,7 +540,20 @@ def _fix_pre_v2_converter_function(function):
474
540
  return fixed_converter_function
475
541
 
476
542
 
477
- def register_converter_pre(function):
543
+ def register_converter_pre(
544
+ function: Callable[[str, Optional[_pytest.fixtures.FixtureRequest]], None],
545
+ ) -> None:
546
+ """Registers a new conversion function at the head of the list
547
+ of existing converters.
548
+
549
+ Parameters:
550
+ function: Function to cleanup given string and remove data which can change
551
+ between test runs without affecting the correctness of the test.
552
+ The second argument is optional and is a `pytest` object which holds
553
+ information about the current `config` or the current test function.
554
+ This argument can be ignored in many situations.
555
+
556
+ """
478
557
  if function not in _converters_pre:
479
558
  signature = inspect.signature(function)
480
559
  # keep downward compatibility:
@@ -483,7 +562,19 @@ def register_converter_pre(function):
483
562
  _converters_pre.append(function)
484
563
 
485
564
 
486
- def register_converter_post(function):
565
+ def register_converter_post(
566
+ function: Callable[[str, Optional[_pytest.fixtures.FixtureRequest]], None],
567
+ ) -> None:
568
+ """Registers a new conversion function at the head of the list
569
+ of existing converters
570
+
571
+ Parameters:
572
+ function: Function to cleanup given string and remove data which can change
573
+ between test runs without affecting the correctness of the test.
574
+ The second argument is optional and is a `pytest` object which holds
575
+ information about the current `config` or the current test function.
576
+ This argument can be ignored in many situations.
577
+ """
487
578
  if function not in _converters_post:
488
579
  signature = inspect.signature(function)
489
580
  # keep downward compatibility:
@@ -543,7 +634,7 @@ class CollectErrorRepr(TerminalRepr):
543
634
 
544
635
 
545
636
  def get_snapshot_handler(obj, handler_options, pytest_config):
546
- for check, handler in snapshot_handlers:
547
- if check(obj):
548
- return handler(handler_options, pytest_config, tw)
549
- raise ValueError(f"cannot take snapshot for type {obj.__class__}")
637
+ handler = SnapshotHandlerRegistry.get_handler(obj)
638
+ if handler is None:
639
+ raise ValueError(f"cannot take snapshot for type {obj.__class__}")
640
+ return handler(handler_options, pytest_config, tw)
@@ -1,20 +1,4 @@
1
- try:
2
- import numpy as np # noqa: F401
3
-
4
- HAS_NUMPY = True
5
- except ImportError:
6
- HAS_NUMPY = False
7
-
8
- try:
9
- import pandas as pd # noqa: F401
10
-
11
- HAS_PANDAS = True
12
- except ImportError:
13
- HAS_PANDAS = False
14
-
15
-
16
- if HAS_PANDAS and HAS_NUMPY:
17
-
1
+ def register_pandas_handler():
18
2
  def is_dataframe(obj):
19
3
  try:
20
4
  import pandas as pd
@@ -24,12 +8,12 @@ if HAS_PANDAS and HAS_NUMPY:
24
8
  return False
25
9
 
26
10
  from .pandas_handler import DataFrameHandler
27
- from .snapshot_handler import snapshot_handlers
11
+ from .snapshot_handler import SnapshotHandlerRegistry
28
12
 
29
- snapshot_handlers.append((is_dataframe, DataFrameHandler))
13
+ SnapshotHandlerRegistry.add_handler(is_dataframe, DataFrameHandler)
30
14
 
31
- if HAS_NUMPY:
32
15
 
16
+ def register_numpy_handler():
33
17
  def is_numpy(obj):
34
18
  try:
35
19
  import numpy as np
@@ -39,6 +23,6 @@ if HAS_NUMPY:
39
23
  return False
40
24
 
41
25
  from .numpy_handler import NumpyHandler
42
- from .snapshot_handler import snapshot_handlers
26
+ from .snapshot_handler import SnapshotHandlerRegistry
43
27
 
44
- snapshot_handlers.append((is_numpy, NumpyHandler))
28
+ SnapshotHandlerRegistry.add_handler(is_numpy, NumpyHandler)