Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
test_deprecations.py461 linesDownload Raw Back to tests
1"""
2Tests related to deprecation warnings. Also a convenient place
3to document how deprecations should eventually be turned into errors.
4
5"""
6import contextlib
7import warnings
8
9import pytest
10
11import numpy as np
12import numpy._core._struct_ufunc_tests as struct_ufunc
13from numpy._core._multiarray_tests import fromstring_null_term_c_api  # noqa: F401
14from numpy.testing import assert_raises
15
16
17class _DeprecationTestCase:
18    # Just as warning: warnings uses re.match, so the start of this message
19    # must match.
20    message = ''
21    warning_cls = DeprecationWarning
22
23    @contextlib.contextmanager
24    def filter_warnings(self):
25        with warnings.catch_warnings(record=True) as w:
26            # Do *not* ignore other DeprecationWarnings. Ignoring warnings
27            # can give very confusing results because of
28            # https://bugs.python.org/issue4180 and it is probably simplest to
29            # try to keep the tests cleanly giving only the right warning type.
30            # (While checking them set to "error" those are ignored anyway)
31            # We still have them show up, because otherwise they would be raised
32            warnings.filterwarnings("always", category=self.warning_cls)
33            warnings.filterwarnings("always", message=self.message,
34                                    category=self.warning_cls)
35            yield w
36        return
37
38    def assert_deprecated(self, function, num=1, ignore_others=False,
39                          function_fails=False,
40                          exceptions=np._NoValue,
41                          args=(), kwargs={}):
42        """Test if DeprecationWarnings are given and raised.
43
44        This first checks if the function when called gives `num`
45        DeprecationWarnings, after that it tries to raise these
46        DeprecationWarnings and compares them with `exceptions`.
47        The exceptions can be different for cases where this code path
48        is simply not anticipated and the exception is replaced.
49
50        Parameters
51        ----------
52        function : callable
53            The function to test
54        num : int
55            Number of DeprecationWarnings to expect. This should normally be 1.
56        ignore_others : bool
57            Whether warnings of the wrong type should be ignored (note that
58            the message is not checked)
59        function_fails : bool
60            If the function would normally fail, setting this will check for
61            warnings inside a try/except block.
62        exceptions : Exception or tuple of Exceptions
63            Exception to expect when turning the warnings into an error.
64            The default checks for DeprecationWarnings. If exceptions is
65            empty the function is expected to run successfully.
66        args : tuple
67            Arguments for `function`
68        kwargs : dict
69            Keyword arguments for `function`
70        """
71        __tracebackhide__ = True  # Hide traceback for py.test
72
73        if exceptions is np._NoValue:
74            exceptions = (self.warning_cls,)
75
76        if function_fails:
77            context_manager = contextlib.suppress(Exception)
78        else:
79            context_manager = contextlib.nullcontext()
80        with context_manager:
81            with self.filter_warnings() as w_context:
82                function(*args, **kwargs)
83
84        # just in case, clear the registry
85        num_found = 0
86        for warning in w_context:
87            if warning.category is self.warning_cls:
88                num_found += 1
89            elif not ignore_others:
90                raise AssertionError(
91                        "expected %s but got: %s" %
92                        (self.warning_cls.__name__, warning.category))
93        if num is not None and num_found != num:
94            msg = f"{len(w_context)} warnings found but {num} expected."
95            lst = [str(w) for w in w_context]
96            raise AssertionError("\n".join([msg] + lst))
97
98        with warnings.catch_warnings():
99            warnings.filterwarnings("error", message=self.message,
100                                    category=self.warning_cls)
101            try:
102                function(*args, **kwargs)
103                if exceptions != ():
104                    raise AssertionError(
105                            "No error raised during function call")
106            except exceptions:
107                if exceptions == ():
108                    raise AssertionError(
109                            "Error raised during function call")
110
111    def assert_not_deprecated(self, function, args=(), kwargs={}):
112        """Test that warnings are not raised.
113
114        This is just a shorthand for:
115
116        self.assert_deprecated(function, num=0, ignore_others=True,
117                        exceptions=tuple(), args=args, kwargs=kwargs)
118        """
119        self.assert_deprecated(function, num=0, ignore_others=True,
120                        exceptions=(), args=args, kwargs=kwargs)
121
122
123class _VisibleDeprecationTestCase(_DeprecationTestCase):
124    warning_cls = np.exceptions.VisibleDeprecationWarning
125
126
127class TestTestDeprecated:
128    def test_assert_deprecated(self):
129        test_case_instance = _DeprecationTestCase()
130        assert_raises(AssertionError,
131                      test_case_instance.assert_deprecated,
132                      lambda: None)
133
134        def foo():
135            warnings.warn("foo", category=DeprecationWarning, stacklevel=2)
136
137        test_case_instance.assert_deprecated(foo)
138
139
140class TestBincount(_DeprecationTestCase):
141    # 2024-07-29, 2.1.0
142    @pytest.mark.parametrize('badlist', [[0.5, 1.2, 1.5],
143                                         ['0', '1', '1']])
144    def test_bincount_bad_list(self, badlist):
145        self.assert_deprecated(lambda: np.bincount(badlist))
146
147
148class BuiltInRoundComplexDType(_DeprecationTestCase):
149    # 2020-03-31 1.19.0
150    deprecated_types = [np.csingle, np.cdouble, np.clongdouble]
151    not_deprecated_types = [
152        np.int8, np.int16, np.int32, np.int64,
153        np.uint8, np.uint16, np.uint32, np.uint64,
154        np.float16, np.float32, np.float64,
155    ]
156
157    def test_deprecated(self):
158        for scalar_type in self.deprecated_types:
159            scalar = scalar_type(0)
160            self.assert_deprecated(round, args=(scalar,))
161            self.assert_deprecated(round, args=(scalar, 0))
162            self.assert_deprecated(round, args=(scalar,), kwargs={'ndigits': 0})
163
164    def test_not_deprecated(self):
165        for scalar_type in self.not_deprecated_types:
166            scalar = scalar_type(0)
167            self.assert_not_deprecated(round, args=(scalar,))
168            self.assert_not_deprecated(round, args=(scalar, 0))
169            self.assert_not_deprecated(round, args=(scalar,), kwargs={'ndigits': 0})
170
171
172class FlatteningConcatenateUnsafeCast(_DeprecationTestCase):
173    # NumPy 1.20, 2020-09-03
174    message = "concatenate with `axis=None` will use same-kind casting"
175
176    def test_deprecated(self):
177        self.assert_deprecated(np.concatenate,
178                args=(([0.], [1.]),),
179                kwargs={'axis': None, 'out': np.empty(2, dtype=np.int64)})
180
181    def test_not_deprecated(self):
182        self.assert_not_deprecated(np.concatenate,
183                args=(([0.], [1.]),),
184                kwargs={'axis': None, 'out': np.empty(2, dtype=np.int64),
185                        'casting': "unsafe"})
186
187        with assert_raises(TypeError):
188            # Tests should notice if the deprecation warning is given first...
189            np.concatenate(([0.], [1.]), out=np.empty(2, dtype=np.int64),
190                           casting="same_kind")
191
192
193class TestCtypesGetter(_DeprecationTestCase):
194    ctypes = np.array([1]).ctypes
195
196    @pytest.mark.parametrize("name", ["data", "shape", "strides", "_as_parameter_"])
197    def test_not_deprecated(self, name: str) -> None:
198        self.assert_not_deprecated(lambda: getattr(self.ctypes, name))
199
200
201class TestPyIntConversion(_DeprecationTestCase):
202    message = r".*stop allowing conversion of out-of-bound.*"
203
204    @pytest.mark.parametrize("dtype", np.typecodes["AllInteger"])
205    def test_deprecated_scalar(self, dtype):
206        dtype = np.dtype(dtype)
207        info = np.iinfo(dtype)
208
209        # Cover the most common creation paths (all end up in the
210        # same place):
211        def scalar(value, dtype):
212            dtype.type(value)
213
214        def assign(value, dtype):
215            arr = np.array([0, 0, 0], dtype=dtype)
216            arr[2] = value
217
218        def create(value, dtype):
219            np.array([value], dtype=dtype)
220
221        for creation_func in [scalar, assign, create]:
222            try:
223                self.assert_deprecated(
224                        lambda: creation_func(info.min - 1, dtype))
225            except OverflowError:
226                pass  # OverflowErrors always happened also before and are OK.
227
228            try:
229                self.assert_deprecated(
230                        lambda: creation_func(info.max + 1, dtype))
231            except OverflowError:
232                pass  # OverflowErrors always happened also before and are OK.
233
234
235@pytest.mark.parametrize("name", ["str", "bytes", "object"])
236def test_future_scalar_attributes(name):
237    # FutureWarning added 2022-11-17, NumPy 1.24,
238    assert name not in dir(np)  # we may want to not add them
239    with pytest.warns(FutureWarning,
240            match=f"In the future .*{name}"):
241        assert not hasattr(np, name)
242
243    # Unfortunately, they are currently still valid via `np.dtype()`
244    np.dtype(name)
245    name in np._core.sctypeDict
246
247
248# Ignore the above future attribute warning for this test.
249@pytest.mark.filterwarnings("ignore:In the future:FutureWarning")
250class TestRemovedGlobals:
251    # Removed 2023-01-12, NumPy 1.24.0
252    # Not a deprecation, but the large error was added to aid those who missed
253    # the previous deprecation, and should be removed similarly to one
254    # (or faster).
255    @pytest.mark.parametrize("name",
256            ["object", "float", "complex", "str", "int"])
257    def test_attributeerror_includes_info(self, name):
258        msg = f".*\n`np.{name}` was a deprecated alias for the builtin"
259        with pytest.raises(AttributeError, match=msg):
260            getattr(np, name)
261
262
263class TestDeprecatedFinfo(_DeprecationTestCase):
264    # Deprecated in NumPy 1.25, 2023-01-16
265    def test_deprecated_none(self):
266        self.assert_deprecated(np.finfo, args=(None,))
267
268
269class TestMathAlias(_DeprecationTestCase):
270    def test_deprecated_np_lib_math(self):
271        self.assert_deprecated(lambda: np.lib.math)
272
273
274class TestLibImports(_DeprecationTestCase):
275    # Deprecated in Numpy 1.26.0, 2023-09
276    def test_lib_functions_deprecation_call(self):
277        from numpy import row_stack
278        from numpy._core.numerictypes import maximum_sctype
279        from numpy.lib._npyio_impl import recfromcsv, recfromtxt
280        from numpy.lib._shape_base_impl import get_array_wrap
281        from numpy.lib._utils_impl import safe_eval
282        from numpy.lib.tests.test_io import TextIO
283
284        self.assert_deprecated(lambda: safe_eval("None"))
285
286        data_gen = lambda: TextIO('A,B\n0,1\n2,3')
287        kwargs = {'delimiter': ",", 'missing_values': "N/A", 'names': True}
288        self.assert_deprecated(lambda: recfromcsv(data_gen()))
289        self.assert_deprecated(lambda: recfromtxt(data_gen(), **kwargs))
290
291        self.assert_deprecated(get_array_wrap)
292        self.assert_deprecated(lambda: maximum_sctype(int))
293
294        self.assert_deprecated(lambda: row_stack([[]]))
295        self.assert_deprecated(lambda: np.chararray)
296
297
298class TestDeprecatedDTypeAliases(_DeprecationTestCase):
299
300    def _check_for_warning(self, func):
301        with pytest.warns(DeprecationWarning,
302                          match="alias 'a' was deprecated in NumPy 2.0") as w:
303            func()
304        assert len(w) == 1
305
306    def test_a_dtype_alias(self):
307        for dtype in ["a", "a10"]:
308            f = lambda: np.dtype(dtype)
309            self._check_for_warning(f)
310            self.assert_deprecated(f)
311            f = lambda: np.array(["hello", "world"]).astype("a10")
312            self._check_for_warning(f)
313            self.assert_deprecated(f)
314
315
316class TestDeprecatedArrayWrap(_DeprecationTestCase):
317    message = "__array_wrap__.*"
318
319    def test_deprecated(self):
320        class Test1:
321            def __array__(self, dtype=None, copy=None):
322                return np.arange(4)
323
324            def __array_wrap__(self, arr, context=None):
325                self.called = True
326                return 'pass context'
327
328        class Test2(Test1):
329            def __array_wrap__(self, arr):
330                self.called = True
331                return 'pass'
332
333        test1 = Test1()
334        test2 = Test2()
335        self.assert_deprecated(lambda: np.negative(test1))
336        assert test1.called
337        self.assert_deprecated(lambda: np.negative(test2))
338        assert test2.called
339
340class TestDeprecatedArrayAttributeSetting(_DeprecationTestCase):
341    message = "Setting the .*on a NumPy array has been deprecated.*"
342
343    def test_deprecated_strides_set(self):
344        x = np.eye(2)
345        self.assert_deprecated(setattr, args=(x, 'strides', x.strides))
346
347
348class TestDeprecatedDTypeParenthesizedRepeatCount(_DeprecationTestCase):
349    message = "Passing in a parenthesized single number"
350
351    @pytest.mark.parametrize("string", ["(2)i,", "(3)3S,", "f,(2)f"])
352    def test_parenthesized_repeat_count(self, string):
353        self.assert_deprecated(np.dtype, args=(string,))
354
355
356class TestAddNewdocUFunc(_DeprecationTestCase):
357    # Deprecated in Numpy 2.2, 2024-11
358    @pytest.mark.thread_unsafe(
359        reason="modifies and checks docstring which is global state"
360    )
361    def test_deprecated(self):
362        doc = struct_ufunc.add_triplet.__doc__
363        # gh-26718
364        # This test mutates the C-level docstring pointer for add_triplet,
365        # which is permanent once set. Skip when re-running tests.
366        if doc is not None and "new docs" in doc:
367            pytest.skip("Cannot retest deprecation, otherwise ValueError: "
368                "Cannot change docstring of ufunc with non-NULL docstring")
369        self.assert_deprecated(
370            lambda: np._core.umath._add_newdoc_ufunc(
371                struct_ufunc.add_triplet, "new docs"
372            )
373        )
374
375
376class TestDTypeAlignBool(_VisibleDeprecationTestCase):
377    # Deprecated in Numpy 2.4, 2025-07
378    # NOTE: As you can see, finalizing this deprecation breaks some (very) old
379    # pickle files.  This may be fine, but needs to be done with some care since
380    # it breaks all of them and not just some.
381    # (Maybe it should be a 3.0 or only after warning more explicitly around pickles.)
382    message = r"dtype\(\): align should be passed as Python or NumPy boolean but got "
383
384    def test_deprecated(self):
385        # in particular integers should be rejected because one may think they mean
386        # alignment, or pass them accidentally as a subarray shape (meaning to pass
387        # a tuple).
388        self.assert_deprecated(lambda: np.dtype("f8", align=3))
389
390    @pytest.mark.parametrize("align", [True, False, np.True_, np.False_])
391    def test_not_deprecated(self, align):
392        # if the user passes a bool, it is accepted.
393        self.assert_not_deprecated(lambda: np.dtype("f8", align=align))
394
395
396class TestFlatiterIndexing0dBoolIndex(_DeprecationTestCase):
397    # Deprecated in Numpy 2.4, 2025-07
398    message = r"Indexing flat iterators with a 0-dimensional boolean index"
399
400    def test_0d_boolean_index_deprecated(self):
401        arr = np.arange(3)
402        # 0d boolean indices on flat iterators are deprecated
403        self.assert_deprecated(lambda: arr.flat[True])
404
405    def test_0d_boolean_assign_index_deprecated(self):
406        arr = np.arange(3)
407
408        def assign_to_index():
409            arr.flat[True] = 10
410
411        self.assert_deprecated(assign_to_index)
412
413
414class TestFlatiterIndexingFloatIndex(_DeprecationTestCase):
415    # Deprecated in NumPy 2.4, 2025-07
416    message = r"Invalid non-array indices for iterator objects"
417
418    def test_float_index_deprecated(self):
419        arr = np.arange(3)
420        # float indices on flat iterators are deprecated
421        self.assert_deprecated(lambda: arr.flat[[1.]])
422
423    def test_float_assign_index_deprecated(self):
424        arr = np.arange(3)
425
426        def assign_to_index():
427            arr.flat[[1.]] = 10
428
429        self.assert_deprecated(assign_to_index)
430
431
432@pytest.mark.thread_unsafe(
433    reason="warning control utilities are deprecated due to being thread-unsafe"
434)
435class TestWarningUtilityDeprecations(_DeprecationTestCase):
436    # Deprecation in NumPy 2.4, 2025-08
437    message = r"NumPy warning suppression and assertion utilities are deprecated."
438
439    def test_assert_warns_deprecated(self):
440        def use_assert_warns():
441            with np.testing.assert_warns(RuntimeWarning):
442                warnings.warn("foo", RuntimeWarning, stacklevel=1)
443
444        self.assert_deprecated(use_assert_warns)
445
446    def test_suppress_warnings_deprecated(self):
447        def use_suppress_warnings():
448            with np.testing.suppress_warnings() as sup:
449                sup.filter(RuntimeWarning, 'invalid value encountered in divide')
450
451        self.assert_deprecated(use_suppress_warnings)
452
453
454class TestTooManyArgsExtremum(_DeprecationTestCase):
455    # Deprecated in Numpy 2.4, 2025-08, gh-27639
456    message = "Passing more than 2 positional arguments to np.maximum and np.minimum "
457
458    @pytest.mark.parametrize("ufunc", [np.minimum, np.maximum])
459    def test_extremem_3_args(self, ufunc):
460        self.assert_deprecated(ufunc, args=(np.ones(1), np.zeros(1), np.empty(1)))
461 
codekingpro/portable-devtools · Team Ai