pytest-arraydiff 0.5.0__py3-none-any.whl → 0.6.1__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.
@@ -28,9 +28,6 @@
28
28
  #
29
29
  # https://github.com/astrofrog/pytest-mpl
30
30
 
31
-
32
- from functools import wraps
33
-
34
31
  import os
35
32
  import abc
36
33
  import shutil
@@ -116,8 +113,9 @@ class FITSDiff(BaseDiff):
116
113
  from astropy.io.fits.diff import FITSDiff
117
114
  from astropy.utils.introspection import minversion
118
115
  if minversion(astropy, '2.0'):
119
- diff = FITSDiff(reference_file, test_file, rtol=rtol)
116
+ diff = FITSDiff(reference_file, test_file, rtol=rtol, atol=atol)
120
117
  else:
118
+ # `atol` is not supported prior to Astropy 2.0
121
119
  diff = FITSDiff(reference_file, test_file, tolerance=rtol)
122
120
  return diff.identical, diff.report()
123
121
 
@@ -137,9 +135,43 @@ class TextDiff(SimpleArrayDiff):
137
135
  return np.savetxt(filename, data, **kwargs)
138
136
 
139
137
 
138
+ class PDHDFDiff(BaseDiff):
139
+
140
+ extension = 'h5'
141
+
142
+ @staticmethod
143
+ def read(filename):
144
+ import pandas as pd
145
+ return pd.read_hdf(filename)
146
+
147
+ @staticmethod
148
+ def write(filename, data, **kwargs):
149
+ import pandas as pd # noqa: F401
150
+ key = os.path.basename(filename).replace('.h5', '')
151
+ return data.to_hdf(filename, key=key, **kwargs)
152
+
153
+ @classmethod
154
+ def compare(cls, reference_file, test_file, atol=None, rtol=None):
155
+ import pandas.testing as pdt
156
+ import pandas as pd
157
+
158
+ ref_data = pd.read_hdf(reference_file)
159
+ test_data = pd.read_hdf(test_file)
160
+ try:
161
+ pdt.assert_frame_equal(ref_data, test_data)
162
+ except AssertionError as exc:
163
+ message = "\n\na: {0}".format(test_file) + '\n'
164
+ message += "b: {0}".format(reference_file) + '\n'
165
+ message += exc.args[0]
166
+ return False, message
167
+ else:
168
+ return True, ""
169
+
170
+
140
171
  FORMATS = {}
141
172
  FORMATS['fits'] = FITSDiff
142
173
  FORMATS['text'] = TextDiff
174
+ FORMATS['pd_hdf'] = PDHDFDiff
143
175
 
144
176
 
145
177
  def _download_file(url):
@@ -166,7 +198,7 @@ def pytest_addoption(parser):
166
198
  def pytest_configure(config):
167
199
  config.getini('markers').append(
168
200
  'array_compare: for functions using array comparison')
169
-
201
+
170
202
  if config.getoption("--arraydiff") or config.getoption("--arraydiff-generate-path") is not None:
171
203
 
172
204
  reference_dir = config.getoption("--arraydiff-reference-path")
@@ -186,6 +218,37 @@ def pytest_configure(config):
186
218
  reference_dir=reference_dir,
187
219
  generate_dir=generate_dir,
188
220
  default_format=default_format))
221
+ else:
222
+ config.pluginmanager.register(ArrayInterceptor(config))
223
+
224
+
225
+ def generate_test_name(item):
226
+ """
227
+ Generate a unique name for this test.
228
+ """
229
+ if item.cls is not None:
230
+ name = f"{item.module.__name__}.{item.cls.__name__}.{item.name}"
231
+ else:
232
+ name = f"{item.module.__name__}.{item.name}"
233
+ return name
234
+
235
+
236
+ def wrap_array_interceptor(plugin, item):
237
+ """
238
+ Intercept and store arrays returned by test functions.
239
+ """
240
+ # Only intercept array on marked array tests
241
+ if item.get_closest_marker('array_compare') is not None:
242
+
243
+ # Use the full test name as a key to ensure correct array is being retrieved
244
+ test_name = generate_test_name(item)
245
+
246
+ def array_interceptor(store, obj):
247
+ def wrapper(*args, **kwargs):
248
+ store.return_value[test_name] = obj(*args, **kwargs)
249
+ return wrapper
250
+
251
+ item.obj = array_interceptor(plugin, item.obj)
189
252
 
190
253
 
191
254
  class ArrayComparison(object):
@@ -195,12 +258,15 @@ class ArrayComparison(object):
195
258
  self.reference_dir = reference_dir
196
259
  self.generate_dir = generate_dir
197
260
  self.default_format = default_format
261
+ self.return_value = {}
198
262
 
199
- def pytest_runtest_setup(self, item):
263
+ @pytest.hookimpl(hookwrapper=True)
264
+ def pytest_runtest_call(self, item):
200
265
 
201
266
  compare = item.get_closest_marker('array_compare')
202
267
 
203
268
  if compare is None:
269
+ yield
204
270
  return
205
271
 
206
272
  file_format = compare.kwargs.get('file_format', self.default_format)
@@ -220,85 +286,97 @@ class ArrayComparison(object):
220
286
 
221
287
  write_kwargs = compare.kwargs.get('write_kwargs', {})
222
288
 
223
- original = item.function
289
+ reference_dir = compare.kwargs.get('reference_dir', None)
290
+ if reference_dir is None:
291
+ if self.reference_dir is None:
292
+ reference_dir = os.path.join(os.path.dirname(item.fspath.strpath), 'reference')
293
+ else:
294
+ reference_dir = self.reference_dir
295
+ else:
296
+ if not reference_dir.startswith(('http://', 'https://')):
297
+ reference_dir = os.path.join(os.path.dirname(item.fspath.strpath), reference_dir)
298
+
299
+ baseline_remote = reference_dir.startswith('http')
224
300
 
225
- @wraps(item.function)
226
- def item_function_wrapper(*args, **kwargs):
301
+ # Run test and get array object
302
+ wrap_array_interceptor(self, item)
303
+ yield
304
+ test_name = generate_test_name(item)
305
+ if test_name not in self.return_value:
306
+ # Test function did not complete successfully
307
+ return
308
+ array = self.return_value[test_name]
227
309
 
228
- reference_dir = compare.kwargs.get('reference_dir', None)
229
- if reference_dir is None:
230
- if self.reference_dir is None:
231
- reference_dir = os.path.join(os.path.dirname(item.fspath.strpath), 'reference')
232
- else:
233
- reference_dir = self.reference_dir
310
+ # Find test name to use as plot name
311
+ filename = compare.kwargs.get('filename', None)
312
+ if filename is None:
313
+ if single_reference:
314
+ filename = item.originalname + '.' + extension
234
315
  else:
235
- if not reference_dir.startswith(('http://', 'https://')):
236
- reference_dir = os.path.join(os.path.dirname(item.fspath.strpath), reference_dir)
237
-
238
- baseline_remote = reference_dir.startswith('http')
239
-
240
- # Run test and get figure object
241
- import inspect
242
- if inspect.ismethod(original): # method
243
- array = original(*args[1:], **kwargs)
244
- else: # function
245
- array = original(*args, **kwargs)
246
-
247
- # Find test name to use as plot name
248
- filename = compare.kwargs.get('filename', None)
249
- if filename is None:
250
- if single_reference:
251
- filename = original.__name__ + '.' + extension
252
- else:
253
- filename = item.name + '.' + extension
254
- filename = filename.replace('[', '_').replace(']', '_')
255
- filename = filename.replace('_.' + extension, '.' + extension)
256
-
257
- # What we do now depends on whether we are generating the reference
258
- # files or simply running the test.
259
- if self.generate_dir is None:
260
-
261
- # Save the figure
262
- result_dir = tempfile.mkdtemp()
263
- test_array = os.path.abspath(os.path.join(result_dir, filename))
264
-
265
- FORMATS[file_format].write(test_array, array, **write_kwargs)
266
-
267
- # Find path to baseline array
268
- if baseline_remote:
269
- baseline_file_ref = _download_file(reference_dir + filename)
270
- else:
271
- baseline_file_ref = os.path.abspath(os.path.join(os.path.dirname(item.fspath.strpath), reference_dir, filename))
272
-
273
- if not os.path.exists(baseline_file_ref):
274
- raise Exception("""File not found for comparison test
275
- Generated file:
276
- \t{test}
277
- This is expected for new tests.""".format(
278
- test=test_array))
279
-
280
- # setuptools may put the baseline arrays in non-accessible places,
281
- # copy to our tmpdir to be sure to keep them in case of failure
282
- baseline_file = os.path.abspath(os.path.join(result_dir, 'reference-' + filename))
283
- shutil.copyfile(baseline_file_ref, baseline_file)
284
-
285
- identical, msg = FORMATS[file_format].compare(baseline_file, test_array, atol=atol, rtol=rtol)
286
-
287
- if identical:
288
- shutil.rmtree(result_dir)
289
- else:
290
- raise Exception(msg)
316
+ filename = item.name + '.' + extension
317
+ filename = filename.replace('[', '_').replace(']', '_')
318
+ filename = filename.replace('_.' + extension, '.' + extension)
319
+
320
+ # What we do now depends on whether we are generating the reference
321
+ # files or simply running the test.
322
+ if self.generate_dir is None:
323
+
324
+ # Save the figure
325
+ result_dir = tempfile.mkdtemp()
326
+ test_array = os.path.abspath(os.path.join(result_dir, filename))
327
+
328
+ FORMATS[file_format].write(test_array, array, **write_kwargs)
291
329
 
330
+ # Find path to baseline array
331
+ if baseline_remote:
332
+ baseline_file_ref = _download_file(reference_dir + filename)
292
333
  else:
334
+ baseline_file_ref = os.path.abspath(os.path.join(os.path.dirname(item.fspath.strpath), reference_dir, filename))
293
335
 
294
- if not os.path.exists(self.generate_dir):
295
- os.makedirs(self.generate_dir)
336
+ if not os.path.exists(baseline_file_ref):
337
+ raise Exception("""File not found for comparison test
338
+ Generated file:
339
+ \t{test}
340
+ This is expected for new tests.""".format(
341
+ test=test_array))
296
342
 
297
- FORMATS[file_format].write(os.path.abspath(os.path.join(self.generate_dir, filename)), array, **write_kwargs)
343
+ # setuptools may put the baseline arrays in non-accessible places,
344
+ # copy to our tmpdir to be sure to keep them in case of failure
345
+ baseline_file = os.path.abspath(os.path.join(result_dir, 'reference-' + filename))
346
+ shutil.copyfile(baseline_file_ref, baseline_file)
298
347
 
299
- pytest.skip("Skipping test, since generating data")
348
+ identical, msg = FORMATS[file_format].compare(baseline_file, test_array, atol=atol, rtol=rtol)
349
+
350
+ if identical:
351
+ shutil.rmtree(result_dir)
352
+ else:
353
+ raise Exception(msg)
300
354
 
301
- if item.cls is not None:
302
- setattr(item.cls, item.function.__name__, item_function_wrapper)
303
355
  else:
304
- item.obj = item_function_wrapper
356
+
357
+ if not os.path.exists(self.generate_dir):
358
+ os.makedirs(self.generate_dir)
359
+
360
+ FORMATS[file_format].write(os.path.abspath(os.path.join(self.generate_dir, filename)), array, **write_kwargs)
361
+
362
+ pytest.skip("Skipping test, since generating data")
363
+
364
+
365
+ class ArrayInterceptor:
366
+ """
367
+ This is used in place of ArrayComparison when the array comparison option is not used,
368
+ to make sure that we still intercept arrays returned by tests.
369
+ """
370
+
371
+ def __init__(self, config):
372
+ self.config = config
373
+ self.return_value = {}
374
+
375
+ @pytest.hookimpl(hookwrapper=True)
376
+ def pytest_runtest_call(self, item):
377
+
378
+ if item.get_closest_marker('array_compare') is not None:
379
+ wrap_array_interceptor(self, item)
380
+
381
+ yield
382
+ return
@@ -1,5 +1,16 @@
1
- # coding: utf-8
2
1
  # file generated by setuptools_scm
3
2
  # don't change, don't track in version control
4
- version = '0.5.0'
5
- version_tuple = (0, 5, 0)
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.6.1'
16
+ __version_tuple__ = version_tuple = (0, 6, 1)
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytest-arraydiff
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: pytest plugin to help with comparing array output from tests
5
5
  Home-page: https://github.com/astropy/pytest-arraydiff
6
6
  Author: The Astropy Developers
7
7
  Author-email: astropy.team@gmail.com
8
8
  License: BSD
9
- Platform: UNKNOWN
10
9
  Classifier: Development Status :: 4 - Beta
11
10
  Classifier: Framework :: Pytest
12
11
  Classifier: Intended Audience :: Developers
@@ -15,19 +14,24 @@ Classifier: Operating System :: OS Independent
15
14
  Classifier: Programming Language :: Python
16
15
  Classifier: Programming Language :: Python :: 3
17
16
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.6
19
17
  Classifier: Programming Language :: Python :: 3.7
20
18
  Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
21
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
22
24
  Classifier: Topic :: Software Development :: Testing
23
25
  Classifier: Topic :: Utilities
24
26
  Requires-Python: >=3.7
25
27
  Description-Content-Type: text/x-rst
26
28
  License-File: LICENSE
27
- Requires-Dist: pytest (>=4.6)
29
+ Requires-Dist: pytest >=4.6
28
30
  Requires-Dist: numpy
29
31
  Provides-Extra: test
30
32
  Requires-Dist: astropy ; extra == 'test'
33
+ Requires-Dist: pandas ; extra == 'test'
34
+ Requires-Dist: tables ; extra == 'test'
31
35
 
32
36
  .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.5811772.svg
33
37
  :target: https://doi.org/10.5281/zenodo.5811772
@@ -37,6 +41,10 @@ Requires-Dist: astropy ; extra == 'test'
37
41
  :target: https://github.com/astropy/pytest-arraydiff/actions
38
42
  :alt: CI Status
39
43
 
44
+ .. image:: https://img.shields.io/pypi/v/pytest-arraydiff.svg
45
+ :target: https://pypi.org/project/pytest-arraydiff
46
+ :alt: PyPI Status
47
+
40
48
  About
41
49
  -----
42
50
 
@@ -46,7 +54,8 @@ in cases where the arrays are too large to conveniently hard-code them
46
54
  in the tests (e.g. ``np.testing.assert_allclose(x, [1, 2, 3])``).
47
55
 
48
56
  The basic idea is that you can write a test that generates a Numpy array (or
49
- other related objects depending on the format). You can then either run the
57
+ other related objects depending on the format, e.g. pandas DataFrame).
58
+ You can then either run the
50
59
  tests in a mode to **generate** reference files from the arrays, or you can run
51
60
  the tests in **comparison** mode, which will compare the results of the tests to
52
61
  the reference ones within some tolerance.
@@ -56,6 +65,7 @@ At the moment, the supported file formats for the reference files are:
56
65
  - A plain text-based format (based on Numpy ``loadtxt`` output)
57
66
  - The FITS format (requires `astropy <http://www.astropy.org>`__). With this
58
67
  format, tests can return either a Numpy array for a FITS HDU object.
68
+ - A pandas HDF5 format using the pandas HDFStore
59
69
 
60
70
  For more information on how to write tests to do this, see the **Using**
61
71
  section below.
@@ -140,12 +150,12 @@ The supported formats at this time are ``text`` and ``fits``, and
140
150
  contributions for other formats are welcome. The default format is
141
151
  ``text``.
142
152
 
143
- Another argument is the relative tolerance for floating point values
144
- (which defaults to 1e-7):
153
+ Additional arguments are the relative and absolute tolerances for floating
154
+ point values (which default to 1e-7 and 0, respectively):
145
155
 
146
156
  .. code:: python
147
157
 
148
- @pytest.mark.array_compare(rtol=20)
158
+ @pytest.mark.array_compare(rtol=20, atol=0.1)
149
159
  def test_array():
150
160
  ...
151
161
 
@@ -223,5 +233,3 @@ install the latest version of the plugin then do::
223
233
 
224
234
  The reason for having to install the plugin first is to ensure that the
225
235
  plugin is correctly loaded as part of the test suite.
226
-
227
-
@@ -0,0 +1,9 @@
1
+ pytest_arraydiff/__init__.py,sha256=ICVlMfJSyUfBfTrbT0iaRCQuzdTOdpNCGyxVOwgxXzQ,117
2
+ pytest_arraydiff/plugin.py,sha256=EiHNkfhbpbCaWj75nXsjVftCerQ84VT7__PofriTd0Q,13342
3
+ pytest_arraydiff/version.py,sha256=lgiCYGSijhLK71WmuudWf_AyhNAutwQWx2V8bV6a5VQ,411
4
+ pytest_arraydiff-0.6.1.dist-info/LICENSE,sha256=sx9xdEnc-QGbdeT_denLAE1qpXXXIO0qP2TRH-4sKe8,1448
5
+ pytest_arraydiff-0.6.1.dist-info/METADATA,sha256=iM0h6TII_DGeAKR6ulxHPw9_kC_s6RyKSx7QiZJtg7o,8545
6
+ pytest_arraydiff-0.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
7
+ pytest_arraydiff-0.6.1.dist-info/entry_points.txt,sha256=1mmqAdBTczR-Jj1RtaLXIdnibdhQBqaNCzRSaxhTRpk,54
8
+ pytest_arraydiff-0.6.1.dist-info/top_level.txt,sha256=7fVOoIyJHVuSQkxQKVkfifGCXbQoyqMWSgzmwEBeegQ,17
9
+ pytest_arraydiff-0.6.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,2 @@
1
1
  [pytest11]
2
2
  pytest_arraydiff = pytest_arraydiff.plugin
3
-
@@ -1,9 +0,0 @@
1
- pytest_arraydiff/__init__.py,sha256=ICVlMfJSyUfBfTrbT0iaRCQuzdTOdpNCGyxVOwgxXzQ,117
2
- pytest_arraydiff/plugin.py,sha256=ezuiW3IGwWd1if22VHKqeqLYnnAPQOH6HD5nbcj2C2c,11206
3
- pytest_arraydiff/version.py,sha256=qOgp63DKbJUTEpeNFSC6-4Z4ggiiiujx7_nJvXchvEg,142
4
- pytest_arraydiff-0.5.0.dist-info/LICENSE,sha256=sx9xdEnc-QGbdeT_denLAE1qpXXXIO0qP2TRH-4sKe8,1448
5
- pytest_arraydiff-0.5.0.dist-info/METADATA,sha256=JIXG_b4AZHhfTlov64bcGiiEN3wz_2Uc5xMoQGGj_JM,8073
6
- pytest_arraydiff-0.5.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
7
- pytest_arraydiff-0.5.0.dist-info/entry_points.txt,sha256=9IUCpViMZtfu6JLjdCBtUGPGirih2uwOewuqqXzqpeQ,55
8
- pytest_arraydiff-0.5.0.dist-info/top_level.txt,sha256=7fVOoIyJHVuSQkxQKVkfifGCXbQoyqMWSgzmwEBeegQ,17
9
- pytest_arraydiff-0.5.0.dist-info/RECORD,,