Skip to content

Commit

Permalink
Use typing.Self (when available) as sentinel and cover with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ericatkin committed Jun 24, 2024
1 parent f4c6d10 commit 9f29bc3
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 11 deletions.
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Features

- Coverage reports in tests based on Python 3.11 instead of Python 3.8.

- Added `LIFT` sentinel value that may be used for context and name arguments
- Added `Self` sentinel value that may be used for context and name arguments
to view_config on class methods in conjunction with venusian lift.

Bug Fixes
Expand Down
18 changes: 9 additions & 9 deletions src/pyramid/config/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
as_sorted_tuple,
is_nonstr_iter,
)
from pyramid.view import LIFT, AppendSlashNotFoundViewFactory
from pyramid.view import Self, AppendSlashNotFoundViewFactory
import pyramid.viewderivers
from pyramid.viewderivers import (
INGRESS,
Expand Down Expand Up @@ -573,7 +573,7 @@ def wrapper(context, request):
The :term:`view name`. Read :ref:`traversal_chapter` to
understand the concept of a view name. When :term:`view` is a class,
the sentinel value view.LIFT will cause the attr value to be copied
the sentinel value view.Self will cause the attr value to be copied
to name (useful with view_defaults to reduce boilerplate).
context
Expand All @@ -589,9 +589,9 @@ def wrapper(context, request):
to ``add_view`` as ``for_`` (an older, still-supported
spelling). If the view should *only* match when handling
exceptions, then set the ``exception_only`` to ``True``.
When :term:`view` is a class, the sentinel value view.LIFT here
When :term:`view` is a class, the sentinel value view.Self
will cause the :term:`context` value to be set at scan time
(useful in conjunction with venusian :term:`lift`).
(useful in conjunction with venusian lift).
route_name
Expand Down Expand Up @@ -821,12 +821,12 @@ def wrapper(context, request):
mapper = self.maybe_dotted(mapper)

if inspect.isclass(view):
if context is LIFT:
if context is Self:
context = view
if name is LIFT:
name = attr
elif LIFT in (context, name):
raise ValueError('LIFT is only allowed when view is a class')
if name is Self:
name = attr or ""
elif Self in (context, name):
raise ValueError('Self is only allowed when view is a class')

if is_nonstr_iter(decorator):
decorator = combine_decorators(*map(self.maybe_dotted, decorator))
Expand Down
6 changes: 5 additions & 1 deletion src/pyramid/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
from pyramid.util import Sentinel, hide_attrs, reraise as reraise_

_marker = object()
LIFT = Sentinel('LIFT')

if sys.version_info.major < 3 or sys.version_info.minor < 11:
Self = Sentinel('Self') # pragma: no cover
else:
from typing import Self


def render_view_to_response(context, request, name='', secure=True):
Expand Down
27 changes: 27 additions & 0 deletions tests/test_config/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from pyramid.interfaces import IMultiView, IRequest, IResponse
from pyramid.util import text_
from pyramid.view import Self

from . import IDummy, dummy_view

Expand Down Expand Up @@ -435,6 +436,32 @@ class Foo:
wrapper = self._getViewCallable(config, foo)
self.assertEqual(wrapper, view)

def test_add_view_raises_on_self_with_non_class_view(self):
def view(exc, request): # pragma: no cover
pass

config = self._makeOne(autocommit=True)
self.assertRaises(
ValueError,
lambda: config.add_view(view=view, context=Self, name=Self),
)

def test_add_view_replaces_self(self):
from zope.interface import implementedBy

class Foo: # pragma: no cover
def __init__(self, request):
pass

def view(self):
pass

config = self._makeOne(autocommit=True)
config.add_view(Foo, context=Self, name=Self, attr='view')
interface = implementedBy(Foo)
wrapper = self._getViewCallable(config, interface, name='view')
self.assertEqual(wrapper.__original_view__, Foo)

def test_add_view_context_as_iface(self):
from pyramid.renderers import null_renderer

Expand Down

0 comments on commit 9f29bc3

Please sign in to comment.