diff --git a/CHANGELOG.md b/CHANGELOG.md index 28315c1fef5..eeb32c244e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ This release is compatible with NumPy 2.5. ### Deprecated * Deprecated setting the shape via the `shape` attribute of `dpnp.ndarray` and `dpnp.tensor.usm_ndarray`. Use `dpnp.reshape`/`dpnp.tensor.reshape` (or the `reshape` method) instead [#2967](https://github.com/IntelPython/dpnp/pull/2967) +* Deprecated `dpnp.config.__DPNP_QUEUE_GPU__`, `dpnp.config.__DPNP_OUTPUT_NUMPY__` and `dpnp.config.__DPNP_OUTPUT_DPCTL_DEFAULT_SHARED__`, which have no effect; reading or setting them now emits a `DeprecationWarning` [#3107](https://github.com/IntelPython/dpnp/pull/3107) ### Removed diff --git a/dpnp/config.py b/dpnp/config.py index 0d973cdd298..eb5e50237ef 100644 --- a/dpnp/config.py +++ b/dpnp/config.py @@ -28,32 +28,62 @@ import os +import sys +import types +import warnings __DPNP_ORIGIN__ = int(os.getenv("DPNP_ORIGIN", 0)) """ Explicitly use original host Python NumPy """ -__DPNP_QUEUE_GPU__ = int(os.getenv("DPNP_QUEUE_GPU", 0)) -""" -Explicitly use GPU for SYCL queue -""" - -__DPNP_OUTPUT_NUMPY__ = int(os.getenv("DPNP_OUTPUT_NUMPY", 0)) -""" -Explicitly use NumPy.ndarray as return type for creation functions -""" - -__DPNP_OUTPUT_DPCTL_DEFAULT_SHARED__ = int( - os.getenv("DPNP_OUTPUT_DPCTL_DEFAULT_SHARED", 0) -) -""" -Explicitly use SYCL shared memory parameter in DPCtl array constructor for creation functions -""" - __DPNP_RAISE_EXCEPION_ON_NUMPY_FALLBACK__ = int( os.getenv("DPNP_RAISE_EXCEPION_ON_NUMPY_FALLBACK", 1) ) """ Trigger non-implemented exception when DPNP fallbacks on NumPy implementation """ + +# settings that no longer have any effect: attribute name -> env var name +_DEPRECATED_SETTINGS = { + "__DPNP_QUEUE_GPU__": "DPNP_QUEUE_GPU", + "__DPNP_OUTPUT_NUMPY__": "DPNP_OUTPUT_NUMPY", + "__DPNP_OUTPUT_DPCTL_DEFAULT_SHARED__": "DPNP_OUTPUT_DPCTL_DEFAULT_SHARED", +} + +# values assigned to deprecated settings after import +_deprecated_values = {} + + +def _warn_deprecated(name): + warnings.warn( + f"`dpnp.config.{name}` is deprecated, and will be removed in a " + "future release. It has no effect.", + DeprecationWarning, + stacklevel=3, + ) + + +def __getattr__(name): + if name in _DEPRECATED_SETTINGS: + _warn_deprecated(name) + if name in _deprecated_values: + return _deprecated_values[name] + # parse lazily, so a bad env value can't break `import dpnp` + return int(os.getenv(_DEPRECATED_SETTINGS[name], 0)) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +class _ConfigModule(types.ModuleType): + """Warn also on assignment to a deprecated setting.""" + + def __setattr__(self, name, value): + if name in _DEPRECATED_SETTINGS: + _warn_deprecated(name) + # keep it out of the module dict, so reads still warn + _deprecated_values[name] = value + else: + super().__setattr__(name, value) + + +sys.modules[__name__].__class__ = _ConfigModule diff --git a/dpnp/tests/test_config.py b/dpnp/tests/test_config.py new file mode 100644 index 00000000000..0984663bad0 --- /dev/null +++ b/dpnp/tests/test_config.py @@ -0,0 +1,62 @@ +import re +import warnings + +import pytest + +import dpnp + + +@pytest.fixture(autouse=True) +def reset_deprecated_values(monkeypatch): + # drop values assigned by other tests + monkeypatch.setattr(dpnp.config, "_deprecated_values", {}) + + +class TestDeprecatedSettings: + @pytest.mark.parametrize( + "name, env", + [ + ("__DPNP_QUEUE_GPU__", "DPNP_QUEUE_GPU"), + ("__DPNP_OUTPUT_NUMPY__", "DPNP_OUTPUT_NUMPY"), + ( + "__DPNP_OUTPUT_DPCTL_DEFAULT_SHARED__", + "DPNP_OUTPUT_DPCTL_DEFAULT_SHARED", + ), + ], + ) + def test_read_warns(self, monkeypatch, name, env): + monkeypatch.delenv(env, raising=False) + with pytest.warns(DeprecationWarning, match=re.escape(name)): + value = getattr(dpnp.config, name) + assert value == 0 + + @pytest.mark.parametrize( + "name", + [ + "__DPNP_QUEUE_GPU__", + "__DPNP_OUTPUT_NUMPY__", + "__DPNP_OUTPUT_DPCTL_DEFAULT_SHARED__", + ], + ) + def test_write_warns(self, name): + with pytest.warns(DeprecationWarning, match=re.escape(name)): + setattr(dpnp.config, name, 1) + + # a later read must still warn + with pytest.warns(DeprecationWarning, match=re.escape(name)): + value = getattr(dpnp.config, name) + assert value == 1 + + @pytest.mark.parametrize( + "name", + ["__DPNP_ORIGIN__", "__DPNP_RAISE_EXCEPION_ON_NUMPY_FALLBACK__"], + ) + def test_no_warning(self, monkeypatch, name): + with warnings.catch_warnings(): + warnings.simplefilter("error") + value = getattr(dpnp.config, name) + monkeypatch.setattr(dpnp.config, name, value) + + def test_unknown_name(self): + with pytest.raises(AttributeError, match="has no attribute"): + getattr(dpnp.config, "__DPNP_UNKNOWN__")