From a09a8fa79265cec76c10cf7b0e236d77830fbc46 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sun, 9 Mar 2025 16:18:19 +0100 Subject: [PATCH 1/3] Deprecate qcodes deprecation utils --- pyproject.toml | 2 ++ src/qcodes/__init__.py | 2 +- src/qcodes/utils/__init__.py | 6 ++++- src/qcodes/utils/deprecate.py | 26 +++++++++++++++--- src/qcodes/utils/helpers.py | 2 +- .../test_parse_parameter_attr.py | 8 +++--- tests/test_deprecate.py | 27 ++++++++++++++----- 7 files changed, 56 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index daefebf91c29..cf54545bdc8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,6 +199,7 @@ ignore_missing_imports = true include = ["src","tests"] ignore = [ "src/qcodes/instrument_drivers/Harvard/Decadac.py", + "tests/test_deprecate.py" ] reportMissingTypeStubs = true reportDeprecated = true @@ -218,6 +219,7 @@ markers = "serial" # and error on all other warnings filterwarnings = [ 'error', + 'ignore:QCoDeS deprecation logic is deprecated:qcodes.utils.deprecate.QCoDeSDeprecationWarning', # deprecation of internal qcodes deprecated. 'ignore:open_binary is deprecated:DeprecationWarning', # pyvisa-sim 'ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning', # jupyter 'ignore:Parsing dates involving a day of month without a year specified is ambiguious:DeprecationWarning', # ipykernel 3.13+ diff --git a/src/qcodes/__init__.py b/src/qcodes/__init__.py index f3fcda8d0cf3..db2acd813a2d 100644 --- a/src/qcodes/__init__.py +++ b/src/qcodes/__init__.py @@ -70,7 +70,7 @@ combine, ) from qcodes.station import Station -from qcodes.utils import deprecate +from qcodes.utils import deprecate # pyright: ignore[reportDeprecated] # ensure to close all instruments when interpreter is closed atexit.register(Instrument.close_all) diff --git a/src/qcodes/utils/__init__.py b/src/qcodes/utils/__init__.py index 3b15b99a62e9..f802cb9d593f 100644 --- a/src/qcodes/utils/__init__.py +++ b/src/qcodes/utils/__init__.py @@ -10,7 +10,11 @@ ) from .deep_update_utils import deep_update from .delaykeyboardinterrupt import DelayedKeyboardInterrupt -from .deprecate import QCoDeSDeprecationWarning, deprecate, issue_deprecation_warning +from .deprecate import ( + QCoDeSDeprecationWarning, + deprecate, # pyright: ignore[reportDeprecated] + issue_deprecation_warning, # pyright: ignore[reportDeprecated] +) from .full_class import full_class from .function_helpers import is_function from .installation_info import ( diff --git a/src/qcodes/utils/deprecate.py b/src/qcodes/utils/deprecate.py index 164b81b96a01..ac23cfb98f7a 100644 --- a/src/qcodes/utils/deprecate.py +++ b/src/qcodes/utils/deprecate.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, cast import wrapt # type: ignore[import-untyped] +from typing_extensions import deprecated if TYPE_CHECKING: from collections.abc import Iterator @@ -17,6 +18,10 @@ class QCoDeSDeprecationWarning(RuntimeWarning): """ +@deprecated( + "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + category=QCoDeSDeprecationWarning, +) def deprecation_message( what: str, reason: str | None = None, alternative: str | None = None ) -> str: @@ -29,6 +34,10 @@ def deprecation_message( return msg +@deprecated( + "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + category=QCoDeSDeprecationWarning, +) def issue_deprecation_warning( what: str, reason: str | None = None, @@ -39,12 +48,16 @@ def issue_deprecation_warning( Issue a `QCoDeSDeprecationWarning` with a consistently formatted message """ warnings.warn( - deprecation_message(what, reason, alternative), + deprecation_message(what, reason, alternative), # pyright: ignore[reportDeprecated] QCoDeSDeprecationWarning, stacklevel=stacklevel, ) +@deprecated( + "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + category=QCoDeSDeprecationWarning, +) def deprecate( reason: str | None = None, alternative: str | None = None ) -> Callable[..., Any]: @@ -71,7 +84,7 @@ def decorate_callable( if func.__name__ == "__init__" else ("function", func.__name__) ) - issue_deprecation_warning(f"{t} <{n}>", reason, alternative, stacklevel=3) + issue_deprecation_warning(f"{t} <{n}>", reason, alternative, stacklevel=3) # pyright: ignore[reportDeprecated] return func(*args, **kwargs) def actual_decorator(obj: Any) -> Any: @@ -113,6 +126,10 @@ def _catch_deprecation_warnings() -> "Iterator[list[warnings.WarningMessage]]": yield ws +@deprecated( + "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + category=QCoDeSDeprecationWarning, +) @contextmanager def assert_not_deprecated() -> "Iterator[None]": with _catch_deprecation_warnings() as ws: @@ -120,11 +137,14 @@ def assert_not_deprecated() -> "Iterator[None]": assert len(ws) == 0 +@deprecated( + "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + category=QCoDeSDeprecationWarning, +) @contextmanager def assert_deprecated(message: str) -> "Iterator[None]": with _catch_deprecation_warnings() as ws: yield - assert len(ws) == 1 recorded_message = ws[0].message assert isinstance(recorded_message, Warning) assert recorded_message.args[0] == message diff --git a/src/qcodes/utils/helpers.py b/src/qcodes/utils/helpers.py index 766423e29f4e..205c3b1eb692 100644 --- a/src/qcodes/utils/helpers.py +++ b/src/qcodes/utils/helpers.py @@ -17,7 +17,7 @@ from qcodes.parameters.sequence_helpers import is_sequence, is_sequence_of from qcodes.parameters.sweep_values import make_sweep from qcodes.parameters.val_mapping import create_on_off_val_mapping -from qcodes.utils.deprecate import deprecate +from qcodes.utils.deprecate import deprecate # pyright: ignore[reportDeprecated] from .abstractmethod import qcodes_abstractmethod as abstractmethod from .attribute_helpers import ( diff --git a/tests/sphinx_extension/test_parse_parameter_attr.py b/tests/sphinx_extension/test_parse_parameter_attr.py index 5d81d0032e70..a19d457eb544 100644 --- a/tests/sphinx_extension/test_parse_parameter_attr.py +++ b/tests/sphinx_extension/test_parse_parameter_attr.py @@ -1,12 +1,12 @@ import pytest from sphinx.util.inspect import safe_getattr +from typing_extensions import deprecated from qcodes.instrument import InstrumentBase, VisaInstrument from qcodes.sphinx_extensions.parse_parameter_attr import ( ParameterProxy, qcodes_parameter_attr_getter, ) -from qcodes.utils import deprecate class DummyTestClass(InstrumentBase): @@ -40,7 +40,7 @@ class DummyDecoratedInitTestClass(InstrumentBase): A class attribute """ - @deprecate("Deprecate to test that decorated init is handled correctly") + @deprecated("Deprecate to test that decorated init is handled correctly") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -50,7 +50,7 @@ def __init__(self, *args, **kwargs): """ -@deprecate("Deprecate to test that decorated class is handled correctly") +@deprecated("Deprecate to test that decorated class is handled correctly") class DummyDecoratedClassTestClass(InstrumentBase): myattr: str = "ClassAttribute" """ @@ -102,7 +102,7 @@ def test_decorated_init_func() -> None: def test_decorated_class() -> None: - attr = qcodes_parameter_attr_getter(DummyDecoratedClassTestClass, "other_attr") + attr = qcodes_parameter_attr_getter(DummyDecoratedClassTestClass, "other_attr") # pyright: ignore[reportDeprecated] assert isinstance(attr, ParameterProxy) assert repr(attr) == '"InstanceAttribute"' diff --git a/tests/test_deprecate.py b/tests/test_deprecate.py index b381df08bd8f..1d8a10757ee0 100644 --- a/tests/test_deprecate.py +++ b/tests/test_deprecate.py @@ -1,22 +1,34 @@ import warnings +from contextlib import contextmanager +from typing import TYPE_CHECKING import pytest from qcodes.utils.deprecate import ( QCoDeSDeprecationWarning, _catch_deprecation_warnings, - assert_deprecated, assert_not_deprecated, deprecate, issue_deprecation_warning, ) +if TYPE_CHECKING: + from collections.abc import Iterator + + +@contextmanager +def assert_deprecated(message: str, n_warnings: int = 1) -> "Iterator[None]": + with _catch_deprecation_warnings() as ws: + yield + assert len(ws) == n_warnings + def test_assert_deprecated_raises() -> None: with assert_deprecated( "The use of this function is deprecated, because " 'of this being a test. Use "a real function" as an ' - "alternative." + "alternative.", + 3, ): issue_deprecation_warning( "use of this function", "of this being a test", "a real function" @@ -57,7 +69,8 @@ def add_one(x): with assert_deprecated( "The function is deprecated, because " - "this function is for private use only." + "this function is for private use only.", + n_warnings=3, ): assert add_one(1) == _add_one(1) @@ -68,12 +81,12 @@ def test_deprecated_context_manager() -> None: issue_deprecation_warning("something") issue_deprecation_warning("something more") warnings.warn("Some other warning") - assert len(ws) == 2 + assert len(ws) == 6 with pytest.warns(expected_warning=QCoDeSDeprecationWarning) as ws_2: issue_deprecation_warning("something") warnings.warn("Some other warning") - assert len(ws_2) == 2 + assert len(ws_2) == 4 @deprecate(reason="this is a test") @@ -108,7 +121,7 @@ def test_init_uninhibited() -> None: def test_init_raises() -> None: - with assert_deprecated("The class is deprecated, because this is a test."): + with assert_deprecated("The class is deprecated, because this is a test.", 3): C("pristine") @@ -124,7 +137,7 @@ def test_method_raises() -> None: c = C("pristine") with assert_deprecated( - "The function is deprecated, because this is a test." + "The function is deprecated, because this is a test.", 3 ): c.method() From 8be8f1f5d2dadc37bf4bd39e60772c1c4e9dea80 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 10 Mar 2025 09:39:58 +0100 Subject: [PATCH 2/3] Add changelog for 6946 --- docs/changes/newsfragments/6946.breaking | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 docs/changes/newsfragments/6946.breaking diff --git a/docs/changes/newsfragments/6946.breaking b/docs/changes/newsfragments/6946.breaking new file mode 100644 index 000000000000..d507a9a12492 --- /dev/null +++ b/docs/changes/newsfragments/6946.breaking @@ -0,0 +1,4 @@ +The QCoDeS deprecation utils including ``qcodes.utils.deprecate.deprecation_message``, +``qcodes.utils.deprecate.issue_deprecation_warning``, ``qcodes.utils.deprecate.deprecate`` ``qcodes.utils.deprecate.assert_not_deprecated`` +and ``qcodes.utils.deprecate.assert_deprecated`` along with their reexports in `qcodes`, `qcodes.utils` and `qcodes.utils.helpers` +are all deprecated and will be removed in QCoDeS 0.54.0. We recommend using `typing_extensions.deprecate` as an alternative. From 751ac1fc44d85eb8c1b50d4acd69a107e5c0fa65 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Mon, 10 Mar 2025 13:44:13 +0100 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Mikhail Astafev --- src/qcodes/utils/deprecate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/qcodes/utils/deprecate.py b/src/qcodes/utils/deprecate.py index ac23cfb98f7a..18f0bc9d0d36 100644 --- a/src/qcodes/utils/deprecate.py +++ b/src/qcodes/utils/deprecate.py @@ -19,7 +19,7 @@ class QCoDeSDeprecationWarning(RuntimeWarning): @deprecated( - "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + "QCoDeS deprecation logic is deprecated. Use `deprecated` decorator from `typing_extensions`/`warnings` modules as an alternative.", category=QCoDeSDeprecationWarning, ) def deprecation_message( @@ -35,7 +35,7 @@ def deprecation_message( @deprecated( - "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + "QCoDeS deprecation logic is deprecated. Use `deprecated` decorator from `typing_extensions`/`warnings` as an alternative.", category=QCoDeSDeprecationWarning, ) def issue_deprecation_warning( @@ -55,7 +55,7 @@ def issue_deprecation_warning( @deprecated( - "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + "QCoDeS deprecation logic is deprecated. Use `deprecated` decorator from `typing_extensions`/`warnings` as an alternative.", category=QCoDeSDeprecationWarning, ) def deprecate( @@ -127,7 +127,7 @@ def _catch_deprecation_warnings() -> "Iterator[list[warnings.WarningMessage]]": @deprecated( - "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + "QCoDeS deprecation logic is deprecated. Use `deprecated` decorator from `typing_extensions`/`warnings` as an alternative.", category=QCoDeSDeprecationWarning, ) @contextmanager @@ -138,7 +138,7 @@ def assert_not_deprecated() -> "Iterator[None]": @deprecated( - "QCoDeS deprecation logic is deprecated. Use deprecated decorator from typing_extensions/warnings as an alternative.", + "QCoDeS deprecation logic is deprecated. Use `deprecated` decorator from `typing_extensions`/`warnings` as an alternative.", category=QCoDeSDeprecationWarning, ) @contextmanager