diff options
| -rw-r--r-- | .github/workflows/ci-tests.yml | 13 | ||||
| -rw-r--r-- | .readthedocs.yaml | 2 | ||||
| -rw-r--r-- | CHANGES.rst | 10 | ||||
| -rw-r--r-- | HACKING.txt | 2 | ||||
| -rw-r--r-- | docs/narr/install.rst | 2 | ||||
| -rw-r--r-- | docs/narr/upgrading.rst | 1 | ||||
| -rw-r--r-- | pyproject.toml | 2 | ||||
| -rw-r--r-- | setup.py | 1 | ||||
| -rw-r--r-- | src/pyramid/config/adapters.py | 13 | ||||
| -rw-r--r-- | src/pyramid/config/routes.py | 2 | ||||
| -rw-r--r-- | src/pyramid/config/tweens.py | 1 | ||||
| -rw-r--r-- | src/pyramid/config/views.py | 6 | ||||
| -rw-r--r-- | src/pyramid/encode.py | 2 | ||||
| -rw-r--r-- | src/pyramid/httpexceptions.py | 1 | ||||
| -rw-r--r-- | src/pyramid/interfaces.py | 2 | ||||
| -rw-r--r-- | src/pyramid/registry.py | 1 | ||||
| -rw-r--r-- | src/pyramid/resource.py | 1 | ||||
| -rw-r--r-- | src/pyramid/router.py | 1 | ||||
| -rw-r--r-- | src/pyramid/scripts/pserve.py | 1 | ||||
| -rw-r--r-- | src/pyramid/scripts/pshell.py | 6 | ||||
| -rw-r--r-- | src/pyramid/traversal.py | 1 | ||||
| -rw-r--r-- | src/pyramid/util.py | 6 | ||||
| -rw-r--r-- | tests/pkgs/eventonly/__init__.py | 19 | ||||
| -rw-r--r-- | tests/test_integration.py | 8 | ||||
| -rw-r--r-- | tests/test_traversal.py | 1 | ||||
| -rw-r--r-- | tox.ini | 18 |
26 files changed, 76 insertions, 47 deletions
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 4f2aaa9bc..f24159c40 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -20,6 +20,7 @@ jobs: - "3.9" - "3.10" - "3.11" + - "3.12" - "pypy-3.8" os: - "ubuntu-20.04" @@ -49,7 +50,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.py }} architecture: ${{ matrix.architecture }} @@ -62,19 +63,19 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 architecture: x64 - run: pip install tox - - run: tox -e py311-cover,coverage + - run: tox -e py312-cover,coverage docs: runs-on: ubuntu-20.04 name: Build the documentation steps: - uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 architecture: x64 @@ -86,7 +87,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 architecture: x64 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index d45905a5d..218fdb215 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,7 +3,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: '3.11' + python: '3.12' sphinx: configuration: docs/conf.py formats: diff --git a/CHANGES.rst b/CHANGES.rst index a717c5b59..b4a62f94e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,12 +4,12 @@ unreleased Features -------- -- Pyramid adds support for Python 3.11. +- Add support for Python 3.11 and 3.12. - Added HTTP 418 error code via `pyramid.httpexceptions.HTTPImATeapot`. See https://github.com/Pylons/pyramid/pull/3667 -- Coverage reports in tests based on Python 3.11 instead of Python 3.8. +- Coverage reports in tests based on Python 3.12 instead of Python 3.8. - All scripts now pass a new option ``__script__`` when loading the WSGI app. For example, ``pserve`` sets ``__script__ == 'pserve'``. This works for @@ -39,10 +39,12 @@ Bug Fixes Backward Incompatibilities -------------------------- +- Drop support for Python 3.6. + - Requests to a static_view are no longer allowed to contain a null-byte in any part of the path segment. -- Pyramid is no longer tested on, nor supports Python 3.6 -- Pyramid drops support for l*gettext() methods in the i18n module. + +- Drop support for l*gettext() methods in the i18n module. These have been deprecated in Python's gettext module since 3.8, and removed in Python 3.11. diff --git a/HACKING.txt b/HACKING.txt index bb7045ed6..f7c569a01 100644 --- a/HACKING.txt +++ b/HACKING.txt @@ -51,7 +51,7 @@ In order to add a feature to Pyramid: (in `docs/`). - The feature must work fully on the following CPython versions: 3.7, 3.8, 3.9, - 3.10, and 3.11 on both UNIX and Windows. + 3.10, 3.11, and 3.12 on both UNIX and Windows. - The feature must work on the latest version of PyPy3. diff --git a/docs/narr/install.rst b/docs/narr/install.rst index d07692d86..c40cd7403 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -22,7 +22,7 @@ the following sections. .. sidebar:: Python Versions As of this writing, :app:`Pyramid` is tested against Python 3.7, - 3.8, 3.9, 3.10, 3.11 and PyPy (matches CPython version 3.8). + 3.8, 3.9, 3.10, 3.11, 3.12 and PyPy (matches CPython version 3.8). :app:`Pyramid` is known to run on all popular Unix-like systems such as Linux, macOS, and FreeBSD, as well as on Windows platforms. It is also known to diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst index 5fc3dc9fa..64d51c024 100644 --- a/docs/narr/upgrading.rst +++ b/docs/narr/upgrading.rst @@ -91,6 +91,7 @@ Python is when security updates are no longer released. - `Python 3.9 Lifespan <https://devguide.python.org/versions/#versions>`_ 2025-10. - `Python 3.10 Lifespan <https://devguide.python.org/versions/#versions>`_ 2026-10. - `Python 3.11 Lifespan <https://devguide.python.org/versions/#versions>`_ 2027-10. +- `Python 3.12 Lifespan <https://devguide.python.org/versions/#versions>`_ 2028-10. To determine the Python support for a specific release of Pyramid, view its ``tox.ini`` file at the root of the repository's version. diff --git a/pyproject.toml b/pyproject.toml index 9069096f7..e5beb1e70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 79 skip-string-normalization = true -target-version = ['py37', 'py38', 'py39', 'py310'] +target-version = ['py37', 'py38', 'py39', 'py310', 'py311', 'py312'] exclude = ''' /( \.git @@ -79,6 +79,7 @@ setup( "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Framework :: Pyramid", diff --git a/src/pyramid/config/adapters.py b/src/pyramid/config/adapters.py index a64c251ca..8a043cf56 100644 --- a/src/pyramid/config/adapters.py +++ b/src/pyramid/config/adapters.py @@ -76,8 +76,6 @@ class AdaptersConfiguratorMixin: return subscriber def _derive_predicate(self, predicate): - derived_predicate = predicate - if eventonly(predicate): def derived_predicate(*arg): @@ -85,12 +83,12 @@ class AdaptersConfiguratorMixin: # seems pointless to try to fix __doc__, __module__, etc as # predicate will invariably be an instance + else: + derived_predicate = predicate return derived_predicate def _derive_subscriber(self, subscriber, predicates): - derived_subscriber = subscriber - if eventonly(subscriber): def derived_subscriber(*arg): @@ -98,6 +96,8 @@ class AdaptersConfiguratorMixin: if hasattr(subscriber, '__name__'): update_wrapper(derived_subscriber, subscriber) + else: + derived_subscriber = subscriber if not predicates: return derived_subscriber @@ -326,4 +326,7 @@ class AdaptersConfiguratorMixin: def eventonly(callee): - return takes_one_arg(callee, argname='event') + # we do not count a function as eventonly if it accepts *args + # which will open up the possibility for the function to receive + # all of the args + return takes_one_arg(callee, argname='event', allow_varargs=False) diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index a7ff66a47..409f36849 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -542,7 +542,7 @@ class RoutesConfiguratorMixin: def add_default_route_predicates(self): p = pyramid.predicates - for (name, factory) in ( + for name, factory in ( ('xhr', p.XHRPredicate), ('request_method', p.RequestMethodPredicate), ('path_info', p.PathInfoPredicate), diff --git a/src/pyramid/config/tweens.py b/src/pyramid/config/tweens.py index 0eeac333e..1cf6d9262 100644 --- a/src/pyramid/config/tweens.py +++ b/src/pyramid/config/tweens.py @@ -102,7 +102,6 @@ class TweensConfiguratorMixin: @action_method def _add_tween(self, tween_factory, under=None, over=None, explicit=False): - if not isinstance(tween_factory, str): raise ConfigurationError( 'The "tween_factory" argument to add_tween must be a ' diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 4f5806df3..fababf542 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -886,7 +886,7 @@ class ViewsConfiguratorMixin: pvals = {} dvals = {} - for (k, v) in ovals.items(): + for k, v in ovals.items(): if k in valid_predicates: pvals[k] = v else: @@ -1206,7 +1206,7 @@ class ViewsConfiguratorMixin: def add_default_view_predicates(self): p = pyramid.predicates - for (name, factory) in ( + for name, factory in ( ('xhr', p.XHRPredicate), ('request_method', p.RequestMethodPredicate), ('path_info', p.PathInfoPredicate), @@ -2163,7 +2163,7 @@ class StaticURLInfo: self.cache_busters = [] def generate(self, path, request, **kw): - for (url, spec, route_name) in self.registrations: + for url, spec, route_name in self.registrations: if path.startswith(spec): subpath = path[len(spec) :] if WIN: # pragma: no cover diff --git a/src/pyramid/encode.py b/src/pyramid/encode.py index 153940534..f97af4fa4 100644 --- a/src/pyramid/encode.py +++ b/src/pyramid/encode.py @@ -64,7 +64,7 @@ def urlencode(query, doseq=True, quote_via=quote_plus): result = '' prefix = '' - for (k, v) in query: + for k, v in query: k = quote_via(k) if is_nonstr_iter(v): diff --git a/src/pyramid/httpexceptions.py b/src/pyramid/httpexceptions.py index 9d61acd8f..46589b1ec 100644 --- a/src/pyramid/httpexceptions.py +++ b/src/pyramid/httpexceptions.py @@ -129,6 +129,7 @@ redirections that require a ``Location`` field. Reflecting this, these subclasses have one additional keyword argument: ``location``, which indicates the location to which to redirect. """ + import json from string import Template from webob import html_escape as _html_escape diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 6221cc21e..4ee294189 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -921,7 +921,6 @@ ILogger = IDebugLogger # b/c class IRoutePregenerator(Interface): def __call__(request, elements, kw): - """A pregenerator is a function associated by a developer with a :term:`route`. The pregenerator for a route is called by :meth:`pyramid.request.Request.route_url` in order to adjust the set @@ -1384,7 +1383,6 @@ class IIntrospectable(Interface): """ # noqa: E501 def __hash__(): - """Introspectables must be hashable. The typical implementation of an introsepectable's __hash__ is:: diff --git a/src/pyramid/registry.py b/src/pyramid/registry.py index 971ae786a..0b983a6c5 100644 --- a/src/pyramid/registry.py +++ b/src/pyramid/registry.py @@ -208,7 +208,6 @@ class Introspector: @implementer(IIntrospectable) class Introspectable(dict): - order = 0 # mutated by introspector.add action_info = None # mutated by self.register diff --git a/src/pyramid/resource.py b/src/pyramid/resource.py index 8ddf4c447..a89395f18 100644 --- a/src/pyramid/resource.py +++ b/src/pyramid/resource.py @@ -1,4 +1,5 @@ """ Backwards compatibility shim module (forever). """ + from pyramid.asset import * # noqa b/w compat resolve_resource_spec = resolve_asset_spec # noqa diff --git a/src/pyramid/router.py b/src/pyramid/router.py index 61660c41b..0b7554e23 100644 --- a/src/pyramid/router.py +++ b/src/pyramid/router.py @@ -28,7 +28,6 @@ from pyramid.view import _call_view @implementer(IRouter) class Router: - debug_notfound = False debug_routematch = False diff --git a/src/pyramid/scripts/pserve.py b/src/pyramid/scripts/pserve.py index b4a4af977..7504d9a1f 100644 --- a/src/pyramid/scripts/pserve.py +++ b/src/pyramid/scripts/pserve.py @@ -31,7 +31,6 @@ def main(argv=sys.argv, quiet=False, original_ignore_files=None): class PServeCommand: - description = """\ This command serves a web application that uses a PasteDeploy configuration file for the server and application. diff --git a/src/pyramid/scripts/pshell.py b/src/pyramid/scripts/pshell.py index 9122ab32e..63600abaf 100644 --- a/src/pyramid/scripts/pshell.py +++ b/src/pyramid/scripts/pshell.py @@ -164,9 +164,9 @@ class PShellCommand: env_help['root'] = 'Root of the default resource tree.' env_help['registry'] = 'Active Pyramid registry.' env_help['request'] = 'Active request object.' - env_help[ - 'root_factory' - ] = 'Default root factory used to create `root`.' + env_help['root_factory'] = ( + 'Default root factory used to create `root`.' + ) # load the pshell section of the ini file env.update(self.loaded_objects) diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 00b64bae2..986069d1d 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -597,7 +597,6 @@ class ResourceTreeTraverser: matchdict = request.matchdict if matchdict is not None: - path = matchdict.get('traverse', '/') or '/' if is_nonstr_iter(path): # this is a *traverse stararg (not a {traverse}) diff --git a/src/pyramid/util.py b/src/pyramid/util.py index 8d384cc84..c71528a49 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -652,7 +652,7 @@ def make_contextmanager(fn): return wrapper -def takes_one_arg(callee, attr=None, argname=None): +def takes_one_arg(callee, attr=None, argname=None, allow_varargs=True): ismethod = False if attr is None: attr = '__call__' @@ -679,11 +679,13 @@ def takes_one_arg(callee, attr=None, argname=None): if not args: return False + if not allow_varargs and argspec.varargs: + return False + if len(args) == 1: return True if argname: - defaults = argspec[3] if defaults is None: defaults = () diff --git a/tests/pkgs/eventonly/__init__.py b/tests/pkgs/eventonly/__init__.py index e45a5691f..f7a9270dc 100644 --- a/tests/pkgs/eventonly/__init__.py +++ b/tests/pkgs/eventonly/__init__.py @@ -15,6 +15,19 @@ class Yup: return getattr(event.response, 'yup', False) +class YupWithAllArgs: + def __init__(self, val, config): + self.val = val + + def text(self): + return f'yup_with_extra_args = {self.val}' + + phash = text + + def __call__(self, event, *args): + return getattr(event.response, 'yup', False) + + class Foo: def __init__(self, response): self.response = response @@ -54,6 +67,11 @@ def foobaryup2(event, context): event.response.text += 'foobaryup2 ' +@subscriber([Foo, Bar], yup=True, yup_with_all_args=True) +def foobaryup3(event, context): + event.response.text += 'foobaryup3 ' + + @view_config(name='sendfoo') def sendfoo(request): response = request.response @@ -72,4 +90,5 @@ def sendfoobar(request): def includeme(config): config.add_subscriber_predicate('yup', Yup) + config.add_subscriber_predicate('yup_with_all_args', YupWithAllArgs) config.scan('tests.pkgs.eventonly') diff --git a/tests/test_integration.py b/tests/test_integration.py index 63a7088e9..7ca11e81e 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -213,7 +213,13 @@ class TestEventOnlySubscribers(IntegrationBase, unittest.TestCase): res = self.testapp.get('/sendfoobar', status=200) self.assertEqual( sorted(res.body.split()), - [b'foobar', b'foobar2', b'foobaryup', b'foobaryup2'], + [ + b'foobar', + b'foobar2', + b'foobaryup', + b'foobaryup2', + b'foobaryup3', + ], ) diff --git a/tests/test_traversal.py b/tests/test_traversal.py index 0d8096f15..36b131242 100644 --- a/tests/test_traversal.py +++ b/tests/test_traversal.py @@ -1271,7 +1271,6 @@ class DummyContext: class DummyRequest: - application_url = ( 'http://example.com:5432' # app_url never ends with slash ) @@ -1,8 +1,8 @@ [tox] envlist = lint, - py37,py38,py39,py310,py311,pypy3, - py311-cover,coverage, + py37,py38,py39,py310,py311,py312,pypy3, + py312-cover,coverage, docs isolated_build = true @@ -16,7 +16,7 @@ extras = setenv = COVERAGE_FILE=.coverage.{envname} -[testenv:py311-cover] +[testenv:py312-cover] commands = python --version pytest --cov {posargs:} @@ -32,9 +32,9 @@ commands = python -m build . twine check dist/* deps = - flake8~=5.0.4 - black~=22.12.0 - isort~=5.10 + flake8~=6.1.0 + black~=24.1.0 + isort~=5.12 build check-manifest readme_renderer @@ -64,7 +64,7 @@ deps = coverage setenv = COVERAGE_FILE=.coverage -depends = py311-cover +depends = py312-cover [testenv:format] skip_install = true @@ -72,8 +72,8 @@ commands = isort src/pyramid tests setup.py black src/pyramid tests setup.py deps = - black~=22.12.0 - isort~=5.10 + black~=24.1.0 + isort~=5.12 [testenv:build] skip_install = true |
