From 69ab9b05017ea0366670b3ca5fff2b748215475f Mon Sep 17 00:00:00 2001 From: priyam0k <87162535+priyam0k@users.noreply.github.com> Date: Sat, 30 May 2026 04:04:54 +0530 Subject: [PATCH 1/2] Deprecate cupy array backend (#843) Emit DeprecationWarning when the cupy backend is selected, without removing functionality: - Options.set_option warns for ARRAY_BACKEND='cupy' and for 'cupy' in ARRAY_PRIORITY. - Common.set_backend warns when backend='cupy'. Adds tests asserting the warnings fire and that non-cupy backends do not warn. --- chainladder/__init__.py | 15 ++++++ chainladder/core/common.py | 9 ++++ chainladder/utils/tests/test_utilities.py | 62 ++++++++++++++++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/chainladder/__init__.py b/chainladder/__init__.py index 4f09953b..efb25a4e 100644 --- a/chainladder/__init__.py +++ b/chainladder/__init__.py @@ -16,6 +16,7 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. import copy +import warnings import numpy as np import pandas as pd from importlib.metadata import version @@ -95,6 +96,20 @@ def set_option( """ self._validate_option(option) + if option == "ARRAY_BACKEND" and value == "cupy": + warnings.warn( + "The 'cupy' array backend is deprecated and will be removed in a " + "future release. See https://github.com/casact/chainladder-python/issues/843.", + DeprecationWarning, + stacklevel=2, + ) + elif option == "ARRAY_PRIORITY" and "cupy" in value: + warnings.warn( + "The 'cupy' array backend is deprecated and will be removed in a " + "future release. See https://github.com/casact/chainladder-python/issues/843.", + DeprecationWarning, + stacklevel=2, + ) setattr(self, option, value) def reset_option(self, option: str | None = None) -> None: diff --git a/chainladder/core/common.py b/chainladder/core/common.py index 7ad6e1f5..d4468c01 100644 --- a/chainladder/core/common.py +++ b/chainladder/core/common.py @@ -3,6 +3,8 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. from __future__ import annotations +import warnings + import numpy as np import pandas as pd @@ -172,6 +174,13 @@ def set_backend( else: raise ValueError("Unable to determine array backend.") if inplace: + if backend == "cupy": + warnings.warn( + "The 'cupy' array backend is deprecated and will be removed in a " + "future release. See https://github.com/casact/chainladder-python/issues/843.", + DeprecationWarning, + stacklevel=2, + ) # Coming from dask - compute and then recall this method # going to dask - if old_backend == "dask" and backend != "dask": diff --git a/chainladder/utils/tests/test_utilities.py b/chainladder/utils/tests/test_utilities.py index 0aeffd27..43423f16 100644 --- a/chainladder/utils/tests/test_utilities.py +++ b/chainladder/utils/tests/test_utilities.py @@ -291,4 +291,64 @@ def test_reset_option_invalid() -> None: None """ with pytest.raises(ValueError): - cl.options.reset_option('NOT_A_REAL_OPTION') \ No newline at end of file + cl.options.reset_option('NOT_A_REAL_OPTION') + + +def test_set_option_cupy_backend_deprecated() -> None: + """ + Setting ARRAY_BACKEND to 'cupy' should emit a DeprecationWarning. See issue #843. + + Returns + ------- + None + """ + try: + with pytest.warns(DeprecationWarning, match="cupy"): + cl.options.set_option('ARRAY_BACKEND', 'cupy') + finally: + cl.options.reset_option('ARRAY_BACKEND') + + +def test_set_option_cupy_priority_deprecated() -> None: + """ + Setting ARRAY_PRIORITY to a list containing 'cupy' should emit a + DeprecationWarning. See issue #843. + + Returns + ------- + None + """ + try: + with pytest.warns(DeprecationWarning, match="cupy"): + cl.options.set_option('ARRAY_PRIORITY', ['dask', 'sparse', 'cupy', 'numpy']) + finally: + cl.options.reset_option('ARRAY_PRIORITY') + + +def test_set_option_non_cupy_no_warning(recwarn) -> None: + """ + Setting backends other than 'cupy' should not emit a DeprecationWarning. + + Returns + ------- + None + """ + try: + cl.options.set_option('ARRAY_BACKEND', 'sparse') + cl.options.set_option('ARRAY_PRIORITY', ['dask', 'sparse', 'numpy']) + assert not [w for w in recwarn if issubclass(w.category, DeprecationWarning)] + finally: + cl.options.reset_option('ARRAY_BACKEND') + cl.options.reset_option('ARRAY_PRIORITY') + + +def test_set_backend_cupy_deprecated(clrd) -> None: + """ + Triangle.set_backend('cupy') should emit a DeprecationWarning. See issue #843. + + Returns + ------- + None + """ + with pytest.warns(DeprecationWarning, match="cupy"): + clrd.set_backend('cupy') \ No newline at end of file From 8c064338436da601bb3d8814ad1ed77cf3bc76c4 Mon Sep 17 00:00:00 2001 From: priyam0k <87162535+priyam0k@users.noreply.github.com> Date: Sat, 30 May 2026 04:32:49 +0530 Subject: [PATCH 2/2] Fix set_backend cupy warning stacklevel and dedupe (#843) Move the DeprecationWarning to set_backend's public entry point and add a private _warn guard so internal recursive (inplace) and deep-child calls do not re-warn. This makes stacklevel=2 point at the user's call site and ensures exactly one warning fires. Strengthens the test to assert a single warning at the caller's file. --- chainladder/core/common.py | 23 +++++++++++++---------- chainladder/utils/tests/test_utilities.py | 15 ++++++++++++--- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/chainladder/core/common.py b/chainladder/core/common.py index d4468c01..a6a9f3e7 100644 --- a/chainladder/core/common.py +++ b/chainladder/core/common.py @@ -149,7 +149,7 @@ def pipe(self, func, *args, **kwargs): return func(self, *args, **kwargs) def set_backend( - self, backend: str, inplace: bool = False, deep: bool = False, **kwargs + self, backend: str, inplace: bool = False, deep: bool = False, _warn: bool = True, **kwargs ): """ Converts triangle array_backend. @@ -166,6 +166,16 @@ def set_backend( ------- Triangle with updated array_backend """ + # Warn once, at the public entry point, so stacklevel=2 points at the + # user's call site rather than an internal recursive call. The _warn + # flag suppresses duplicate warnings from internal recursion below. + if _warn and backend == "cupy": + warnings.warn( + "The 'cupy' array backend is deprecated and will be removed in a " + "future release. See https://github.com/casact/chainladder-python/issues/843.", + DeprecationWarning, + stacklevel=2, + ) if hasattr(self, "array_backend"): old_backend: str = self.array_backend else: @@ -174,13 +184,6 @@ def set_backend( else: raise ValueError("Unable to determine array backend.") if inplace: - if backend == "cupy": - warnings.warn( - "The 'cupy' array backend is deprecated and will be removed in a " - "future release. See https://github.com/casact/chainladder-python/issues/843.", - DeprecationWarning, - stacklevel=2, - ) # Coming from dask - compute and then recall this method # going to dask - if old_backend == "dask" and backend != "dask": @@ -214,7 +217,7 @@ def set_backend( if deep: for k, v in vars(self).items(): if isinstance(v, Common): - v.set_backend(backend, inplace=True, deep=True) + v.set_backend(backend, inplace=True, deep=True, _warn=False) if hasattr(self, "array_backend"): self.array_backend = backend else: @@ -222,7 +225,7 @@ def set_backend( return self else: obj = self.copy() - return obj.set_backend(backend=backend, inplace=True, deep=deep, **kwargs) + return obj.set_backend(backend=backend, inplace=True, deep=deep, _warn=False, **kwargs) @staticmethod def _validate_assumption( diff --git a/chainladder/utils/tests/test_utilities.py b/chainladder/utils/tests/test_utilities.py index 43423f16..228bda4d 100644 --- a/chainladder/utils/tests/test_utilities.py +++ b/chainladder/utils/tests/test_utilities.py @@ -344,11 +344,20 @@ def test_set_option_non_cupy_no_warning(recwarn) -> None: def test_set_backend_cupy_deprecated(clrd) -> None: """ - Triangle.set_backend('cupy') should emit a DeprecationWarning. See issue #843. + Triangle.set_backend('cupy') should emit exactly one DeprecationWarning, + pointing at the caller. See issue #843. Returns ------- None """ - with pytest.warns(DeprecationWarning, match="cupy"): - clrd.set_backend('cupy') \ No newline at end of file + with pytest.warns(DeprecationWarning, match="cupy") as record: + clrd.set_backend('cupy', deep=True) + cupy_warnings = [ + w for w in record + if issubclass(w.category, DeprecationWarning) and "cupy" in str(w.message) + ] + # A single warning should fire at the user's call site, not once per + # internal recursive / deep child conversion. + assert len(cupy_warnings) == 1 + assert cupy_warnings[0].filename == __file__ \ No newline at end of file