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

