Skip to content

Commit

Permalink
@deprecate_arguments and @deprecate_function add deprecation to d…
Browse files Browse the repository at this point in the history
…ocstring (Qiskit#9790)
  • Loading branch information
Eric-Arellano authored and king-p3nguin committed May 22, 2023
1 parent eb14fd0 commit 6d08086
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 42 deletions.
7 changes: 3 additions & 4 deletions qiskit/algorithms/factorizers/shor.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@ class Shor:
"""

@deprecate_function(
"""The Shor class is deprecated as of Qiskit Terra 0.22.0 and will be removed
no sooner than 3 months after the release date.
It is replaced by the tutorial at https://qiskit.org/textbook/ch-algorithms/shor.html
""",
"The Shor class is deprecated as of Qiskit Terra 0.22.0 and will be removed "
"no sooner than 3 months after the release date. It is replaced by the tutorial "
"at https://qiskit.org/textbook/ch-algorithms/shor.html",
since="0.22.0",
)
def __init__(self, quantum_instance: Optional[Union[QuantumInstance, Backend]] = None) -> None:
Expand Down
7 changes: 3 additions & 4 deletions qiskit/algorithms/linear_solvers/hhl.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,9 @@ class HHL(LinearSolver):
"""

@deprecate_function(
"""The HHL class is deprecated as of Qiskit Terra 0.22.0 and will be removed
no sooner than 3 months after the release date.
It is replaced by the tutorial at https://qiskit.org/textbook/ch-applications/hhl_tutorial.html"
""",
"The HHL class is deprecated as of Qiskit Terra 0.22.0 and will be removed "
"no sooner than 3 months after the release date. It is replaced by the tutorial at "
"https://qiskit.org/textbook/ch-applications/hhl_tutorial.html",
since="0.22.0",
)
def __init__(
Expand Down
6 changes: 3 additions & 3 deletions qiskit/algorithms/phase_estimators/phase_estimation_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ def circuit_result(self) -> Result:

@property
@deprecate_function(
"""The 'PhaseEstimationResult.most_likely_phase' attribute
is deprecated as of 0.18.0 and will be removed no earlier than 3 months
after the release date. It has been renamed as the 'phase' attribute.""",
"The 'PhaseEstimationResult.most_likely_phase' attribute is deprecated as of 0.18.0 and "
"will be removed no earlier than 3 months after the release date. It has been renamed as "
"the 'phase' attribute.",
since="0.18.0",
)
def most_likely_phase(self) -> float:
Expand Down
7 changes: 3 additions & 4 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,9 @@ def _add_op_node(self, op, qargs, cargs):
return node_index

@deprecate_function(
"""The DAGCircuit._copy_circuit_metadata method is deprecated as of 0.20.0. It will be removed
no earlier than 3 months after the release date. You should use the DAGCircuit.copy_empty_like
method instead, which acts identically.
""",
"The DAGCircuit._copy_circuit_metadata method is deprecated as of 0.20.0. It will be "
"removed no earlier than 3 months after the release date. You should use the "
"DAGCircuit.copy_empty_like method instead, which acts identically.",
since="0.20.0",
)
def _copy_circuit_metadata(self):
Expand Down
53 changes: 27 additions & 26 deletions qiskit/utils/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,27 @@ def deprecate_arguments(
Callable: The decorated callable.
"""

del since # Will be used in a followup to add deprecations to our docs site.

def decorator(func):
func_name = func.__qualname__
old_kwarg_to_msg = {}
for old_arg, new_arg in kwarg_map.items():
msg_suffix = (
"will in the future be removed." if new_arg is None else f"replaced with {new_arg}."
)
old_kwarg_to_msg[
old_arg
] = f"{func_name} keyword argument {old_arg} is deprecated and {msg_suffix}"

@functools.wraps(func)
def wrapper(*args, **kwargs):
if kwargs:
_rename_kwargs(func.__name__, kwargs, kwarg_map, category)
_rename_kwargs(func_name, kwargs, old_kwarg_to_msg, kwarg_map, category)
return func(*args, **kwargs)

for msg in old_kwarg_to_msg.values():
add_deprecation_to_docstring(
wrapper, msg, since=since, pending=issubclass(category, PendingDeprecationWarning)
)
return wrapper

return decorator
Expand Down Expand Up @@ -73,14 +85,15 @@ def deprecate_function(
Callable: The decorated, deprecated callable.
"""

del since # Will be used in a followup to add deprecations to our docs site.

def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(msg, category=category, stacklevel=stacklevel)
return func(*args, **kwargs)

add_deprecation_to_docstring(
wrapper, msg, since=since, pending=issubclass(category, PendingDeprecationWarning)
)
return wrapper

return decorator
Expand All @@ -89,30 +102,18 @@ def wrapper(*args, **kwargs):
def _rename_kwargs(
func_name: str,
kwargs: Dict[str, Any],
kwarg_map: Dict[str, str],
old_kwarg_to_msg: Dict[str, str],
kwarg_map: Dict[str, Optional[str]],
category: Type[Warning] = DeprecationWarning,
) -> None:
for old_arg, new_arg in kwarg_map.items():
if old_arg in kwargs:
if new_arg in kwargs:
raise TypeError(f"{func_name} received both {new_arg} and {old_arg} (deprecated).")

if new_arg is None:
warnings.warn(
f"{func_name} keyword argument {old_arg} is deprecated and "
"will in future be removed.",
category=category,
stacklevel=3,
)
else:
warnings.warn(
f"{func_name} keyword argument {old_arg} is deprecated and "
f"replaced with {new_arg}.",
category=category,
stacklevel=3,
)

kwargs[new_arg] = kwargs.pop(old_arg)
if old_arg not in kwargs:
continue
if new_arg in kwargs:
raise TypeError(f"{func_name} received both {new_arg} and {old_arg} (deprecated).")
warnings.warn(old_kwarg_to_msg[old_arg], category=category, stacklevel=3)
if new_arg is not None:
kwargs[new_arg] = kwargs.pop(old_arg)


# We insert deprecations in-between the description and Napoleon's meta sections. The below is from
Expand Down
84 changes: 83 additions & 1 deletion test/python/utils/test_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,89 @@
from textwrap import dedent

from qiskit.test import QiskitTestCase
from qiskit.utils.deprecation import add_deprecation_to_docstring
from qiskit.utils.deprecation import (
add_deprecation_to_docstring,
deprecate_function,
deprecate_arguments,
)


class TestDeprecationDecorators(QiskitTestCase):
"""Test that the decorators in ``utils.deprecation`` correctly log warnings and get added to
docstring."""

def test_deprecate_arguments_message(self) -> None:
"""Test that `@deprecate_arguments` adds the correct message to the docstring."""

@deprecate_arguments(
{"old_arg1": "new_arg1", "old_arg2": None},
category=PendingDeprecationWarning,
since="9.99",
)
def my_func() -> None:
pass

self.assertEqual(
my_func.__doc__,
dedent(
f"""\
.. deprecated:: 9.99_pending
{my_func.__qualname__} keyword argument old_arg1 is deprecated and replaced with \
new_arg1.
.. deprecated:: 9.99_pending
{my_func.__qualname__} keyword argument old_arg2 is deprecated and will in the \
future be removed.
"""
),
)

def test_deprecate_function_docstring(self) -> None:
"""Test that `@deprecate_function` adds the correct message to the docstring."""

@deprecate_function("Stop using my_func!", since="9.99")
def my_func() -> None:
pass

self.assertEqual(
my_func.__doc__,
dedent(
"""\
.. deprecated:: 9.99
Stop using my_func!
"""
),
)

def test_deprecate_arguments_runtime_warning(self) -> None:
"""Test that `@deprecate_arguments` warns whenever the arguments are used.
Also check that old arguments are passed in as their new alias.
"""

@deprecate_arguments({"arg1": None, "arg2": "new_arg2"}, since="9.99")
def my_func(*, arg1: str = "a", new_arg2: str) -> None:
del arg1
self.assertEqual(new_arg2, "z")

my_func(new_arg2="z") # No warnings if no deprecated args used.
with self.assertWarnsRegex(DeprecationWarning, "arg1"):
my_func(arg1="a", new_arg2="z")
with self.assertWarnsRegex(DeprecationWarning, "arg2"):
# `arg2` should be converted into `new_arg2`.
my_func(arg2="z") # pylint: disable=missing-kwoa

def test_deprecate_function_runtime_warning(self) -> None:
"""Test that `@deprecate_function` warns whenever the function is used."""

@deprecate_function("Stop using my_func!", since="9.99")
def my_func() -> None:
pass

with self.assertWarnsRegex(DeprecationWarning, "Stop using my_func!"):
my_func()


class AddDeprecationDocstringTest(QiskitTestCase):
Expand Down

0 comments on commit 6d08086

Please sign in to comment.