Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add add_deprecation_to_docstring for docsite deprecation support #9685

Merged
merged 23 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e8bd369
Add deprecations to function docstring
Eric-Arellano Feb 28, 2023
2679c84
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into de…
Eric-Arellano Feb 28, 2023
f5a49e4
Work around issue with multiline strings
Eric-Arellano Mar 1, 2023
5eab996
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into de…
Eric-Arellano Mar 1, 2023
300903e
Merge branch 'main' into deprecation-via-docstring
Eric-Arellano Mar 1, 2023
581e206
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into de…
Eric-Arellano Mar 3, 2023
803492d
Better variable name
Eric-Arellano Mar 3, 2023
36b759e
Merge branch 'deprecation-via-docstring' of https://github.com/Eric-A…
Eric-Arellano Mar 3, 2023
4fff65d
Jake's review feedback
Eric-Arellano Mar 3, 2023
a005a9d
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into de…
Eric-Arellano Mar 6, 2023
d399caa
Make Napoleon check case-insensitive
Eric-Arellano Mar 6, 2023
f733a6f
Promote add_deprecation_to_docstring to be public
Eric-Arellano Mar 6, 2023
d96714a
Properly error when metadata line is the first line
Eric-Arellano Mar 6, 2023
aa93cca
Tests feedback: don't use helper function and simplify instructions t…
Eric-Arellano Mar 6, 2023
d5f92cf
Merge branch 'main' into deprecation-via-docstring
Eric-Arellano Mar 6, 2023
c425e30
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into de…
Eric-Arellano Mar 8, 2023
285021f
Don't use the function with @deprecate_function and @deprecate_argume…
Eric-Arellano Mar 8, 2023
6541f9d
Update qiskit/utils/deprecation.py
Eric-Arellano Mar 10, 2023
bf28d6b
Merge branch 'main' into deprecation-via-docstring
Eric-Arellano Mar 10, 2023
54e1ebc
Update qiskit/utils/deprecation.py
Eric-Arellano Mar 10, 2023
fef76a6
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into de…
Eric-Arellano Mar 13, 2023
7e21cc5
Add release note
Eric-Arellano Mar 13, 2023
117f64e
Merge branch 'deprecation-via-docstring' of https://github.com/Eric-A…
Eric-Arellano Mar 13, 2023
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
118 changes: 116 additions & 2 deletions qiskit/utils/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

import functools
import warnings
from typing import Any, Dict, Optional, Type
from typing import Any, Callable, Dict, Optional, Type


def deprecate_arguments(
kwarg_map: Dict[str, str],
kwarg_map: Dict[str, Optional[str]],
category: Type[Warning] = DeprecationWarning,
*,
since: Optional[str] = None,
Expand Down Expand Up @@ -113,3 +113,117 @@ def _rename_kwargs(
)

kwargs[new_arg] = kwargs.pop(old_arg)


# We insert deprecations in-between the description and Napoleon's meta sections. The below is from
# https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#docstring-sections. We use
# lowercase because Napoleon is case-insensitive.
_NAPOLEON_META_LINES = frozenset(
{
"args:",
"arguments:",
"attention:",
"attributes:",
"caution:",
"danger:",
"error:",
"example:",
"examples:",
"hint:",
"important:",
"keyword args:",
"keyword arguments:",
"note:",
"notes:",
"other parameters:",
"parameters:",
"return:",
"returns:",
"raises:",
"references:",
"see also:",
"tip:",
"todo:",
"warning:",
"warnings:",
"warn:",
"warns:",
"yield:",
"yields:",
}
)


def add_deprecation_to_docstring(
func: Callable, msg: str, *, since: Optional[str], pending: bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the point of the *?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It requires you to use keyword arguments for everything after the star. The reasons for that:

  1. Call sites are clearer, rather than seeing a True or False with no context. IDEs make this less painful by showing what argument names are, but there are many contexts you read code w/o that, like GitHub or Vim.
  2. More flexibility for the future of the API because we no longer need to worry about the order of positional arguments. Deprecating positional arguments is a pain.

) -> None:
"""Dynamically insert the deprecation message into ``func``'s docstring.

Args:
func: The function to modify.
msg: The full deprecation message.
since: The version the deprecation started at.
pending: Is the deprecation still pending?
"""
if "\n" in msg:
raise ValueError(
"Deprecation messages cannot contain new lines (`\\n`), but the deprecation for "
f'{func.__qualname__} had them. Usually this happens when using `"""` multiline '
f"strings; instead, use string concatenation.\n\n"
"This is a simplification to facilitate deprecation messages being added to the "
"documentation. If you have a compelling reason to need "
"new lines, feel free to improve this function or open a request at "
"https://github.com/Qiskit/qiskit-terra/issues."
)
Comment on lines +169 to +177
Copy link
Member

@jakelishman jakelishman Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a bit hypocritical to complain that the developer uses a newline in their warning message when we do the exact same thing in this error message ;).

edit: not saying we need to change this. The situations are different.


if since is None:
version_str = "unknown"
else:
version_str = f"{since}_pending" if pending else since
jakelishman marked this conversation as resolved.
Show resolved Hide resolved

indent = ""
meta_index = None
if func.__doc__:
original_lines = func.__doc__.splitlines()
content_encountered = False
for i, line in enumerate(original_lines):
stripped = line.strip()

# Determine the indent based on the first line with content. But, we don't consider the
# first line, which corresponds to the format """Docstring.""", as it does not properly
# capture the indentation of lines beneath it.
if not content_encountered and i != 0 and stripped:
num_leading_spaces = len(line) - len(line.lstrip())
indent = " " * num_leading_spaces
content_encountered = True

if stripped.lower() in _NAPOLEON_META_LINES:
meta_index = i
if content_encountered is not True:
Eric-Arellano marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(
"add_deprecation_to_docstring cannot currently handle when a Napoleon "
"metadata line like 'Args' is the very first line of docstring, "
f'e.g. `"""Args:`. So, it cannot process {func.__qualname__}. Instead, '
f'move the metadata line to the second line, e.g.:\n\n"""\nArgs:'
)
# We can stop checking since we only care about the first meta line, and
# we've validated content_encountered is True to determine the indent.
break
else:
original_lines = []

# We defensively include new lines in the beginning and end. This is sometimes necessary,
# depending on the original docstring. It is not a big deal to have extra, other than `help()`
# being a little longer.
new_lines = [
indent,
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
f"{indent}.. deprecated:: {version_str}",
f"{indent} {msg}",
Eric-Arellano marked this conversation as resolved.
Show resolved Hide resolved
indent,
]

if meta_index:
original_lines[meta_index - 1 : meta_index - 1] = new_lines
else:
original_lines.extend(new_lines)
func.__doc__ = "\n".join(original_lines)
Loading