pytest-regtest 2.2.1__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.
@@ -16,7 +16,9 @@ from .pytest_regtest import (
16
16
  from .register_third_party_handlers import (
17
17
  register_numpy_handler,
18
18
  register_pandas_handler,
19
+ register_polars_handler,
19
20
  )
21
+
20
22
  from .snapshot_handler import register_python_object_handler
21
23
 
22
24
  __version__ = _version(__package__)
@@ -97,3 +99,10 @@ try:
97
99
  register_numpy_handler()
98
100
  except ImportError:
99
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")
@@ -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
@@ -271,8 +271,7 @@ class SnapshotPlugin:
271
271
  return SnapshotException(results)
272
272
 
273
273
  def check_snapshot(self, idx, item, snapshot):
274
- obj, version, handler_options, _ = snapshot
275
- handler = get_snapshot_handler(obj, handler_options, item.config)
274
+ handler, obj, version, _ = snapshot
276
275
 
277
276
  test_folder = item.fspath.dirname
278
277
  if version is not None:
@@ -328,11 +327,10 @@ class SnapshotPlugin:
328
327
  path = item.fspath.relto(item.session.fspath)
329
328
  code_lines = item.fspath.readlines()
330
329
 
331
- for obj, version, handler_options, line_no in snapshots:
330
+ for handler, obj, version, line_no in snapshots:
332
331
  info = code_lines[line_no - 1].strip()
333
332
  tw.line(f"> {path} +{line_no}")
334
333
  tw.line(f"> {info}")
335
- handler = get_snapshot_handler(obj, handler_options, item.config)
336
334
  lines = handler.show(obj)
337
335
  for line in lines:
338
336
  tw.line(line, cyan=True)
@@ -490,9 +488,14 @@ class Snapshot:
490
488
 
491
489
  self.snapshots = []
492
490
 
493
- def check(self, obj, *, version=None, **kw):
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)
494
497
  line_no = inspect.currentframe().f_back.f_lineno
495
- self.snapshots.append((obj, version, kw, line_no))
498
+ self.snapshots.append((handler, obj, version, line_no))
496
499
 
497
500
 
498
501
  def cleanup(output, request):
@@ -631,10 +634,3 @@ class CollectErrorRepr(TerminalRepr):
631
634
  def toterminal(self, out):
632
635
  for message, color in zip(self.messages, self.colors):
633
636
  out.line(message, **color)
634
-
635
-
636
- def get_snapshot_handler(obj, handler_options, pytest_config):
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)
@@ -26,3 +26,18 @@ def register_numpy_handler():
26
26
  from .snapshot_handler import SnapshotHandlerRegistry
27
27
 
28
28
  SnapshotHandlerRegistry.add_handler(is_numpy, NumpyHandler)
29
+
30
+
31
+ def register_polars_handler():
32
+ def is_polars(obj):
33
+ try:
34
+ import polars as pl
35
+
36
+ return isinstance(obj, pl.DataFrame)
37
+ except ImportError:
38
+ return False
39
+
40
+ from .polars_handler import PolarsHandler
41
+ from .snapshot_handler import SnapshotHandlerRegistry
42
+
43
+ SnapshotHandlerRegistry.add_handler(is_polars, PolarsHandler)
@@ -14,15 +14,14 @@ class BaseSnapshotHandler(abc.ABC):
14
14
  Abstract base class to implement snapshot handlers.
15
15
  """
16
16
 
17
+ @abc.abstractmethod
17
18
  def __init__(
18
- self,
19
19
  handler_options: dict[str, Any],
20
20
  pytest_config: type[pytest.Config],
21
21
  tw: int,
22
22
  ) -> None:
23
23
  """
24
- This method does not need to be implemented in a subclass, but
25
- can e.g. be used to process entries from `handler_options`.
24
+ This method is called within `snapshot.check` to configure the handler.
26
25
 
27
26
  Parameters:
28
27
  handler_options: Keyword arguments from `shapshot.check` call
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pytest-regtest
3
- Version: 2.2.1
3
+ Version: 2.3.0
4
4
  Summary: pytest plugin for snapshot regression testing
5
5
  Project-URL: Source, https://gitlab.com/uweschmitt/pytest-regtest
6
6
  Project-URL: Documentation, https://pytest-regtest.readthedocs.org
@@ -25,7 +25,10 @@ Requires-Dist: mkdocs-awesome-pages-plugin; extra == 'dev'
25
25
  Requires-Dist: mkdocs-material; extra == 'dev'
26
26
  Requires-Dist: mkdocstrings[python]; extra == 'dev'
27
27
  Requires-Dist: numpy; extra == 'dev'
28
+ Requires-Dist: numpy>=2.1.1; extra == 'dev'
28
29
  Requires-Dist: pandas; extra == 'dev'
30
+ Requires-Dist: pandas>=2.2.3; extra == 'dev'
31
+ Requires-Dist: polars>=1.9.0; extra == 'dev'
29
32
  Requires-Dist: pre-commit; extra == 'dev'
30
33
  Requires-Dist: pytest-cov; extra == 'dev'
31
34
  Requires-Dist: ruff; extra == 'dev'
@@ -33,6 +36,13 @@ Requires-Dist: twine; extra == 'dev'
33
36
  Requires-Dist: wheel; extra == 'dev'
34
37
  Description-Content-Type: text/markdown
35
38
 
39
+ ![](https://gitlab.com/uweschmitt/pytest-regtest/badges/main/pipeline.svg)
40
+ ![](https://gitlab.com/uweschmitt/pytest-regtest/badges/main/coverage.svg?job=coverage)
41
+
42
+
43
+ The full documentatin for this package are available at
44
+ https://pytest-regtest.readthedocs.org
45
+
36
46
  # About
37
47
 
38
48
  ## Introduction
@@ -42,7 +52,7 @@ Description-Content-Type: text/markdown
42
52
 
43
53
  Unlike [functional testing](https://en.wikipedia.org/wiki/Functional_testing),
44
54
  [regression testing](https://en.wikipedia.org/wiki/Regression_testing)
45
- testing does not test whether the software produces the correct
55
+ does not test whether the software produces the correct
46
56
  results, but whether it behaves as it did before changes were introduced.
47
57
 
48
58
  More specifically, `pytest-regtest` provides **snapshot testing**, which
@@ -58,30 +68,41 @@ To install and activate this plugin execute:
58
68
  $ pip install pytest-regtest
59
69
 
60
70
 
71
+ !!! note
72
+
73
+ `pyteset-regtest` offers some functionalities which are tailored
74
+ for `NumPy`, `pandas` and `polars`. These depencies are not
75
+ installed if you install `pytest-regtest`. In case you are using
76
+ e.g. `NumPy` snapshots, we assume that your productive code (the
77
+ code under test) uses `NumPy` and thus should be part of the setup
78
+ of your project.
79
+
80
+
61
81
  ## Use case 1: Changing code with no or little testing setup yet
62
82
  If you're working with code that has little or no unit testing, you
63
83
  can use regression testing to ensure that your changes don't break or
64
84
  alter previous results.
65
85
 
66
- **Example**: This can be useful when working with scientific data
67
- analysis scripts, which often start a long script and then are
68
- restructured into different functions.
86
+ **Example**:
87
+ This can be useful when working with data analysis scripts, which often
88
+ start as one long script and then are restructured into different
89
+ functions as they evolve.
69
90
 
70
91
 
71
92
  ## Use case 2: Testing complex data
72
-
73
- If your unit tests contain a lot of `assert` statements to check
74
- a complex data structure you can use regression tests instead.
93
+ If a unit tests contains many `assert` statements to check a complex
94
+ data structure you can use regression tests instead.
75
95
 
76
96
  **Example**: To test code which ingests data into a database one can
77
97
  use regression tests on textual database dumps.
78
98
 
79
- ## Use case 3: Testing numpy arrays or pandas data frames
99
+ ## Use case 3: Testing NumPy arrays or pandas data frames
100
+
101
+ If your code generates numerical results, such as `NumPy` arrays,
102
+ `pandas` or `polars` data frames, you can use `pytest-regtest` to simply record such
103
+ results and test them later, taking into account relative and absolute
104
+ tolerances.
80
105
 
81
- If code produces numerical results, such as `numpy` arrays or
82
- `pandas` data frames, you can use `pytest-regtest` to simply record
83
- such results and test later with considering relative
84
- and absolute tolerances.
85
106
 
86
107
  **Example**:
87
108
  A function creates a 10 x 10 matrix. Either you have to write 100
@@ -0,0 +1,13 @@
1
+ pytest_regtest/__init__.py,sha256=q3wEEi3ZZXaN5CXc0jKCeupbAl73t64Fz1pFNcYYPYo,2536
2
+ pytest_regtest/numpy_handler.py,sha256=hKrVY9dId-45rUR_83w9jbUWgUhd0ABk8uLbOhgm0IM,7173
3
+ pytest_regtest/pandas_handler.py,sha256=7vKaHyODcKMltgn9MbbbL1FsOBQK994dEY5fVlDoQ1g,4528
4
+ pytest_regtest/polars_handler.py,sha256=B1CFNz0L96W8ktFMAT_j13q7nJZVN8ug402ILJjuBZA,3771
5
+ pytest_regtest/pytest_regtest.py,sha256=umtd97AIpY4FpaYJCd741mTlJbrToWkUfrkoKCvHDAE,22135
6
+ pytest_regtest/register_third_party_handlers.py,sha256=mfmcyeMKuxFykkKLO7T1Tz5RPitn1pKLYRM7B9lA1Kg,1132
7
+ pytest_regtest/snapshot_handler.py,sha256=5q1oMisifbQ_i8LCSPsD1OxPVEHVUIWwZeEDcZcBNDI,6036
8
+ pytest_regtest/utils.py,sha256=2jYTlV_qL5hH6FCeg7T1HJJvKual-Kux2scJ9_aB1lY,811
9
+ pytest_regtest-2.3.0.dist-info/METADATA,sha256=AJQuXnr5Dqc2mcGI5zdXdOoxRs227YIa7owdSFY2SQU,4097
10
+ pytest_regtest-2.3.0.dist-info/WHEEL,sha256=fl6v0VwpzfGBVsGtkAkhILUlJxROXbA3HvRL6Fe3140,105
11
+ pytest_regtest-2.3.0.dist-info/entry_points.txt,sha256=4VuIhXeMGhDo0ATbaUfyjND0atofmZjV_P-o6_uEk2s,36
12
+ pytest_regtest-2.3.0.dist-info/licenses/LICENSE.txt,sha256=Tue36uAzpW79-9WAqzkwPhsDDVd1X-VWUmdZ0MfGYvk,1068
13
+ pytest_regtest-2.3.0.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- pytest_regtest/__init__.py,sha256=d-L1wYyQqhts4dHKV5SnOqlhyjk6I7Yva78xgDrS8Zk,2408
2
- pytest_regtest/numpy_handler.py,sha256=UZyp9GqO07Kq07qgNSGJq5JXw2vUlgfljGeoxco95Mk,6919
3
- pytest_regtest/pandas_handler.py,sha256=wlCVYLQU_CJ0x6r9Nok3ToF4CXm8eydBkoil-Nu2rEk,4249
4
- pytest_regtest/pytest_regtest.py,sha256=NfeOsO-YtnEq0K7fltTQMvFmA4EP6QWlqg3r59ABLyw,22344
5
- pytest_regtest/register_third_party_handlers.py,sha256=UboIatgRB8I2uvZyL-4BfRLZw5YDyh2wpElP8jeOH10,755
6
- pytest_regtest/snapshot_handler.py,sha256=QxPKP8fNA69OEFUcfM0OlYUzd3cYrMgSOqzRzbYxpxM,6085
7
- pytest_regtest/utils.py,sha256=2jYTlV_qL5hH6FCeg7T1HJJvKual-Kux2scJ9_aB1lY,811
8
- pytest_regtest-2.2.1.dist-info/METADATA,sha256=6qoBsWn6A8m37ff-PnqJRnivHf1KjlAvDnuaS23GZy0,3302
9
- pytest_regtest-2.2.1.dist-info/WHEEL,sha256=fl6v0VwpzfGBVsGtkAkhILUlJxROXbA3HvRL6Fe3140,105
10
- pytest_regtest-2.2.1.dist-info/entry_points.txt,sha256=4VuIhXeMGhDo0ATbaUfyjND0atofmZjV_P-o6_uEk2s,36
11
- pytest_regtest-2.2.1.dist-info/licenses/LICENSE.txt,sha256=Tue36uAzpW79-9WAqzkwPhsDDVd1X-VWUmdZ0MfGYvk,1068
12
- pytest_regtest-2.2.1.dist-info/RECORD,,