[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