Skip to content

[3.13] GH-121970: Extract changes into a new extension (GH-129105) #129109

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

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
'audit_events',
'availability',
'c_annotations',
'changes',
'glossary_search',
'lexers',
'pyspecific',
Expand Down
90 changes: 90 additions & 0 deletions Doc/tools/extensions/changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Support for documenting version of changes, additions, deprecations."""

from __future__ import annotations

from typing import TYPE_CHECKING

from sphinx.domains.changeset import (
VersionChange,
versionlabel_classes,
versionlabels,
)
from sphinx.locale import _ as sphinx_gettext

if TYPE_CHECKING:
from docutils.nodes import Node
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata


def expand_version_arg(argument: str, release: str) -> str:
"""Expand "next" to the current version"""
if argument == "next":
return sphinx_gettext("{} (unreleased)").format(release)
return argument


class PyVersionChange(VersionChange):
def run(self) -> list[Node]:
# Replace the 'next' special token with the current development version
self.arguments[0] = expand_version_arg(
self.arguments[0], self.config.release
)
return super().run()


class DeprecatedRemoved(VersionChange):
required_arguments = 2

_deprecated_label = sphinx_gettext(
"Deprecated since version %s, will be removed in version %s"
)
_removed_label = sphinx_gettext(
"Deprecated since version %s, removed in version %s"
)

def run(self) -> list[Node]:
# Replace the first two arguments (deprecated version and removed version)
# with a single tuple of both versions.
version_deprecated = expand_version_arg(
self.arguments[0], self.config.release
)
version_removed = self.arguments.pop(1)
if version_removed == "next":
raise ValueError(
"deprecated-removed:: second argument cannot be `next`"
)
self.arguments[0] = version_deprecated, version_removed

# Set the label based on if we have reached the removal version
current_version = tuple(map(int, self.config.version.split(".")))
removed_version = tuple(map(int, version_removed.split(".")))
if current_version < removed_version:
versionlabels[self.name] = self._deprecated_label
versionlabel_classes[self.name] = "deprecated"
else:
versionlabels[self.name] = self._removed_label
versionlabel_classes[self.name] = "removed"
try:
return super().run()
finally:
# reset versionlabels and versionlabel_classes
versionlabels[self.name] = ""
versionlabel_classes[self.name] = ""


def setup(app: Sphinx) -> ExtensionMetadata:
# Override Sphinx's directives with support for 'next'
app.add_directive("versionadded", PyVersionChange, override=True)
app.add_directive("versionchanged", PyVersionChange, override=True)
app.add_directive("versionremoved", PyVersionChange, override=True)
app.add_directive("deprecated", PyVersionChange, override=True)

# Register the ``.. deprecated-removed::`` directive
app.add_directive("deprecated-removed", DeprecatedRemoved)

return {
"version": "1.0",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
57 changes: 0 additions & 57 deletions Doc/tools/extensions/pyspecific.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from docutils.utils import new_document, unescape
from sphinx import addnodes
from sphinx.builders import Builder
from sphinx.domains.changeset import VersionChange, versionlabels, versionlabel_classes
from sphinx.domains.python import PyFunction, PyMethod, PyModule
from sphinx.locale import _ as sphinx_gettext
from sphinx.util.docutils import SphinxDirective
Expand Down Expand Up @@ -184,57 +183,6 @@ def run(self):
return PyMethod.run(self)


# Support for documenting version of changes, additions, deprecations

def expand_version_arg(argument, release):
"""Expand "next" to the current version"""
if argument == 'next':
return sphinx_gettext('{} (unreleased)').format(release)
return argument


class PyVersionChange(VersionChange):
def run(self):
# Replace the 'next' special token with the current development version
self.arguments[0] = expand_version_arg(self.arguments[0],
self.config.release)
return super().run()


class DeprecatedRemoved(VersionChange):
required_arguments = 2

_deprecated_label = sphinx_gettext('Deprecated since version %s, will be removed in version %s')
_removed_label = sphinx_gettext('Deprecated since version %s, removed in version %s')

def run(self):
# Replace the first two arguments (deprecated version and removed version)
# with a single tuple of both versions.
version_deprecated = expand_version_arg(self.arguments[0],
self.config.release)
version_removed = self.arguments.pop(1)
if version_removed == 'next':
raise ValueError(
'deprecated-removed:: second argument cannot be `next`')
self.arguments[0] = version_deprecated, version_removed

# Set the label based on if we have reached the removal version
current_version = tuple(map(int, self.config.version.split('.')))
removed_version = tuple(map(int, version_removed.split('.')))
if current_version < removed_version:
versionlabels[self.name] = self._deprecated_label
versionlabel_classes[self.name] = 'deprecated'
else:
versionlabels[self.name] = self._removed_label
versionlabel_classes[self.name] = 'removed'
try:
return super().run()
finally:
# reset versionlabels and versionlabel_classes
versionlabels[self.name] = ''
versionlabel_classes[self.name] = ''


# Support for including Misc/NEWS

issue_re = re.compile('(?:[Ii]ssue #|bpo-)([0-9]+)', re.I)
Expand Down Expand Up @@ -417,11 +365,6 @@ def setup(app):
app.add_role('issue', issue_role)
app.add_role('gh', gh_issue_role)
app.add_directive('impl-detail', ImplementationDetail)
app.add_directive('versionadded', PyVersionChange, override=True)
app.add_directive('versionchanged', PyVersionChange, override=True)
app.add_directive('versionremoved', PyVersionChange, override=True)
app.add_directive('deprecated', PyVersionChange, override=True)
app.add_directive('deprecated-removed', DeprecatedRemoved)
app.add_builder(PydocTopicsBuilder)
app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature)
app.add_object_type('pdbcommand', 'pdbcmd', '%s (pdb command)', parse_pdb_command)
Expand Down
Loading