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.
@@ -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="do not apply standard output converters to clean up indeterministic output",
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
- config.pluginmanager.register(PytestRegtestPlugin())
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
- snapshot = regtest
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
@@ -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
@@ -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