Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions chainladder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -95,6 +96,28 @@ 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:
Comment thread
genedan marked this conversation as resolved.
# Only warn when 'cupy' is prioritized ahead of a non-deprecated
# backend ('numpy' or 'sparse'), i.e. cupy would actually be
# selected over a supported backend.
cupy_index = value.index("cupy")
if any(
backend in value and value.index(backend) > cupy_index
for backend in ("numpy", "sparse")
):
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:
Expand Down
25 changes: 22 additions & 3 deletions chainladder/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -147,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
Comment thread
genedan marked this conversation as resolved.
):
"""
Converts triangle array_backend.
Expand All @@ -159,11 +161,28 @@ def set_backend(
inplace : bool
Whether to mutate the existing Triangle instance or return a new
one.
deep : bool
Whether to also convert the backend of nested Triangle-like
attributes (e.g. fitted estimator components).
_warn : bool
Internal flag controlling whether a deprecation warning is emitted.
Set to ``False`` on internal recursive calls so the warning fires
only once, at the user's call site. Not part of the public API.

Returns
-------
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:
Expand Down Expand Up @@ -205,15 +224,15 @@ 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:
raise AttributeError(backend, "backend is not supported.")
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(
Expand Down
88 changes: 87 additions & 1 deletion chainladder/utils/tests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,4 +291,90 @@ def test_reset_option_invalid() -> None:
None
"""
with pytest.raises(ValueError):
cl.options.reset_option('NOT_A_REAL_OPTION')
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 with 'cupy' ahead of a non-deprecated backend
('numpy' or 'sparse') should emit a DeprecationWarning. See issue #843.

Returns
-------
None
"""
try:
with pytest.warns(DeprecationWarning, match="cupy"):
cl.options.set_option('ARRAY_PRIORITY', ['cupy', 'dask', 'sparse', 'numpy'])
finally:
cl.options.reset_option('ARRAY_PRIORITY')


def test_set_option_cupy_priority_last_no_warning(recwarn) -> None:
"""
Setting ARRAY_PRIORITY with 'cupy' ranked below every non-deprecated
backend should not warn, since cupy would never be selected over a
supported backend. See issue #843.

Returns
-------
None
"""
try:
cl.options.set_option('ARRAY_PRIORITY', ['dask', 'sparse', 'numpy', 'cupy'])
assert not [w for w in recwarn if issubclass(w.category, DeprecationWarning)]
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 exactly one DeprecationWarning,
pointing at the caller. See issue #843.

Returns
-------
None
"""
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__
Loading