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.
- pytest_regtest/__init__.py +47 -6
- pytest_regtest/numpy_handler.py +23 -3
- pytest_regtest/pandas_handler.py +8 -0
- pytest_regtest/polars_handler.py +114 -0
- pytest_regtest/pytest_regtest.py +284 -197
- pytest_regtest/register_third_party_handlers.py +21 -22
- pytest_regtest/snapshot_handler.py +130 -21
- pytest_regtest-2.3.0.dist-info/METADATA +111 -0
- pytest_regtest-2.3.0.dist-info/RECORD +13 -0
- pytest_regtest-2.2.0a2.dist-info/METADATA +0 -356
- pytest_regtest-2.2.0a2.dist-info/RECORD +0 -12
- {pytest_regtest-2.2.0a2.dist-info → pytest_regtest-2.3.0.dist-info}/WHEEL +0 -0
- {pytest_regtest-2.2.0a2.dist-info → pytest_regtest-2.3.0.dist-info}/entry_points.txt +0 -0
- {pytest_regtest-2.2.0a2.dist-info → pytest_regtest-2.3.0.dist-info}/licenses/LICENSE.txt +0 -0
pytest_regtest/__init__.py
CHANGED
|
@@ -2,13 +2,24 @@ from importlib.metadata import version as _version
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from . import register_third_party_handlers # noqa: F401
|
|
6
|
-
from .pytest_regtest import PytestRegtestPlugin # noqa: F401
|
|
7
|
-
from .pytest_regtest import RegtestStream # noqa: F401
|
|
8
5
|
from .pytest_regtest import clear_converters # noqa: F401
|
|
9
6
|
from .pytest_regtest import patch_terminal_size # noqa: F401
|
|
10
7
|
from .pytest_regtest import register_converter_post # noqa: F401
|
|
11
8
|
from .pytest_regtest import register_converter_pre # noqa: F401
|
|
9
|
+
from .pytest_regtest import (
|
|
10
|
+
PytestRegtestCommonHooks,
|
|
11
|
+
PytestRegtestPlugin,
|
|
12
|
+
RegtestStream,
|
|
13
|
+
Snapshot,
|
|
14
|
+
SnapshotPlugin,
|
|
15
|
+
)
|
|
16
|
+
from .register_third_party_handlers import (
|
|
17
|
+
register_numpy_handler,
|
|
18
|
+
register_pandas_handler,
|
|
19
|
+
register_polars_handler,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .snapshot_handler import register_python_object_handler
|
|
12
23
|
|
|
13
24
|
__version__ = _version(__package__)
|
|
14
25
|
|
|
@@ -43,12 +54,17 @@ def pytest_addoption(parser):
|
|
|
43
54
|
"--regtest-disable-stdconv",
|
|
44
55
|
action="store_true",
|
|
45
56
|
default=False,
|
|
46
|
-
help=
|
|
57
|
+
help=(
|
|
58
|
+
"do not apply standard output converters to clean up indeterministic output"
|
|
59
|
+
),
|
|
47
60
|
)
|
|
48
61
|
|
|
49
62
|
|
|
50
63
|
def pytest_configure(config):
|
|
51
|
-
|
|
64
|
+
common = PytestRegtestCommonHooks()
|
|
65
|
+
config.pluginmanager.register(common)
|
|
66
|
+
config.pluginmanager.register(PytestRegtestPlugin(common))
|
|
67
|
+
config.pluginmanager.register(SnapshotPlugin(common))
|
|
52
68
|
|
|
53
69
|
|
|
54
70
|
@pytest.fixture
|
|
@@ -56,7 +72,9 @@ def regtest(request):
|
|
|
56
72
|
yield RegtestStream(request)
|
|
57
73
|
|
|
58
74
|
|
|
59
|
-
|
|
75
|
+
@pytest.fixture
|
|
76
|
+
def snapshot(request):
|
|
77
|
+
yield Snapshot(request)
|
|
60
78
|
|
|
61
79
|
|
|
62
80
|
@pytest.fixture
|
|
@@ -65,3 +83,26 @@ def regtest_all(regtest):
|
|
|
65
83
|
|
|
66
84
|
|
|
67
85
|
snapshot_all_output = regtest_all
|
|
86
|
+
|
|
87
|
+
register_python_object_handler()
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
import pandas # noqa: F401
|
|
91
|
+
|
|
92
|
+
register_pandas_handler()
|
|
93
|
+
except ImportError:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
import numpy # noqa: F401
|
|
98
|
+
|
|
99
|
+
register_numpy_handler()
|
|
100
|
+
except ImportError:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
import polars # noqa: F401
|
|
105
|
+
|
|
106
|
+
register_polars_handler()
|
|
107
|
+
except ImportError:
|
|
108
|
+
pass
|
pytest_regtest/numpy_handler.py
CHANGED
|
@@ -11,12 +11,19 @@ from .utils import highlight_mismatches
|
|
|
11
11
|
|
|
12
12
|
class NumpyHandler(BaseSnapshotHandler):
|
|
13
13
|
def __init__(self, handler_options, pytest_config, tw):
|
|
14
|
-
self.print_options = np.get_printoptions() | handler_options.get(
|
|
15
|
-
"print_options", {}
|
|
16
|
-
)
|
|
17
14
|
self.atol = handler_options.get("atol", 0.0)
|
|
18
15
|
self.rtol = handler_options.get("rtol", 0.0)
|
|
19
16
|
self.equal_nan = handler_options.get("equal_nan", True)
|
|
17
|
+
if handler_options.get("print_options"):
|
|
18
|
+
warnings.warn(
|
|
19
|
+
"please use the numpy.printoptions context manager instead of"
|
|
20
|
+
" the print_options argument.",
|
|
21
|
+
DeprecationWarning,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
self.print_options = np.get_printoptions() | handler_options.get(
|
|
25
|
+
"print_options", {}
|
|
26
|
+
)
|
|
20
27
|
|
|
21
28
|
def _filename(self, folder):
|
|
22
29
|
return os.path.join(folder, "arrays.npy")
|
|
@@ -148,6 +155,7 @@ class NumpyHandler(BaseSnapshotHandler):
|
|
|
148
155
|
self, current_obj, current_as_text, recorded_obj, recorded_as_text, has_markup
|
|
149
156
|
):
|
|
150
157
|
sub_diff = []
|
|
158
|
+
|
|
151
159
|
for i, (l1, l2, r1, r2) in enumerate(
|
|
152
160
|
zip(current_as_text, recorded_as_text, current_obj, recorded_obj)
|
|
153
161
|
):
|
|
@@ -193,4 +201,16 @@ class NumpyHandler(BaseSnapshotHandler):
|
|
|
193
201
|
sub_diff.append(f"row {i:3d}: {l1}")
|
|
194
202
|
sub_diff.append(f" {l2}")
|
|
195
203
|
|
|
204
|
+
missing = len(current_as_text) - len(recorded_as_text)
|
|
205
|
+
if missing > 0:
|
|
206
|
+
for i, row in enumerate(current_as_text[-missing:], len(recorded_as_text)):
|
|
207
|
+
# remove duplicate brackets
|
|
208
|
+
row = row.rstrip("]") + "]"
|
|
209
|
+
sub_diff.append(f"row {i:3d}: -{row.lstrip()}")
|
|
210
|
+
if missing < 0:
|
|
211
|
+
for i, row in enumerate(recorded_as_text[missing:], len(current_as_text)):
|
|
212
|
+
# remove duplicate brackets
|
|
213
|
+
row = row.rstrip("]") + "]"
|
|
214
|
+
sub_diff.append(f"row {i:3d}: +{row.lstrip()}")
|
|
215
|
+
|
|
196
216
|
return sub_diff
|
pytest_regtest/pandas_handler.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import difflib
|
|
2
2
|
import io
|
|
3
3
|
import os.path
|
|
4
|
+
import warnings
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
import pandas as pd
|
|
@@ -10,6 +11,13 @@ from .snapshot_handler import BaseSnapshotHandler
|
|
|
10
11
|
|
|
11
12
|
class DataFrameHandler(BaseSnapshotHandler):
|
|
12
13
|
def __init__(self, handler_options, pytest_config, tw):
|
|
14
|
+
if handler_options.get("display_options"):
|
|
15
|
+
warnings.warn(
|
|
16
|
+
"please use the 'pandas.option_context' context manager instead of"
|
|
17
|
+
" the display_options argument.",
|
|
18
|
+
DeprecationWarning,
|
|
19
|
+
)
|
|
20
|
+
|
|
13
21
|
# default contains a few nested dicts and we flatten those, e.g.
|
|
14
22
|
# { "html": {"border": 1} } -> { "html.border": 1 }
|
|
15
23
|
default = list(pd.options.display.d.items())
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import difflib
|
|
2
|
+
import io
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import polars as pl
|
|
7
|
+
from polars.testing import assert_frame_equal
|
|
8
|
+
|
|
9
|
+
from .snapshot_handler import BaseSnapshotHandler
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PolarsHandler(BaseSnapshotHandler):
|
|
13
|
+
"""
|
|
14
|
+
PolarsHandler is a class for handling Polars DataFrame snapshots in pytest-regtest.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, handler_options: dict[str, Any], pytest_config, tw):
|
|
18
|
+
self.atol = handler_options.get("atol", 0.0)
|
|
19
|
+
self.rtol = handler_options.get("rtol", 0.0)
|
|
20
|
+
self.display_options = handler_options.get("display_options", None)
|
|
21
|
+
|
|
22
|
+
def _filename(self, folder: str | os.PathLike[Any]) -> str:
|
|
23
|
+
return os.path.join(folder, "polars.parquet")
|
|
24
|
+
|
|
25
|
+
def save(self, folder: str | os.PathLike[Any], obj: pl.DataFrame):
|
|
26
|
+
obj.write_parquet(self._filename(folder))
|
|
27
|
+
|
|
28
|
+
def load(self, folder: str | os.PathLike[Any]) -> pl.DataFrame:
|
|
29
|
+
return pl.read_parquet(self._filename(folder))
|
|
30
|
+
|
|
31
|
+
def show(self, obj: pl.DataFrame) -> list[str]:
|
|
32
|
+
stream = io.StringIO()
|
|
33
|
+
if self.display_options:
|
|
34
|
+
with pl.Config(**self.display_options):
|
|
35
|
+
stream.write(str(obj))
|
|
36
|
+
else:
|
|
37
|
+
stream.write(str(obj))
|
|
38
|
+
return stream.getvalue().splitlines()
|
|
39
|
+
|
|
40
|
+
def compare(self, current_obj: pl.DataFrame, recorded_obj: pl.DataFrame) -> bool:
|
|
41
|
+
try:
|
|
42
|
+
assert_frame_equal(
|
|
43
|
+
current_obj, recorded_obj, atol=self.atol, rtol=self.rtol
|
|
44
|
+
)
|
|
45
|
+
return True
|
|
46
|
+
except AssertionError:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def create_schema_info(df: pl.DataFrame) -> list[str]:
|
|
51
|
+
"""
|
|
52
|
+
Generate a summary of the schema information for a given Polars DataFrame.
|
|
53
|
+
|
|
54
|
+
Parameters:
|
|
55
|
+
df (pl.DataFrame): The Polars DataFrame for which to generate schema information.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
list[str]: A list of strings representing the schema information, including
|
|
59
|
+
the total number of columns, column names, non-null counts, and data types.
|
|
60
|
+
"""
|
|
61
|
+
schema = df.schema
|
|
62
|
+
schema_string_repr = [
|
|
63
|
+
"Data columns (total {} columns):".format(len(schema)),
|
|
64
|
+
" # Column Non-Null Count Dtype ",
|
|
65
|
+
"--- ------ -------------- ----- ",
|
|
66
|
+
]
|
|
67
|
+
for i, (column, dtype) in enumerate(schema.items()):
|
|
68
|
+
total_count = df.height
|
|
69
|
+
null_count = df[column].null_count()
|
|
70
|
+
non_null_count = total_count - null_count
|
|
71
|
+
dtype_str = str(dtype)
|
|
72
|
+
schema_string_repr.append(
|
|
73
|
+
f" {i} {column} {non_null_count} non-null {dtype_str}"
|
|
74
|
+
)
|
|
75
|
+
return schema_string_repr
|
|
76
|
+
|
|
77
|
+
def show_differences(
|
|
78
|
+
self, current_obj: pl.DataFrame, recorded_obj: pl.DataFrame, has_markup: bool
|
|
79
|
+
) -> list[str]:
|
|
80
|
+
lines = []
|
|
81
|
+
|
|
82
|
+
current_schema = self.create_schema_info(current_obj)
|
|
83
|
+
recorded_schema = self.create_schema_info(recorded_obj)
|
|
84
|
+
|
|
85
|
+
info_diff = list(
|
|
86
|
+
difflib.unified_diff(
|
|
87
|
+
current_schema,
|
|
88
|
+
recorded_schema,
|
|
89
|
+
"current",
|
|
90
|
+
"expected",
|
|
91
|
+
lineterm="",
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
lines.extend(info_diff)
|
|
95
|
+
recorded_as_text = self.show(recorded_obj)
|
|
96
|
+
current_as_text = self.show(current_obj)
|
|
97
|
+
|
|
98
|
+
diffs = list(
|
|
99
|
+
difflib.unified_diff(
|
|
100
|
+
current_as_text,
|
|
101
|
+
recorded_as_text,
|
|
102
|
+
"current",
|
|
103
|
+
"expected",
|
|
104
|
+
lineterm="",
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
lines.append("")
|
|
109
|
+
if diffs:
|
|
110
|
+
lines.extend(diffs)
|
|
111
|
+
else:
|
|
112
|
+
lines.append("diff is empty, you may want to change the print options")
|
|
113
|
+
|
|
114
|
+
return lines
|