pytest-regtest 2.2.0a2__py2.py3-none-any.whl → 2.3.0__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,185 @@ 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
+ handler, obj, version, _ = snapshot
275
+
276
+ test_folder = item.fspath.dirname
277
+ if version is not None:
278
+ identifier = str(version) + "__" + str(idx)
279
+ else:
280
+ identifier = str(idx)
281
+
282
+ config = item.config
283
+
284
+ orig_identifer, recorded_output_path = result_file_paths(
285
+ test_folder, item.nodeid, identifier
286
+ )
287
+
288
+ reset = config.getvalue("--regtest-reset")
289
+
290
+ if reset:
291
+ os.makedirs(recorded_output_path, exist_ok=True)
292
+ handler.save(recorded_output_path, obj)
293
+ if orig_identifer is not None:
294
+ self.recorder._reset_snapshots.append(recorded_output_path + ".item")
295
+ with open(recorded_output_path + ".item", "w") as fh:
296
+ print(orig_identifer, file=fh)
297
+ self.recorder._reset_snapshots.append(recorded_output_path)
298
+ return True, True, None
299
+
300
+ has_markup = item.config.get_terminal_writer().hasmarkup
301
+ if os.path.exists(recorded_output_path):
302
+ recorded_obj = handler.load(recorded_output_path)
303
+ ok = handler.compare(obj, recorded_obj)
304
+ if ok:
305
+ return True, True, None
306
+ msg = handler.show_differences(obj, recorded_obj, has_markup)
307
+ return True, False, msg
308
+
309
+ msg = handler.show(obj)
310
+ return False, False, msg
311
+
312
+ @pytest.hookimpl(hookwrapper=True)
313
+ def pytest_runtest_makereport(self, item, call):
314
+ outcome = yield
315
+ result = outcome.get_result()
316
+ if call.when == "teardown" and hasattr(item, "snapshot"):
317
+ if item.config.getvalue("--regtest-tee"):
318
+ tw = TerminalWriter()
319
+ snapshots = item.snapshot.snapshots
320
+ if not snapshots:
321
+ return
322
+
323
+ tw.line()
324
+ line = "recorded snapshots: "
325
+ line = line.ljust(tw.fullwidth, "-")
326
+ tw.line(line, green=True)
327
+ path = item.fspath.relto(item.session.fspath)
328
+ code_lines = item.fspath.readlines()
329
+
330
+ for handler, obj, version, line_no in snapshots:
331
+ info = code_lines[line_no - 1].strip()
332
+ tw.line(f"> {path} +{line_no}")
333
+ tw.line(f"> {info}")
334
+ lines = handler.show(obj)
335
+ for line in lines:
336
+ tw.line(line, cyan=True)
337
+ tw.line("-" * tw.fullwidth, green=True)
338
+ tw.line()
339
+ tw.flush()
340
+
341
+ if call.when != "call" or not hasattr(item, "snapshot"):
342
+ return
343
+
344
+ result.keywords["uses-regtest"] = True
345
+
346
+ if call.excinfo is not None:
347
+ all_lines, all_colors = [], []
348
+ if call.excinfo.type is SnapshotException:
349
+ snapshot_exception = call.excinfo
259
350
  if snapshot_exception is not None:
260
351
  lines, colors = self._handle_snapshot_exception(
261
- item, snapshot_exception.args, result
352
+ item, snapshot_exception.value.args, result
262
353
  )
263
354
  all_lines.extend(lines)
264
355
  all_colors.extend(colors)
356
+ else:
357
+ return
265
358
 
266
- result.longrepr = CollectErrorRepr(all_lines, all_colors)
359
+ result.longrepr = CollectErrorRepr(all_lines, all_colors)
267
360
 
268
361
  def _handle_snapshot_exception(self, item, exc_args, result):
269
- regtest_stream = item.regtest_stream
362
+ snapshot = item.snapshot
270
363
  lines = []
271
364
  colors = []
272
365
 
@@ -276,17 +369,12 @@ class PytestRegtestPlugin:
276
369
  RED = dict(red=True, bold=True)
277
370
  GREEN = dict(green=True, bold=False)
278
371
 
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)
372
+ headline = "\nsnapshot error(s) for {}:".format(item.nodeid)
285
373
  lines.append(headline)
286
374
  colors.append(NO_COLOR)
287
375
 
288
376
  for ok, snapshot, is_recorded, msg in exc_args[0]:
289
- obj, kw, line_no = snapshot
377
+ obj, version, kw, line_no = snapshot
290
378
  info = code_lines[line_no - 1].strip()
291
379
 
292
380
  path = item.fspath.relto(item.session.fspath)
@@ -322,45 +410,8 @@ class PytestRegtestPlugin:
322
410
 
323
411
  return lines, colors
324
412
 
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
-
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
413
 
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):
414
+ def result_file_paths(test_folder, nodeid, version):
364
415
  file_path, __, test_function_name = nodeid.partition("::")
365
416
  file_name = os.path.basename(file_path)
366
417
 
@@ -381,8 +432,8 @@ def result_file_paths(test_folder, nodeid, identifier):
381
432
 
382
433
  test_function_name = test_function_name.replace(" ", "_")
383
434
  stem, __ = os.path.splitext(file_name)
384
- if identifier is not None:
385
- output_file_name = stem + "." + test_function_name + "__" + identifier
435
+ if version is not None:
436
+ output_file_name = stem + "." + test_function_name + "__" + str(version)
386
437
  else:
387
438
  output_file_name = stem + "." + test_function_name
388
439
 
@@ -394,16 +445,14 @@ def result_file_paths(test_folder, nodeid, identifier):
394
445
  class RegtestStream:
395
446
  def __init__(self, request):
396
447
  request.node.regtest_stream = self
448
+ request.node.regtest = True
397
449
  self.request = request
398
450
  self.buffer = StringIO()
451
+ self.version = None
399
452
  self.identifier = None
400
453
 
401
454
  self.snapshots = []
402
455
 
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
456
  def write(self, what):
408
457
  self.buffer.write(what)
409
458
 
@@ -430,6 +479,25 @@ class RegtestStream:
430
479
  return False # dont suppress exception
431
480
 
432
481
 
482
+ class Snapshot:
483
+ def __init__(self, request):
484
+ request.node.snapshot = self
485
+ request.node.regtest = True
486
+ self.request = request
487
+ self.buffer = StringIO()
488
+
489
+ self.snapshots = []
490
+
491
+ def check(self, obj, *, version=None, **options):
492
+ handler_class = SnapshotHandlerRegistry.get_handler(obj)
493
+ if handler_class is None:
494
+ raise ValueError(f"no handler registered for {obj}")
495
+
496
+ handler = handler_class(options, self.request.config, tw)
497
+ line_no = inspect.currentframe().f_back.f_lineno
498
+ self.snapshots.append((handler, obj, version, line_no))
499
+
500
+
433
501
  def cleanup(output, request):
434
502
  for converter in _converters_pre:
435
503
  output = converter(output, request)
@@ -461,7 +529,8 @@ _converters_pre = []
461
529
  _converters_post = []
462
530
 
463
531
 
464
- def clear_converters():
532
+ def clear_converters() -> None:
533
+ """Unregisters all converters, including the builtin converters."""
465
534
  _converters_pre.clear()
466
535
  _converters_post.clear()
467
536
 
@@ -474,7 +543,20 @@ def _fix_pre_v2_converter_function(function):
474
543
  return fixed_converter_function
475
544
 
476
545
 
477
- def register_converter_pre(function):
546
+ def register_converter_pre(
547
+ function: Callable[[str, Optional[_pytest.fixtures.FixtureRequest]], None],
548
+ ) -> None:
549
+ """Registers a new conversion function at the head of the list
550
+ of existing converters.
551
+
552
+ Parameters:
553
+ function: Function to cleanup given string and remove data which can change
554
+ between test runs without affecting the correctness of the test.
555
+ The second argument is optional and is a `pytest` object which holds
556
+ information about the current `config` or the current test function.
557
+ This argument can be ignored in many situations.
558
+
559
+ """
478
560
  if function not in _converters_pre:
479
561
  signature = inspect.signature(function)
480
562
  # keep downward compatibility:
@@ -483,7 +565,19 @@ def register_converter_pre(function):
483
565
  _converters_pre.append(function)
484
566
 
485
567
 
486
- def register_converter_post(function):
568
+ def register_converter_post(
569
+ function: Callable[[str, Optional[_pytest.fixtures.FixtureRequest]], None],
570
+ ) -> None:
571
+ """Registers a new conversion function at the head of the list
572
+ of existing converters
573
+
574
+ Parameters:
575
+ function: Function to cleanup given string and remove data which can change
576
+ between test runs without affecting the correctness of the test.
577
+ The second argument is optional and is a `pytest` object which holds
578
+ information about the current `config` or the current test function.
579
+ This argument can be ignored in many situations.
580
+ """
487
581
  if function not in _converters_post:
488
582
  signature = inspect.signature(function)
489
583
  # keep downward compatibility:
@@ -540,10 +634,3 @@ class CollectErrorRepr(TerminalRepr):
540
634
  def toterminal(self, out):
541
635
  for message, color in zip(self.messages, self.colors):
542
636
  out.line(message, **color)
543
-
544
-
545
- 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__}")