--- 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 ---