Skip to content

Commit

Permalink
Allow a function for view_config(context) (useful with venusian lift)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericatkin committed Sep 12, 2024
1 parent 5b69614 commit a6d06fa
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 3 deletions.
5 changes: 3 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ Features

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

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

Bug Fixes
---------
Expand Down
9 changes: 8 additions & 1 deletion src/pyramid/config/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,9 @@ def wrapper(context, request):
exceptions, then set the ``exception_only`` to ``True``.
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 lift).
(useful in conjunction with venusian lift). It can also be
a function taking the view as it's only argument which return
value will be set as context at scan time.
route_name
Expand Down Expand Up @@ -823,10 +825,15 @@ def wrapper(context, request):
if inspect.isclass(view):
if context is Self:
context = view
elif inspect.isfunction(context):
context = context(view)
if name is Self:
name = attr or ""
elif Self in (context, name):
raise ValueError('Self is only allowed when view is a class')
elif inspect.isfunction(context):
raise ValueError(
'context as function is only allowed when view is a class')

if is_nonstr_iter(decorator):
decorator = combine_decorators(*map(self.maybe_dotted, decorator))
Expand Down
30 changes: 30 additions & 0 deletions tests/test_config/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,16 @@ def view(exc, request): # pragma: no cover
lambda: config.add_view(view=view, context=Self, name=Self),
)

def test_add_view_raises_on_function_context_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=lambda x: x, name=Self),
)

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

Expand All @@ -462,6 +472,26 @@ def view(self):
wrapper = self._getViewCallable(config, interface, name='view')
self.assertEqual(wrapper.__original_view__, Foo)

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

class Foo2:
pass

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

def view(self):
pass

config = self._makeOne(autocommit=True)
config.add_view(Foo, context=lambda x: x.wrapped, name=Self, attr='view')
interface = implementedBy(Foo2)
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 a6d06fa

Please sign in to comment.