mirror of https://github.com/Qiskit/qiskit.git
727 lines
20 KiB
Python
727 lines
20 KiB
Python
# This code is part of Qiskit.
|
|
#
|
|
# (C) Copyright IBM 2023.
|
|
#
|
|
# This code is licensed under the Apache License, Version 2.0. You may
|
|
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
#
|
|
# Any modifications or derivative works of this code must retain this
|
|
# copyright notice, and modified files need to carry a notice indicating
|
|
# that they have been altered from the originals.
|
|
|
|
"""Tests for the functions in ``utils.deprecation``."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from textwrap import dedent
|
|
|
|
from qiskit.utils.deprecation import (
|
|
add_deprecation_to_docstring,
|
|
deprecate_arg,
|
|
deprecate_func,
|
|
)
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
|
|
@deprecate_func(
|
|
since="9.99",
|
|
additional_msg="Instead, use new_func().",
|
|
removal_timeline="in 2 releases",
|
|
)
|
|
def _deprecated_func():
|
|
pass
|
|
|
|
|
|
class _Foo:
|
|
@deprecate_func(since="9.99", pending=True)
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
@property
|
|
@deprecate_func(since="9.99", is_property=True)
|
|
def my_property(self):
|
|
"""Property."""
|
|
return 0
|
|
|
|
@my_property.setter
|
|
@deprecate_func(since="9.99")
|
|
def my_property(self, value):
|
|
pass
|
|
|
|
@deprecate_func(since="9.99", additional_msg="Stop using this!")
|
|
def my_method(self):
|
|
"""Method."""
|
|
|
|
def normal_method(self):
|
|
"""Method."""
|
|
|
|
|
|
class TestDeprecationDecorators(QiskitTestCase):
|
|
"""Test that the decorators in ``utils.deprecation`` correctly log warnings and get added to
|
|
docstring."""
|
|
|
|
def test_deprecate_func_docstring(self) -> None:
|
|
"""Test that `@deprecate_func` adds the correct message to the docstring."""
|
|
|
|
self.assertEqual(
|
|
_deprecated_func.__doc__,
|
|
dedent(
|
|
f"""\
|
|
|
|
.. deprecated:: 9.99
|
|
The function ``{__name__}._deprecated_func()`` is deprecated as of Qiskit \
|
|
9.99. It will be removed in 2 releases. Instead, use new_func().
|
|
"""
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
_Foo.__init__.__doc__,
|
|
dedent(
|
|
f"""\
|
|
|
|
.. deprecated:: 9.99_pending
|
|
The class ``{__name__}._Foo`` is pending deprecation as of Qiskit 9.99. It \
|
|
will be marked deprecated in a future release, and then removed no earlier than 3 months after \
|
|
the release date.
|
|
"""
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
_Foo.my_method.__doc__,
|
|
dedent(
|
|
f"""\
|
|
Method.
|
|
|
|
.. deprecated:: 9.99
|
|
The method ``{__name__}._Foo.my_method()`` is deprecated as of Qiskit \
|
|
9.99. It will be removed no earlier than 3 months after the release date. Stop using this!
|
|
"""
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
_Foo.my_property.__doc__,
|
|
dedent(
|
|
f"""\
|
|
Property.
|
|
|
|
.. deprecated:: 9.99
|
|
The property ``{__name__}._Foo.my_property`` is deprecated as of Qiskit \
|
|
9.99. It will be removed no earlier than 3 months after the release date.
|
|
"""
|
|
),
|
|
)
|
|
|
|
def test_deprecate_func_package_name(self) -> None:
|
|
"""Test setting the package name works."""
|
|
|
|
@deprecate_func(since="9.99", package_name="riskit")
|
|
def my_func() -> None:
|
|
pass
|
|
|
|
with self.assertWarnsRegex(DeprecationWarning, "is deprecated as of riskit 9.99"):
|
|
my_func()
|
|
|
|
def test_deprecate_arg_docstring(self) -> None:
|
|
"""Test that `@deprecate_arg` adds the correct message to the docstring."""
|
|
|
|
@deprecate_arg("arg1", since="9.99", removal_timeline="in 2 releases")
|
|
@deprecate_arg("arg2", pending=True, since="9.99")
|
|
@deprecate_arg(
|
|
"arg3",
|
|
since="9.99",
|
|
deprecation_description="Using the argument arg3",
|
|
new_alias="new_arg3",
|
|
)
|
|
@deprecate_arg(
|
|
"arg4",
|
|
since="9.99",
|
|
additional_msg="Instead, use foo.",
|
|
# This predicate always fails, but it should not impact storing the deprecation
|
|
# metadata. That ensures the deprecation still shows up in our docs.
|
|
predicate=lambda arg4: False,
|
|
)
|
|
def my_func() -> None:
|
|
pass
|
|
|
|
self.assertEqual(
|
|
my_func.__doc__,
|
|
dedent(
|
|
f"""\
|
|
|
|
.. deprecated:: 9.99
|
|
``{__name__}.{my_func.__qualname__}()``'s argument ``arg4`` is deprecated as of \
|
|
Qiskit 9.99. It will be removed no earlier than 3 months after the release date. Instead, \
|
|
use foo.
|
|
|
|
.. deprecated:: 9.99
|
|
Using the argument arg3 is deprecated as of Qiskit 9.99. It will be \
|
|
removed no earlier than 3 months after the release date. Instead, use the argument ``new_arg3``, \
|
|
which behaves identically.
|
|
|
|
.. deprecated:: 9.99_pending
|
|
``{__name__}.{my_func.__qualname__}()``'s argument ``arg2`` is pending \
|
|
deprecation as of Qiskit 9.99. It will be marked deprecated in a future release, and then \
|
|
removed no earlier than 3 months after the release date.
|
|
|
|
.. deprecated:: 9.99
|
|
``{__name__}.{my_func.__qualname__}()``'s argument ``arg1`` is deprecated as of \
|
|
Qiskit 9.99. It will be removed in 2 releases.
|
|
"""
|
|
),
|
|
)
|
|
|
|
def test_deprecate_func_runtime_warning(self) -> None:
|
|
"""Test that `@deprecate_func` warns whenever the function is used."""
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
_deprecated_func()
|
|
with self.assertWarns(PendingDeprecationWarning):
|
|
instance = _Foo()
|
|
with self.assertWarns(DeprecationWarning):
|
|
instance.my_method()
|
|
with self.assertWarns(DeprecationWarning):
|
|
_ = instance.my_property
|
|
with self.assertWarns(DeprecationWarning):
|
|
instance.my_property = 1
|
|
instance.normal_method()
|
|
|
|
def test_deprecate_arg_runtime_warning(self) -> None:
|
|
"""Test that `@deprecate_arg` warns whenever the arguments are used.
|
|
|
|
Also check these edge cases:
|
|
* If `new_alias` is set, pass the old argument as the new alias.
|
|
* If `predicate` is set, only warn if the predicate is True.
|
|
"""
|
|
|
|
@deprecate_arg("arg1", new_alias="new_arg1", since="9.99")
|
|
@deprecate_arg("arg2", since="9.99")
|
|
@deprecate_arg("arg3", predicate=lambda arg3: arg3 == "deprecated value", since="9.99")
|
|
def my_func(
|
|
arg1: str = "a",
|
|
arg2: str = "a",
|
|
arg3: str = "a",
|
|
new_arg1: str | None = None,
|
|
) -> None:
|
|
del arg2
|
|
del arg3
|
|
# If the old arg was set, we should set its `new_alias` to that value.
|
|
if arg1 != "a":
|
|
self.assertEqual(new_arg1, "z")
|
|
if new_arg1 is not None:
|
|
self.assertEqual(new_arg1, "z")
|
|
|
|
# No warnings if no deprecated args used.
|
|
my_func()
|
|
my_func(new_arg1="z")
|
|
|
|
# Warn if argument is specified, regardless of positional vs kwarg.
|
|
with self.assertWarnsRegex(DeprecationWarning, "arg1"):
|
|
my_func("z")
|
|
with self.assertWarnsRegex(DeprecationWarning, "arg1"):
|
|
my_func(arg1="z")
|
|
with self.assertWarnsRegex(DeprecationWarning, "arg2"):
|
|
my_func("z", "z")
|
|
with self.assertWarnsRegex(DeprecationWarning, "arg2"):
|
|
my_func(arg2="z")
|
|
|
|
# Error if new_alias specified at the same time as old argument name.
|
|
with self.assertRaises(TypeError):
|
|
my_func("a", new_arg1="z")
|
|
with self.assertRaises(TypeError):
|
|
my_func(arg1="a", new_arg1="z")
|
|
with self.assertRaises(TypeError):
|
|
my_func("a", "a", "a", "z")
|
|
|
|
# Test the `predicate` functionality.
|
|
my_func(arg3="okay value")
|
|
with self.assertWarnsRegex(DeprecationWarning, "arg3"):
|
|
my_func(arg3="deprecated value")
|
|
with self.assertWarnsRegex(DeprecationWarning, "arg3"):
|
|
my_func("z", "z", "deprecated value")
|
|
|
|
def test_deprecate_arg_runtime_warning_variadic_args(self) -> None:
|
|
"""Test that `@deprecate_arg` errors when defined on a function using `*args` but can
|
|
handle `**kwargs`.
|
|
|
|
We know how to support *args robustly via `inspect.signature().bind()`, but it has worse
|
|
performance (see the commit history of PR #9884). Given how infrequent we expect
|
|
deprecations to involve *args, we simply error.
|
|
"""
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
@deprecate_arg("my_kwarg", since="9.99")
|
|
def my_func1(*args: int, my_kwarg: int | None = None) -> None:
|
|
del args
|
|
del my_kwarg
|
|
|
|
@deprecate_arg("my_kwarg", since="9.99")
|
|
def my_func2(my_kwarg: int | None = None, **kwargs) -> None:
|
|
# We assign an arbitrary variable `c` because it will be included in
|
|
# `my_func.__code__.co_varnames` and we want to make sure that does not break the
|
|
# implementation.
|
|
c = 0 # pylint: disable=unused-variable
|
|
del kwargs
|
|
del my_kwarg
|
|
|
|
# No warnings if no deprecated args used.
|
|
my_func2()
|
|
my_func2(another_arg=0, yet_another=0)
|
|
|
|
# Warn if argument is specified.
|
|
with self.assertWarnsRegex(DeprecationWarning, "my_kwarg"):
|
|
my_func2(my_kwarg=5)
|
|
with self.assertWarnsRegex(DeprecationWarning, "my_kwarg"):
|
|
my_func2(my_kwarg=5, another_arg=0, yet_another=0)
|
|
|
|
|
|
class AddDeprecationDocstringTest(QiskitTestCase):
|
|
"""Test that we correctly insert the deprecation directive at the right location.
|
|
|
|
When determining the ``expected`` output, manually modify the docstring of a function
|
|
(in any Qiskit repo) to have the same structure. Then, build the docs to make sure that it
|
|
renders correctly.
|
|
"""
|
|
|
|
def test_add_deprecation_docstring_no_meta_lines(self) -> None:
|
|
"""When no metadata lines like Args, the directive should be added to the end."""
|
|
|
|
def func1():
|
|
pass
|
|
|
|
add_deprecation_to_docstring(func1, msg="Deprecated!", since="9.99", pending=False)
|
|
self.assertEqual(
|
|
func1.__doc__,
|
|
dedent(
|
|
"""\
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
"""
|
|
),
|
|
)
|
|
|
|
def func2():
|
|
"""Docstring."""
|
|
|
|
add_deprecation_to_docstring(func2, msg="Deprecated!", since="9.99", pending=False)
|
|
self.assertEqual(
|
|
func2.__doc__,
|
|
dedent(
|
|
"""\
|
|
Docstring.
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
"""
|
|
),
|
|
)
|
|
|
|
indent = " "
|
|
|
|
def func3():
|
|
"""Docstring extending
|
|
to a new line."""
|
|
|
|
add_deprecation_to_docstring(func3, msg="Deprecated!", since="9.99", pending=False)
|
|
expected_doc = f"""Docstring extending
|
|
to a new line.
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
{indent}"""
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """Docstring extending
|
|
to a new line.
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
"""
|
|
|
|
self.assertEqual(func3.__doc__, expected_doc)
|
|
|
|
def func4():
|
|
"""
|
|
Docstring starting on a new line.
|
|
"""
|
|
|
|
add_deprecation_to_docstring(func4, msg="Deprecated!", since="9.99", pending=False)
|
|
expected_doc = f"""\
|
|
|
|
Docstring starting on a new line.
|
|
{indent}
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
{indent}"""
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """\
|
|
|
|
Docstring starting on a new line.
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
"""
|
|
|
|
self.assertEqual(
|
|
func4.__doc__,
|
|
expected_doc,
|
|
)
|
|
|
|
def func5():
|
|
"""
|
|
Paragraph 1, line 1.
|
|
Line 2.
|
|
|
|
Paragraph 2.
|
|
|
|
"""
|
|
|
|
expected_doc = f"""\
|
|
|
|
Paragraph 1, line 1.
|
|
Line 2.
|
|
|
|
Paragraph 2.
|
|
|
|
{indent}
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
{indent}"""
|
|
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """\
|
|
|
|
Paragraph 1, line 1.
|
|
Line 2.
|
|
|
|
Paragraph 2.
|
|
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
"""
|
|
|
|
add_deprecation_to_docstring(func5, msg="Deprecated!", since="9.99", pending=False)
|
|
self.assertEqual(
|
|
func5.__doc__,
|
|
expected_doc,
|
|
)
|
|
|
|
def func6():
|
|
"""Blah.
|
|
|
|
A list.
|
|
* element 1
|
|
* element 2
|
|
continued
|
|
"""
|
|
|
|
expected_doc = f"""Blah.
|
|
|
|
A list.
|
|
* element 1
|
|
* element 2
|
|
continued
|
|
{indent}
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
{indent}"""
|
|
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """Blah.
|
|
|
|
A list.
|
|
* element 1
|
|
* element 2
|
|
continued
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
"""
|
|
|
|
add_deprecation_to_docstring(func6, msg="Deprecated!", since="9.99", pending=False)
|
|
self.assertEqual(
|
|
func6.__doc__,
|
|
expected_doc,
|
|
)
|
|
|
|
def test_add_deprecation_docstring_meta_lines(self) -> None:
|
|
"""When there are metadata lines like Args, the directive should be inserted in-between the
|
|
summary and those lines."""
|
|
indent = " "
|
|
|
|
def func1():
|
|
"""
|
|
Returns:
|
|
Content.
|
|
|
|
Raises:
|
|
SomeError
|
|
"""
|
|
|
|
add_deprecation_to_docstring(func1, msg="Deprecated!", since="9.99", pending=False)
|
|
expected_doc = f"""\
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
{indent}
|
|
|
|
Returns:
|
|
Content.
|
|
|
|
Raises:
|
|
SomeError
|
|
{indent}"""
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """\
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
|
|
|
|
Returns:
|
|
Content.
|
|
|
|
Raises:
|
|
SomeError"""
|
|
|
|
self.assertEqual(
|
|
func1.__doc__,
|
|
expected_doc,
|
|
)
|
|
|
|
def func2():
|
|
"""Docstring.
|
|
|
|
Returns:
|
|
Content.
|
|
"""
|
|
|
|
add_deprecation_to_docstring(func2, msg="Deprecated!", since="9.99", pending=False)
|
|
expected_doc = f"""Docstring.
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
{indent}
|
|
|
|
Returns:
|
|
Content.
|
|
{indent}"""
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """Docstring.
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
|
|
|
|
Returns:
|
|
Content."""
|
|
|
|
self.assertEqual(func2.__doc__, expected_doc)
|
|
|
|
def func3():
|
|
"""
|
|
Docstring starting on a new line.
|
|
|
|
Paragraph 2.
|
|
|
|
Examples:
|
|
Content.
|
|
"""
|
|
|
|
expected_doc = f"""\
|
|
|
|
Docstring starting on a new line.
|
|
|
|
Paragraph 2.
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
{indent}
|
|
|
|
Examples:
|
|
Content.
|
|
{indent}"""
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """\
|
|
|
|
Docstring starting on a new line.
|
|
|
|
Paragraph 2.
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated!
|
|
|
|
|
|
Examples:
|
|
Content."""
|
|
|
|
add_deprecation_to_docstring(func3, msg="Deprecated!", since="9.99", pending=False)
|
|
self.assertEqual(
|
|
func3.__doc__,
|
|
expected_doc,
|
|
)
|
|
|
|
def test_add_deprecation_docstring_multiple_entries(self) -> None:
|
|
"""Multiple entries are appended correctly."""
|
|
|
|
def func1():
|
|
pass
|
|
|
|
add_deprecation_to_docstring(func1, msg="Deprecated #1!", since="9.99", pending=False)
|
|
add_deprecation_to_docstring(func1, msg="Deprecated #2!", since="9.99", pending=False)
|
|
self.assertEqual(
|
|
func1.__doc__,
|
|
dedent(
|
|
"""\
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated #1!
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated #2!
|
|
"""
|
|
),
|
|
)
|
|
|
|
indent = " "
|
|
|
|
def func2():
|
|
"""
|
|
Docstring starting on a new line.
|
|
"""
|
|
|
|
add_deprecation_to_docstring(func2, msg="Deprecated #1!", since="9.99", pending=False)
|
|
add_deprecation_to_docstring(func2, msg="Deprecated #2!", since="9.99", pending=False)
|
|
expected_doc = f"""\
|
|
|
|
Docstring starting on a new line.
|
|
{indent}
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated #1!
|
|
{indent}
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated #2!
|
|
{indent}"""
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """\
|
|
|
|
Docstring starting on a new line.
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated #1!
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated #2!
|
|
"""
|
|
|
|
self.assertEqual(
|
|
func2.__doc__,
|
|
expected_doc,
|
|
)
|
|
|
|
def func3():
|
|
"""Docstring.
|
|
|
|
Yields:
|
|
Content.
|
|
"""
|
|
|
|
expected_doc = f"""Docstring.
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated #1!
|
|
{indent}
|
|
{indent}
|
|
.. deprecated:: 9.99
|
|
Deprecated #2!
|
|
{indent}
|
|
|
|
Yields:
|
|
Content.
|
|
{indent}"""
|
|
|
|
if sys.version_info >= (3, 13, 0):
|
|
expected_doc = """Docstring.
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated #1!
|
|
|
|
|
|
.. deprecated:: 9.99
|
|
Deprecated #2!
|
|
|
|
|
|
Yields:
|
|
Content."""
|
|
|
|
add_deprecation_to_docstring(func3, msg="Deprecated #1!", since="9.99", pending=False)
|
|
add_deprecation_to_docstring(func3, msg="Deprecated #2!", since="9.99", pending=False)
|
|
self.assertEqual(
|
|
func3.__doc__,
|
|
expected_doc,
|
|
)
|
|
|
|
def test_add_deprecation_docstring_pending(self) -> None:
|
|
"""The version string should end in `_pending` when pending."""
|
|
|
|
def func():
|
|
pass
|
|
|
|
add_deprecation_to_docstring(func, msg="Deprecated!", since="9.99", pending=True)
|
|
self.assertEqual(
|
|
func.__doc__,
|
|
dedent(
|
|
"""\
|
|
|
|
.. deprecated:: 9.99_pending
|
|
Deprecated!
|
|
"""
|
|
),
|
|
)
|
|
|
|
def test_add_deprecation_docstring_since_not_set(self) -> None:
|
|
"""The version string should be `unknown` when ``None``."""
|
|
|
|
def func():
|
|
pass
|
|
|
|
add_deprecation_to_docstring(func, msg="Deprecated!", since=None, pending=False)
|
|
self.assertEqual(
|
|
func.__doc__,
|
|
dedent(
|
|
"""\
|
|
|
|
.. deprecated:: unknown
|
|
Deprecated!
|
|
"""
|
|
),
|
|
)
|
|
|
|
def test_add_deprecation_docstring_newline_msg_banned(self) -> None:
|
|
"""Test that `\n` is banned in the deprecation message, as it breaks Sphinx rendering."""
|
|
|
|
def func():
|
|
pass
|
|
|
|
with self.assertRaises(ValueError):
|
|
add_deprecation_to_docstring(func, msg="line1\nline2", since="9.99", pending=False)
|
|
|
|
def test_add_deprecation_docstring_initial_metadata_line_banned(self) -> None:
|
|
"""Test that the docstring cannot start with e.g. `Args:`."""
|
|
|
|
def func():
|
|
"""Args:
|
|
Foo.
|
|
"""
|
|
|
|
with self.assertRaises(ValueError):
|
|
add_deprecation_to_docstring(func, msg="Deprecated!", since="9.99", pending=False)
|