Your message dated Wed, 02 Sep 2026 13:35:05 +0000
with message-id <[email protected]>
and subject line Bug#1146357: fixed in python-repoze.who 3.1.0-2
has caused the Debian Bug report #1146357,
regarding python-repoze.who: FTBFS against python 3.15rc1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact [email protected]
immediately.)


-- 
1146357: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1146357
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: src:python-repoze.who
Version: 3.1.0-1
User: [email protected]
Usertags: python3.15
Tags: patch, ftbfs, forky, sid

Hi!

While rebuilding the python related packages against the Python 3.15rc1
version we found that python-repoze.who fails to build from source [1].

The build errors when trying to use the deprecated pkg_resources. I
found that the upstream patch to modernize the build tooling takes care
of the problem [2].

I applied the upstream fix in the sandbox [3] to be able to build the
packages that depend on python-repoze.who, please consider applying the
patch to support the upcoming 3.15 version.

Happy hacking,

[1]: 
https://debusine.debian.net/debian/r-python-python3.15/work-request/1075339/
[2]: 
https://github.com/repoze/repoze.who/commit/8521c98925ac694b62d1c5c8ce1142df6384a3f8
[3]: https://debusine.debian.net/debian/r-python-python3.15/

--
"Can you imagine what I would do if I could do all I can?" -- Sun Tzu
Saludos /\/\ /\ >< `/
From 8521c98925ac694b62d1c5c8ce1142df6384a3f8 Mon Sep 17 00:00:00 2001
From: Alessandro Molina <[email protected]>
Date: Tue, 12 May 2026 15:01:36 +0200
Subject: [PATCH] chore: modernize repoze.who

- Add support for Python 3.14.
- Drop support for Python < 3.10.
- Drop use of deprecated 'pkg_resources':
  - Drop use of entry points in favor of  local dotted name resolution.
  - Adopt "native" namespaces.
---
 pyproject.toml                            | 62 +++++++++++++++
 repoze/__init__.py                        |  2 -
 repoze/who/__init__.py                    |  2 -
 repoze/who/config.py                      |  8 +-
 repoze/who/middleware.py                  |  6 +-
 repoze/who/plugins/__init__.py            |  2 -
 repoze/who/restrict.py                    |  4 +-
 repoze/who/tests/test_namespace_compat.py | 92 +++++++++++++++++++++++
 repoze/who/tests/test_utils.py            | 38 ++++++++++
 repoze/who/utils.py                       | 14 +++-
 setup.cfg                                 |  5 --
 setup.py                                  | 80 +-------------------
 12 files changed, 213 insertions(+), 102 deletions(-)
 create mode 100644 pyproject.toml
 delete mode 100644 repoze/__init__.py
 delete mode 100644 repoze/who/__init__.py
 delete mode 100644 repoze/who/plugins/__init__.py
 create mode 100644 repoze/who/tests/test_namespace_compat.py
 create mode 100644 repoze/who/tests/test_utils.py
 delete mode 100644 setup.cfg

diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..9c2abae
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,62 @@
+[build-system]
+requires = ["setuptools>=68", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "repoze.who"
+version = "3.1.0"
+description = "repoze.who is an identification and authentication framework for WSGI."
+dynamic = ["readme"]
+requires-python = ">=3.10"
+keywords = ["web", "application", "server", "wsgi", "zope"]
+license = { text = "BSD-derived (http://www.repoze.org/LICENSE.txt)" }
+authors = [
+  { name = "Agendaless Consulting", email = "[email protected]" },
+]
+classifiers = [
+  "Development Status :: 5 - Production/Stable",
+  "Intended Audience :: Developers",
+  "Programming Language :: Python :: 3",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: 3.12",
+  "Programming Language :: Python :: 3.13",
+  "Programming Language :: Python :: 3.14",
+  "Programming Language :: Python :: Implementation :: CPython",
+  "Programming Language :: Python :: Implementation :: PyPy",
+  "Topic :: Internet :: WWW/HTTP",
+  "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
+  "Topic :: Internet :: WWW/HTTP :: WSGI",
+  "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+]
+dependencies = [
+  "WebOb",
+  "zope.interface",
+  "legacy-cgi; python_version > '3.12'",
+]
+
+[project.urls]
+Homepage = "http://www.repoze.org";
+
+[project.optional-dependencies]
+docs = ["Sphinx", "repoze.sphinx.autointerface"]
+
+[project.entry-points."paste.filter_app_factory"]
+test = "repoze.who.middleware:make_test_middleware"
+config = "repoze.who.config:make_middleware_with_config"
+predicate = "repoze.who.restrict:make_predicate_restriction"
+authenticated = "repoze.who.restrict:make_authenticated_restriction"
+
+[tool.setuptools]
+include-package-data = false
+zip-safe = false
+
+[tool.setuptools.packages.find]
+include = ["repoze*"]
+exclude = ["repoze.who.tests*", "repoze.who.plugins.tests*"]
+
+[tool.setuptools.dynamic]
+readme = { file = ["README.rst", "CHANGES.rst"], content-type = "text/x-rst" }
+
+[tool.pytest.ini_options]
+consider_namespace_packages = true
diff --git a/repoze/__init__.py b/repoze/__init__.py
deleted file mode 100644
index a958a99..0000000
--- a/repoze/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# repoze package
-__import__('pkg_resources').declare_namespace(__name__)
diff --git a/repoze/who/__init__.py b/repoze/who/__init__.py
deleted file mode 100644
index ccaa6c7..0000000
--- a/repoze/who/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# repoze.who package
-__import__('pkg_resources').declare_namespace(__name__) #pragma NO COVERAGE
diff --git a/repoze/who/config.py b/repoze/who/config.py
index fe368db..a66fa10 100644
--- a/repoze/who/config.py
+++ b/repoze/who/config.py
@@ -3,7 +3,6 @@
 import configparser
 from io import StringIO
 import logging
-from pkg_resources import EntryPoint
 import sys
 import warnings
 
@@ -16,10 +15,7 @@ from repoze.who.interfaces import IMetadataProvider
 from repoze.who.interfaces import IPlugin
 from repoze.who.interfaces import IRequestClassifier
 from repoze.who.middleware import PluggableAuthenticationMiddleware
-
-def _resolve(name):
-    if name:
-        return EntryPoint.parse('x=%s' % name).resolve()
+from repoze.who.utils import resolveDotted
 
 class WhoConfig:
     def __init__(self, here):
@@ -36,7 +32,7 @@ class WhoConfig:
     def _makePlugin(self, name, iface, options=None):
         if options is None:
             options = {}
-        obj = _resolve(name)
+        obj = resolveDotted(name)
         if not iface.providedBy(obj):
             obj = obj(**options)
         return obj
diff --git a/repoze/who/middleware.py b/repoze/who/middleware.py
index 1ae98a3..9e5526a 100644
--- a/repoze/who/middleware.py
+++ b/repoze/who/middleware.py
@@ -187,7 +187,7 @@ def make_test_middleware(app, global_conf):
     """ Functionally equivalent to
 
     [plugin:redirector]
-    use = repoze.who.plugins.redirector.RedirectorPlugin
+    use = repoze.who.plugins.redirector:RedirectorPlugin
     login_url = /login.html
 
     [plugin:auth_tkt]
@@ -196,11 +196,11 @@ def make_test_middleware(app, global_conf):
     cookie_name = oatmeal
 
     [plugin:basicauth]
-    use = repoze.who.plugins.basicauth.BasicAuthPlugin
+    use = repoze.who.plugins.basicauth:BasicAuthPlugin
     realm = repoze.who
 
     [plugin:htpasswd]
-    use = repoze.who.plugins.htpasswd.HTPasswdPlugin
+    use = repoze.who.plugins.htpasswd:HTPasswdPlugin
     filename = <...>
     check_fn = repoze.who.plugins.htpasswd:crypt_check
 
diff --git a/repoze/who/plugins/__init__.py b/repoze/who/plugins/__init__.py
deleted file mode 100644
index fc15384..0000000
--- a/repoze/who/plugins/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# repoze.who.plugins package
-__import__('pkg_resources').declare_namespace(__name__) #pragma NO COVERAGE
diff --git a/repoze/who/restrict.py b/repoze/who/restrict.py
index d74d34c..d2c18dc 100644
--- a/repoze/who/restrict.py
+++ b/repoze/who/restrict.py
@@ -1,5 +1,5 @@
 # Authorization middleware
-from pkg_resources import EntryPoint
+from repoze.who.utils import resolveDotted
 
 def authenticated_predicate():
     def _predicate(environ):
@@ -27,5 +27,5 @@ def make_authenticated_restriction(app, global_config, enabled=True):
 def make_predicate_restriction(app, global_config,
                                predicate, enabled=True, **kw):
     if isinstance(predicate, str):
-        predicate = EntryPoint.parse('x=%s' % predicate).resolve()
+        predicate = resolveDotted(predicate)
     return PredicateRestriction(app, predicate, enabled, **kw)
diff --git a/repoze/who/tests/test_namespace_compat.py b/repoze/who/tests/test_namespace_compat.py
new file mode 100644
index 0000000..c6239bb
--- /dev/null
+++ b/repoze/who/tests/test_namespace_compat.py
@@ -0,0 +1,92 @@
+import pathlib
+import shutil
+import sys
+import tempfile
+import unittest
+
+
+class NamespaceCompatibilityTests(unittest.TestCase):
+
+    def _create_sibling(self):
+        workspace = pathlib.Path(tempfile.mkdtemp())
+        sibling_root = workspace / "sibling"
+        plugin_pkg = sibling_root / "repoze" / "who" / "plugins"
+        plugin_pkg.mkdir(parents=True)
+
+        (plugin_pkg / "sibling_identifier.py").write_text(
+            "from zope.interface import implementer\n"
+            "from repoze.who.interfaces import IIdentifier\n"
+            "\n"
+            "@implementer(IIdentifier)\n"
+            "class SiblingIdentifier:\n"
+            "    def __init__(self, marker=None):\n"
+            "        self.marker = marker\n"
+            "\n"
+            "    def identify(self, environ):\n"
+            "        return None\n"
+            "\n"
+            "    def remember(self, environ, identity):\n"
+            "        return []\n"
+            "\n"
+            "    def forget(self, environ, identity):\n"
+            "        return []\n"
+            "\n"
+            "def make_plugin(marker=None):\n"
+            "    return SiblingIdentifier(marker)\n",
+            encoding="utf-8",
+        )
+        return workspace, sibling_root
+
+    def _assert_native_namespace(self, module):
+        self.assertIsNone(module.__spec__.origin)
+        self.assertIsNotNone(module.__spec__.submodule_search_locations)
+
+    def _clear_repoze_modules(self):
+        for module_name in list(sys.modules):
+            if module_name == "repoze" or module_name.startswith("repoze."):
+                del sys.modules[module_name]
+
+    def test_config_resolves_plugin_from_sibling_namespace_package(self):
+        workspace, sibling_root = self._create_sibling()
+
+        original_sys_path = list(sys.path)
+        original_modules = {
+            k: v
+            for k, v in sys.modules.items()
+            if k == "repoze" or k.startswith("repoze.")
+        }
+        self._clear_repoze_modules()
+
+        try:
+            sys.path.insert(0, str(sibling_root))
+            import repoze
+            import repoze.who
+            import repoze.who.plugins
+            from repoze.who.config import WhoConfig
+
+            self._assert_native_namespace(repoze)
+            self._assert_native_namespace(repoze.who)
+            self._assert_native_namespace(repoze.who.plugins)
+
+            config = WhoConfig("/")
+            config.parse(
+                "[plugin:sibling]\n"
+                "use = repoze.who.plugins.sibling_identifier:make_plugin\n"
+                "marker = loaded from sibling namespace\n"
+                "\n"
+                "[identifiers]\n"
+                "plugins = sibling\n"
+            )
+
+            plugin = config.plugins["sibling"]
+            self.assertEqual(plugin.marker, "loaded from sibling namespace")
+            self.assertEqual(
+                plugin.__class__.__module__,
+                "repoze.who.plugins.sibling_identifier",
+            )
+            self.assertEqual(config.identifiers, [("sibling", plugin)])
+        finally:
+            sys.path[:] = original_sys_path
+            self._clear_repoze_modules()
+            sys.modules.update(original_modules)
+            shutil.rmtree(str(workspace))
diff --git a/repoze/who/tests/test_utils.py b/repoze/who/tests/test_utils.py
new file mode 100644
index 0000000..9d222d8
--- /dev/null
+++ b/repoze/who/tests/test_utils.py
@@ -0,0 +1,38 @@
+import unittest
+
+
+class ResolveDottedTests(unittest.TestCase):
+
+    def _callFUT(self, dotted_or_ep):
+        from repoze.who.utils import resolveDotted
+        return resolveDotted(dotted_or_ep)
+
+    def test_resolve_module_colon_object(self):
+        resolved = self._callFUT("repoze.who.tests.test_utils:DummyCallable")
+        self.assertEqual(resolved.__name__, "DummyCallable")
+        self.assertIn("test_utils", resolved.__module__)
+
+    def test_resolve_missing_colon_raises_value_error(self):
+        self.assertRaises(
+            ValueError,
+            self._callFUT,
+            "repoze.who.tests.test_utils.DummyCallable",
+        )
+
+    def test_resolve_empty_object_raises_value_error(self):
+        self.assertRaises(
+            ValueError,
+            self._callFUT,
+            "repoze.who.tests.test_utils:",
+        )
+
+    def test_resolve_extras_suffix_raises_value_error(self):
+        self.assertRaises(
+            ValueError,
+            self._callFUT,
+            "repoze.who.tests.test_utils:DummyCallable [extra]",
+        )
+
+
+class DummyCallable:
+    pass
diff --git a/repoze/who/utils.py b/repoze/who/utils.py
index 857dd2d..0afe2e0 100644
--- a/repoze/who/utils.py
+++ b/repoze/who/utils.py
@@ -1,5 +1,13 @@
+from importlib.metadata import EntryPoint
+
+
 def resolveDotted(dotted_or_ep):
-    """ Resolve a dotted name or setuptools entry point to a callable.
+    """Resolve a standard ``module:object`` reference to a callable.
     """
-    from pkg_resources import EntryPoint
-    return EntryPoint.parse('x=%s' % dotted_or_ep).resolve()
+    name = dotted_or_ep.strip()
+    if ":" not in name or "[" in name or "]" in name:
+        raise ValueError(f"Invalid dotted name: {name}")
+    module_name, object_name = name.split(":", 1)
+    if not module_name.strip() or not object_name.strip():
+        raise ValueError(f"Invalid dotted name: {name}")
+    return EntryPoint(name="x", value=name, group="x").load()
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index c4bd7a6..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-[easy_install]
-zip_ok = false
-
-[aliases]
-dev = develop
diff --git a/setup.py b/setup.py
index b2b3224..26e08e4 100644
--- a/setup.py
+++ b/setup.py
@@ -1,79 +1,5 @@
-##############################################################################
-#
-# Copyright (c) 2007-2009 Agendaless Consulting and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the BSD-like license at
-# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany
-# this distribution.  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL
-# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND
-# FITNESS FOR A PARTICULAR PURPOSE
-#
-##############################################################################
+from setuptools import setup
 
-import os
 
-from setuptools import setup, find_packages
-
-here = os.path.abspath(os.path.dirname(__file__))
-def _read_file(filename):
-    try:
-        with open(os.path.join(here, filename)) as f:
-            return f.read()
-    except IOError:  # Travis???
-        return ''
-
-README = _read_file('README.rst')
-CHANGES = _read_file('CHANGES.rst')
-
-setup(name='repoze.who',
-      version='3.1.0',
-      description=('repoze.who is an identification and authentication '
-                   'framework for WSGI.'),
-      long_description='\n\n'.join([README, CHANGES]),
-      long_description_content_type="text/x-rst",
-      classifiers=[
-        "Development Status :: 5 - Production/Stable",
-        "Intended Audience :: Developers",
-        "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.9",
-        "Programming Language :: Python :: 3.10",
-        "Programming Language :: Python :: 3.11",
-        "Programming Language :: Python :: 3.12",
-        "Programming Language :: Python :: 3.13",
-        "Programming Language :: Python :: Implementation :: CPython",
-        "Programming Language :: Python :: Implementation :: PyPy",
-        "Topic :: Internet :: WWW/HTTP",
-        "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
-        "Topic :: Internet :: WWW/HTTP :: WSGI",
-        "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
-        ],
-      python_requires=">=3.9",
-      keywords='web application server wsgi zope',
-      author="Agendaless Consulting",
-      author_email="[email protected]",
-      url="http://www.repoze.org";,
-      license="BSD-derived (http://www.repoze.org/LICENSE.txt)",
-      packages=find_packages(),
-      include_package_data=True,
-      namespace_packages=['repoze', 'repoze.who', 'repoze.who.plugins'],
-      zip_safe=False,
-      install_requires=[
-        'WebOb',
-        'zope.interface',
-        'setuptools',
-        'legacy-cgi; python_version > "3.12"',  # WebOb uses the cgi module
-      ],
-      test_suite="repoze.who",
-      entry_points = """\
-      [paste.filter_app_factory]
-      test = repoze.who.middleware:make_test_middleware
-      config = repoze.who.config:make_middleware_with_config
-      predicate = repoze.who.restrict:make_predicate_restriction
-      authenticated = repoze.who.restrict:make_authenticated_restriction
-      """,
-      extras_require = {
-        'docs': ['Sphinx', 'repoze.sphinx.autointerface'],
-      },
-)
+if __name__ == "__main__":
+    setup()
-- 
2.53.0


--- End Message ---
--- Begin Message ---
Source: python-repoze.who
Source-Version: 3.1.0-2
Done: Thomas Goirand <[email protected]>

We believe that the bug you reported is fixed in the latest version of
python-repoze.who, which is due to be installed in the Debian FTP archive.

A summary of the changes between this version and the previous one is
attached.

Thank you for reporting the bug, which will now be closed.  If you
have further comments please address them to [email protected],
and the maintainer will reopen the bug report if appropriate.

Debian distribution maintenance software
pp.
Thomas Goirand <[email protected]> (supplier of updated python-repoze.who package)

(This message was generated automatically at their request; if you
believe that there is a problem with it please contact the archive
administrators by mailing [email protected])


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Format: 1.8
Date: Wed, 02 Sep 2026 15:14:46 +0200
Source: python-repoze.who
Architecture: source
Version: 3.1.0-2
Distribution: unstable
Urgency: medium
Maintainer: Debian OpenStack <[email protected]>
Changed-By: Thomas Goirand <[email protected]>
Closes: 1146357
Changes:
 python-repoze.who (3.1.0-2) unstable; urgency=medium
 .
   * Add modernize.patch (Closes: #1146357).
Checksums-Sha1:
 06cd5b9ef548b171f9a2d286d2d8461aa519438a 2261 python-repoze.who_3.1.0-2.dsc
 b4bff140706e90a6bf97c7392880a9aa939ebc30 9444 
python-repoze.who_3.1.0-2.debian.tar.xz
 03007ddfac10af537c8e7ca15650ac3897a6c834 7988 
python-repoze.who_3.1.0-2_amd64.buildinfo
Checksums-Sha256:
 bdb9baf60aa3be15b45f0e925592c6796607b7fa890bd9af68fe1016c37fb810 2261 
python-repoze.who_3.1.0-2.dsc
 6230c65ef9868fd933059f76f801df3515bc61f00ec6d6d250de5dfbc58eab1d 9444 
python-repoze.who_3.1.0-2.debian.tar.xz
 5672282f9cff645287a4e4f37691aad1e4e94e095ccce792eadec4c88297f7f3 7988 
python-repoze.who_3.1.0-2_amd64.buildinfo
Files:
 9771ab9e66a038ab09300c93175e32c7 2261 python optional 
python-repoze.who_3.1.0-2.dsc
 438a1723995294cbc0a263d400f9e5b8 9444 python optional 
python-repoze.who_3.1.0-2.debian.tar.xz
 dd2d48ab488da79f974bc4d522ba4b60 7988 python optional 
python-repoze.who_3.1.0-2_amd64.buildinfo

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEoLGp81CJVhMOekJc1BatFaxrQ/4FAmqYIbwACgkQ1BatFaxr
Q/6AFA//W5+Eomj9I5kG7hTbcxQS55yc8cq0hkb1UdWJwQMS5CkOeMF+GCE+s2Zr
PWflVZS5LhYkHMT7RwQit7LyytqlwoIIdL09aDDxUCfL7q/tXAsrspKkguzqmH5A
EXJ6pGJFT8KuVArvy6N4/tTEojf987x6cIgDJAtL3MJuqx3UUHzCS5rDU32HccT5
Pior2p9zenyflxW/9eWxu7Ky8azm7khtPEhXMq3k8AEmX//C9op17fX2bH7fHu4T
1cRtjQmaCeM69RJpzEjxjVZ1vmPWp1b/ipm0T2Q9DOWAmYAppi/EW3vd8QVMtnGE
YKy9WJsVGpEH1P9gWQtzR3NbXbOkKOOnby9AJ1JP8zrBaPJe/+KNIVkBH4WbhmJ+
kNjCt823DtwaocRiAWrT2bVqB10P4dW1Lu99VrNhRjKKnH6gWECue2o6L/Qd2RN+
84TwBsrAivhi/snmbd+5BjzQbQHx9BG663WfHdtSePbnWlgOhcpt+BNNTjI6nQe7
WXE91INhI3ecXsI4hAVp505OnpX2NyJp0zMgn3R6NYJ0Xhk0Kc0JvQsbE1hy4+5r
cdiT789lCPjFtkbGOdkuwcIMSz8rrXJdeoT4hDJhVED2PjzAvYCIoKJK/ygQM5gK
c43PfU1Usv9pxfOtEr/P1jVtQ7ZMZe2/hUMWsd/WREGjsO6XYwo=
=kEKU
-----END PGP SIGNATURE-----

Attachment: pgpEZXCss7zXv.pgp
Description: PGP signature


--- End Message ---

Reply via email to