[packages/python-releases] - initial, +patches to work with latest Sphinx and semantic_version for python2

qboosh qboosh at pld-linux.org
Sat Dec 6 19:11:23 CET 2025


commit ed4fc7cb0e66a6c6cc1d67c8d3e3c51dd83cf677
Author: Jakub Bogusz <qboosh at pld-linux.org>
Date:   Sat Dec 6 19:11:30 2025 +0100

    - initial, +patches to work with latest Sphinx and semantic_version for python2

 python-releases.spec            | 157 +++++++++++++++++++++++
 releases-requires.patch         |  11 ++
 releases-semantic_version.patch | 143 +++++++++++++++++++++
 releases-sphinx1.8.patch        | 269 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 580 insertions(+)
---
diff --git a/python-releases.spec b/python-releases.spec
new file mode 100644
index 0000000..16aba41
--- /dev/null
+++ b/python-releases.spec
@@ -0,0 +1,157 @@
+#
+# Conditional build:
+%bcond_without	doc	# Sphinx documentation
+%bcond_without	tests	# unit tests
+%bcond_without	python2 # CPython 2.x module
+%bcond_with	python3 # CPython 3.x module (built from python3-releases.spec)
+
+Summary:	Sphinx extension for changelog manipulation
+Summary(pl.UTF-8):	Rozszerzenie Sphinksa do operacji na rejestrze zmian
+Name:		python-releases
+# keep 1.x here for python2 support
+Version:	1.6.3
+Release:	1
+License:	BSD
+Group:		Libraries/Python
+#Source0Download: https://pypi.org/simple/releases/
+Source0:	https://files.pythonhosted.org/packages/source/r/releases/releases-%{version}.tar.gz
+# Source0-md5:	e3334a7ba426f895fb817a6147eefb7c
+Patch0:		releases-sphinx1.8.patch
+# https://github.com/bitprophet/releases/pull/86.patch (adjusted for 2.1.1)
+Patch1:		releases-semantic_version.patch
+Patch2:		releases-requires.patch
+URL:		https://pypi.org/project/releases/
+%if %{with python2}
+BuildRequires:	python-modules >= 1:2.7
+BuildRequires:	python-setuptools
+%if %{with tests}
+BuildRequires:	python-Sphinx >= 1.8
+BuildRequires:	python-mock >= 1.0.1
+BuildRequires:	python-semantic_version
+BuildRequires:	python-six >= 1.4.1
+BuildRequires:	python-spec >= 0.11.3
+%endif
+%endif
+%if %{with python3}
+BuildRequires:	python3-modules >= 1:3.4
+BuildRequires:	python3-setuptools
+%if %{with tests}
+BuildRequires:	python3-Sphinx >= 1.8
+BuildRequires:	python3-mock >= 1.0.1
+BuildRequires:	python3-semantic_version
+BuildRequires:	python3-six >= 1.4.1
+BuildRequires:	python3-spec >= 0.11.3
+%endif
+%endif
+BuildRequires:	rpm-pythonprov
+BuildRequires:	rpmbuild(macros) >= 1.714
+%if %{with doc}
+BuildRequires:	python-sphinx_rtd_theme >= 0.1.5
+BuildRequires:	sphinx-pdg-2 >= 1.8
+%endif
+Requires:	python-modules >= 1:2.7
+BuildArch:	noarch
+BuildRoot:	%{tmpdir}/%{name}-%{version}-root-%(id -u -n)
+
+%description
+Releases is a Sphinx extension designed to help you keep a source
+control friendly, merge friendly changelog file & turn it into useful,
+human readable HTML output.
+
+%description -l pl.UTF-8
+Releases to rozszerzenie Sphinksa zaprojektowane, aby pomóc utrzymywać
+plik logu zmian przyjazny dla kontroli wersji i łączenia gałęzi oraz
+zamieniać go w przydatne, czytelne dla człowieka wyjście HTML.
+
+%package -n python3-releases
+Summary:	Sphinx extension for changelog manipulation
+Summary(pl.UTF-8):	Rozszerzenie Sphinksa do operacji na rejestrze zmian
+Group:		Libraries/Python
+Requires:	python3-modules >= 1:3.4
+
+%description -n python3-releases
+Releases is a Sphinx extension designed to help you keep a source
+control friendly, merge friendly changelog file & turn it into useful,
+human readable HTML output.
+
+%description -n python3-releases -l pl.UTF-8
+Releases to rozszerzenie Sphinksa zaprojektowane, aby pomóc utrzymywać
+plik logu zmian przyjazny dla kontroli wersji i łączenia gałęzi oraz
+zamieniać go w przydatne, czytelne dla człowieka wyjście HTML.
+
+%package apidocs
+Summary:	API documentation for Python releases module
+Summary(pl.UTF-8):	Dokumentacja API modułu Pythona releases
+Group:		Documentation
+
+%description apidocs
+API documentation for Python releases module.
+
+%description apidocs -l pl.UTF-8
+Dokumentacja API modułu Pythona releases.
+
+%prep
+%setup -q -n releases-%{version}
+%patch -P0 -p1
+%patch -P1 -p1
+%patch -P2 -p1
+
+%build
+%if %{with python2}
+%py_build
+
+%if %{with tests}
+spec-2 -w tests
+%endif
+%endif
+
+%if %{with python3}
+%py3_build
+
+%if %{with tests}
+spec-3 -w tests
+%endif
+%endif
+
+%if %{with doc}
+PYTHONPATH=$(pwd) \
+sphinx-build-2 -b html docs docs/_build/html
+%endif
+
+%install
+rm -rf $RPM_BUILD_ROOT
+
+%if %{with python2}
+%py_install
+
+%py_postclean
+%endif
+
+%if %{with python3}
+%py3_install
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%if %{with python2}
+%files
+%defattr(644,root,root,755)
+%doc LICENSE README.rst
+%{py_sitescriptdir}/releases
+%{py_sitescriptdir}/releases-%{version}-py*.egg-info
+%endif
+
+%if %{with python3}
+%files -n python3-releases
+%defattr(644,root,root,755)
+%doc LICENSE README.rst
+%{py3_sitescriptdir}/releases
+%{py3_sitescriptdir}/releases-%{version}-py*.egg-info
+%endif
+
+%if %{with doc}
+%files apidocs
+%defattr(644,root,root,755)
+%doc docs/_build/html/{_static,*.html,*.js}
+%endif
diff --git a/releases-requires.patch b/releases-requires.patch
new file mode 100644
index 0000000..eef12bc
--- /dev/null
+++ b/releases-requires.patch
@@ -0,0 +1,11 @@
+--- releases-1.6.3/setup.py.orig	2020-01-11 01:27:20.000000000 +0100
++++ releases-1.6.3/setup.py	2025-12-01 21:18:27.131700049 +0100
+@@ -18,7 +18,7 @@ setup(
+     url='https://github.com/bitprophet/releases',
+     packages=['releases'],
+     install_requires=[
+-        'semantic_version<2.7',
++        'semantic_version',
+         'sphinx>=1.8',
+     ],
+     classifiers=[
diff --git a/releases-semantic_version.patch b/releases-semantic_version.patch
new file mode 100644
index 0000000..4dedd1f
--- /dev/null
+++ b/releases-semantic_version.patch
@@ -0,0 +1,143 @@
+From 8787236dffb7383427b3e1448ece9a5b3eaf5257 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= <raphael.barrois at polytechnique.org>
+Date: Sun, 8 Sep 2019 00:18:00 +0200
+Subject: [PATCH] Fix usage of semanticversion to intended API.
+
+The recent versions of python-semanticversion made changes to private
+APIs, removing the interaction between `Version(x, partial=True)` and
+`Spec()` (`partial=True` was designed for implementing the `Spec` class
+only)
+
+The code used these classes to exclude ranges of version whose major
+component didn't match a bugfix/issue range; the code went akin to:
+
+    Version('1', partial=True) in Spec('>=1.0')
+
+This no longer works; this patch changes that behaviour to exclude
+families where no actual release matches the bugfix/issue range - this
+should be more accurate.
+
+The patch also uses `Version.coerce`, the intended API to manage
+non semver-compliant version strings.
+
+The patch has been tested with both python-semanticversion==2.6.0 and
+python-semanticversion==2.8.1; it can be included to an upgraded version
+of `releases` even if users haven't yet upgraded python-semanticversion.
+---
+ releases/__init__.py |  2 +-
+ releases/models.py   | 54 ++++++++++++++++++++++++--------------------
+ 2 files changed, 30 insertions(+), 26 deletions(-)
+
+diff --git a/releases/__init__.py b/releases/__init__.py
+index 3c73f0b..48fc5f5 100644
+--- a/releases/__init__.py
++++ b/releases/__init__.py
+@@ -426,7 +426,7 @@ def handle_upcoming_major_release(entries, manager):
+     # to the line manager!
+     for obj in next_releases:
+         # TODO: update when Release gets tied closer w/ Version
+-        version = Version(obj.number)
++        version = Version.coerce(obj.number)
+         if version.minor == 0 and version.patch == 0:
+             manager.add_family(obj.family)
+ 
+diff --git a/releases/models.py b/releases/models.py
+index d980e9c..0517174 100644
+--- a/releases/models.py
++++ b/releases/models.py
+@@ -2,18 +2,10 @@
+ from operator import xor
+ 
+ from docutils import nodes
+-from semantic_version import Version as StrictVersion, Spec
++from semantic_version import Version, Spec
+ import six
+ 
+ 
+-class Version(StrictVersion):
+-    """
+-    Version subclass toggling ``partial=True`` by default.
+-    """
+-    def __init__(self, version_string, partial=True):
+-        super(Version, self).__init__(version_string, partial)
+-
+-
+ # Issue type list (keys) + color values
+ ISSUE_TYPES = {
+     'bug': 'A04040',
+@@ -122,7 +114,7 @@ def default_spec(self, manager):
+             buckets = self.minor_releases(manager)
+             if buckets:
+                 specstr = ">={}".format(max(buckets))
+-        return Spec(specstr) if specstr else Spec()
++        return Spec(specstr) if specstr else Spec('*')
+ 
+     def add_to_manager(self, manager):
+         """
+@@ -130,32 +122,43 @@ def add_to_manager(self, manager):
+         """
+         # Derive version spec allowing us to filter against major/minor buckets
+         spec = self.spec or self.default_spec(manager)
+-        # Only look in appropriate major version/family; if self is an issue
+-        # declared as living in e.g. >=2, this means we don't even bother
+-        # looking in the 1.x family.
+-        families = [Version(str(x)) for x in manager]
+-        versions = list(spec.filter(families))
+-        for version in versions:
+-            family = version.major
+-            # Within each family, we further limit which bugfix lines match up
+-            # to what self cares about (ignoring 'unreleased' until later)
+-            candidates = [
+-                Version(x)
++
++        # Browse through families, adding us to every line we match.
++        for family in manager:
++            # Map changelog keys to Version objects, keeping a link
++            # to the original text
++            versions = {
++                Version.coerce(x): x
+                 for x in manager[family]
+                 if not x.startswith('unreleased')
+-            ]
+-            # Select matching release lines (& stringify)
++            }
++
++            # Bail out if no listed version (included pending feature/bugfix)
++            # match self.spec: if self is an issue for >=2, don't look
++            # at the 1.x family. If self is an issue for >=1.0, include it
++            # in the 1.x family even if no 1.0 release exists yet.
++            candidates = list(spec.filter(versions))
++            # Also compare the first release in the family, for cases
++            # where no release has been performed yet.
++            if not candidates and Version.coerce(str(family)) not in spec:
++                continue
++
++            # `buckets` has the list of line families
+             buckets = []
+-            bugfix_buckets = [str(x) for x in spec.filter(candidates)]
++            bugfix_buckets = candidates
+             # Add back in unreleased_* as appropriate
+             # TODO: probably leverage Issue subclasses for this eventually?
+             if self.is_buglike:
+-                buckets.extend(bugfix_buckets)
++                # Convert back Version() to line
++                buckets.extend([
++                    versions[bucket] for bucket in bugfix_buckets
++                ])
+                 # Don't put into JUST unreleased_bugfix; it implies that this
+                 # major release/family hasn't actually seen any releases yet
+                 # and only exists for features to go into.
+                 if bugfix_buckets:
+                     buckets.append('unreleased_bugfix')
++
+             # Obtain list of minor releases to check for "haven't had ANY
+             # releases yet" corner case, in which case ALL issues get thrown in
+             # unreleased_feature for the first release to consume.
+@@ -164,6 +167,7 @@ def add_to_manager(self, manager):
+             no_releases = not self.minor_releases(manager)
+             if self.is_featurelike or self.backported or no_releases:
+                 buckets.append('unreleased_feature')
++
+             # Now that we know which buckets are appropriate, add ourself to
+             # all of them. TODO: or just...do it above...instead...
+             for bucket in buckets:
diff --git a/releases-sphinx1.8.patch b/releases-sphinx1.8.patch
new file mode 100644
index 0000000..ad660e1
--- /dev/null
+++ b/releases-sphinx1.8.patch
@@ -0,0 +1,269 @@
+From 5756c09446b47674a050df6c112b529fead5329b Mon Sep 17 00:00:00 2001
+From: Jeff Forcier <jeff at bitprophet.org>
+Date: Fri, 17 Jan 2020 15:29:53 -0500
+Subject: [PATCH] Drop support for Sphinx <1.8
+
+---
+ .travis.yml        |  3 +--
+ README.rst         |  2 +-
+ docs/changelog.rst | 10 ++++++++
+ releases/util.py   | 60 ++++++++++------------------------------------
+ setup.py           |  2 +-
+ 5 files changed, 25 insertions(+), 52 deletions(-)
+
+#diff --git a/.travis.yml b/.travis.yml
+#index 9e4bd84..3a8f964 100644
+#--- a/.travis.yml
+#+++ b/.travis.yml
+#@@ -8,8 +8,7 @@ python:
+#   - "pypy"
+#   #- "pypy3" # Looks like Sphinx (as of 1.4.1) is not pypy3 compat
+# env:
+#-  - SPHINX=">=1.3,<1.4"
+#-  - SPHINX=">=1.7,<1.8"
+#+  - SPHINX=">=1.8,<2.0"
+#   - SPHINX=">=2.3,<2.4"
+# jobs:
+#   exclude:
+diff --git a/README.rst b/README.rst
+index bdaac6b..d4cbb3e 100644
+--- a/README.rst
++++ b/README.rst
+@@ -5,7 +5,7 @@ What is Releases?
+ =================
+ 
+ Releases is a Python (2.7, 3.4+) compatible `Sphinx <http://sphinx-doc.org>`_
+-(1.3+) extension designed to help you keep a source control friendly, merge
++(1.8+) extension designed to help you keep a source control friendly, merge
+ friendly changelog file & turn it into useful, human readable HTML output.
+ 
+ Specifically:
+diff --git a/docs/changelog.rst b/docs/changelog.rst
+index 20d813e..72c7648 100644
+--- a/docs/changelog.rst
++++ b/docs/changelog.rst
+@@ -2,6 +2,16 @@
+ Changelog
+ =========
+ 
++- :support:`-` Dropped support for Sphinx <1.8, which is now pretty rare in the
++  wild. This makes it easier to support Sphinx 1.8+ and lets us drop an
++  ever-growing amount of compatibility code for Sphinx 1.3-1.7.
++
++  .. warning::
++    This change is technically backwards incompatible, but our own API and
++    behavior is not changing, and Sphinx itself did not make breaking changes
++    (that we're aware of) in 1.8, so as long as you upgrade your Sphinx along
++    with your Releases, you should be okay.
++
+ - :release:`1.6.3 <2020-01-10>`
+ - :support:`87 backported` (via :issue:`88`) Our upper Sphinx version limit was
+   mostly defensive and at this point is just too old to even build on
+diff --git a/releases/util.py b/releases/util.py
+index 12e3365..5a069b3 100644
+--- a/releases/util.py
++++ b/releases/util.py
+@@ -11,25 +11,10 @@
+ from docutils.io import NullOutput
+ from docutils.nodes import bullet_list
+ from sphinx.application import Sphinx # not exposed at top level
+-try:
+-    from sphinx.io import (
+-        SphinxStandaloneReader, SphinxFileInput, SphinxDummyWriter,
+-    )
+-except ImportError:
+-    # NOTE: backwards compat with Sphinx 1.3
+-    from sphinx.environment import (
+-        SphinxStandaloneReader, SphinxFileInput, SphinxDummyWriter,
+-    )
+-# sphinx_domains is only in Sphinx 1.5+, but is presumably necessary from then
+-# onwards.
+-try:
+-    from sphinx.util.docutils import sphinx_domains
+-except ImportError:
+-    # Just dummy it up.
+-    from contextlib import contextmanager
+-    @contextmanager
+-    def sphinx_domains(env):
+-        yield
++from sphinx.io import (
++    SphinxStandaloneReader, SphinxFileInput, SphinxDummyWriter,
++)
++from sphinx.util.docutils import sphinx_domains
+ 
+ from . import construct_releases, setup
+ 
+@@ -134,35 +119,22 @@ def get_doctree(path, **kwargs):
+     # Create & init a BuildEnvironment. Mm, tasty side effects.
+     app._init_env(freshenv=True)
+     env = app.env
+-    # More arity/API changes: Sphinx 1.3/1.4-ish require one to pass in the app
+-    # obj in BuildEnvironment.update(); modern Sphinx performs that inside
+-    # Application._init_env() (which we just called above) and so that kwarg is
+-    # removed from update(). EAFP.
+-    kwargs = dict(
++    env.update(
+         config=app.config,
+         srcdir=root,
+         doctreedir=app.doctreedir,
+-        app=app,
+     )
+-    try:
+-        env.update(**kwargs)
+-    except TypeError:
+-        # Assume newer Sphinx w/o an app= kwarg
+-        del kwargs['app']
+-        env.update(**kwargs)
++    # Update "temp" data (must be done here as it's wiped on update())
++    env.temp_data['docname'] = docname
+     # Code taken from sphinx.environment.read_doc; easier to manually call
+     # it with a working Environment object, instead of doing more random crap
+     # to trick the higher up build system into thinking our single changelog
+     # document was "updated".
+-    env.temp_data['docname'] = docname
+     env.app = app
+-    # NOTE: SphinxStandaloneReader API changed in 1.4 :(
+     reader_kwargs = {
+         'app': app,
+         'parsers': env.config.source_parsers,
+     }
+-    if sphinx.version_info[:2] < (1, 4):
+-        del reader_kwargs['app']
+     # This monkeypatches (!!!) docutils to 'inject' all registered Sphinx
+     # domains' roles & so forth. Without this, rendering the doctree lacks
+     # almost all Sphinx magic, including things like :ref: and :doc:!
+@@ -254,12 +226,10 @@ def make_app(**kwargs):
+     load_extensions = kwargs.pop('load_extensions', False)
+     real_conf = None
+     try:
+-        # Sphinx <1.6ish
+-        Sphinx._log = lambda self, message, wfile, nonl=False: None
+-        # Sphinx >=1.6ish. Technically still lets Very Bad Things through,
+-        # unlike the total muting above, but probably OK.
+-        # NOTE: used to just do 'sphinx' but that stopped working, even on
+-        # sphinx 1.6.x. Weird. Unsure why hierarchy not functioning.
++        # Turn off most logging, which is rarely useful and usually just gums
++        # up the output of whatever tool is calling us.
++        # NOTE: used to just do 'sphinx' but that stopped working. Unsure why
++        # hierarchy not functioning.
+         for name in ('sphinx', 'sphinx.sphinx.application'):
+             logging.getLogger(name).setLevel(logging.ERROR)
+         # App API seems to work on all versions so far.
+@@ -300,13 +270,7 @@ def make_app(**kwargs):
+         config['releases_{}'.format(name)] = kwargs[name]
+     # Stitch together as the sphinx app init() usually does w/ real conf files
+     app.config._raw_config = config
+-    # init_values() requires a 'warn' runner on Sphinx 1.3-1.6, so if we seem
+-    # to be hitting arity errors, give it a dummy such callable. Hopefully
+-    # calling twice doesn't introduce any wacko state issues :(
+-    try:
+-        app.config.init_values()
+-    except TypeError: # boy I wish Python had an ArityError or w/e
+-        app.config.init_values(lambda x: x)
++    app.config.init_values()
+     # Initialize extensions (the internal call to this happens at init time,
+     # which of course had no valid config yet here...)
+     if load_extensions:
+diff --git a/setup.py b/setup.py
+index 10b17eb..6cefb55 100644
+--- a/setup.py
++++ b/setup.py
+@@ -19,7 +19,7 @@
+     packages=['releases'],
+     install_requires=[
+         'semantic_version<2.7',
+-        'sphinx>=1.3',
++        'sphinx>=1.8',
+     ],
+     classifiers=[
+         'Development Status :: 5 - Production/Stable',
+From fd620847a179c2587b75c710925bc71158258e01 Mon Sep 17 00:00:00 2001
+From: Jeff Forcier <jeff at bitprophet.org>
+Date: Fri, 17 Jan 2020 21:10:04 -0500
+Subject: [PATCH] sphinx.io.read_doc now does exactly what we needed
+
+No idea when they added it, hopefully after I wrote
+all those gross glarly hacks. And good riddance!
+---
+ releases/util.py | 59 ++++--------------------------------------------
+ 1 file changed, 4 insertions(+), 55 deletions(-)
+
+diff --git a/releases/util.py b/releases/util.py
+index 661bd46..3c3e810 100644
+--- a/releases/util.py
++++ b/releases/util.py
+@@ -6,15 +6,9 @@
+ import os
+ from tempfile import mkdtemp
+ 
+-import sphinx
+-from docutils.core import Publisher
+-from docutils.io import NullOutput
+ from docutils.nodes import bullet_list
+ from sphinx.application import Sphinx # not exposed at top level
+-from sphinx.io import (
+-    SphinxStandaloneReader, SphinxFileInput, SphinxDummyWriter,
+-)
+-from sphinx.util.docutils import sphinx_domains
++from sphinx.io import read_doc
+ 
+ from . import construct_releases, setup
+ 
+@@ -118,54 +112,9 @@ def get_doctree(path, **kwargs):
+     # TODO: this only works for top level changelog files (i.e. ones where
+     # their dirname is the project/doc root)
+     app = make_app(srcdir=root, **kwargs)
+-    # Create & init a BuildEnvironment. Mm, tasty side effects.
+-    app._init_env(freshenv=True)
+-    env = app.env
+-    env.update(
+-        config=app.config,
+-        srcdir=root,
+-        doctreedir=app.doctreedir,
+-    )
+-    # Update "temp" data (must be done here as it's wiped on update())
+-    env.temp_data['docname'] = docname
+-    # Code taken from sphinx.environment.read_doc; easier to manually call
+-    # it with a working Environment object, instead of doing more random crap
+-    # to trick the higher up build system into thinking our single changelog
+-    # document was "updated".
+-    env.app = app
+-    reader_kwargs = {
+-        'app': app,
+-        'parsers': env.config.source_parsers,
+-    }
+-    # This monkeypatches (!!!) docutils to 'inject' all registered Sphinx
+-    # domains' roles & so forth. Without this, rendering the doctree lacks
+-    # almost all Sphinx magic, including things like :ref: and :doc:!
+-    with sphinx_domains(env):
+-        try:
+-            reader = SphinxStandaloneReader(**reader_kwargs)
+-        except TypeError:
+-            # If we import from io, this happens automagically, not in API
+-            del reader_kwargs['parsers']
+-            reader = SphinxStandaloneReader(**reader_kwargs)
+-        pub = Publisher(reader=reader,
+-                        writer=SphinxDummyWriter(),
+-                        destination_class=NullOutput)
+-        pub.set_components(None, 'restructuredtext', None)
+-        pub.process_programmatic_settings(None, env.settings, None)
+-        # NOTE: docname derived higher up, from our given path
+-        src_path = env.doc2path(docname)
+-        source = SphinxFileInput(
+-            app,
+-            env,
+-            source=None,
+-            source_path=src_path,
+-            encoding=env.config.source_encoding,
+-        )
+-        pub.source = source
+-        pub.settings._source = src_path
+-        pub.set_destination(None, None)
+-        pub.publish()
+-        return app, pub.document
++    app.env.temp_data['docname'] = docname
++    doctree = read_doc(app, app.env, path)
++    return app, doctree
+ 
+ 
+ def load_conf(srcdir):
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/python-releases.git/commitdiff/ed4fc7cb0e66a6c6cc1d67c8d3e3c51dd83cf677



More information about the pld-cvs-commit mailing list