Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add InstancePropertyHelper and apply_request_extensions #1581

Merged
merged 3 commits into from
Feb 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Next release
Features
--------

- Add ``pyramid.request.apply_request_extensions`` function which can be
used in testing to apply any request extensions configured via
``config.add_request_method``. Previously it was only possible to test
the extensions by going through Pyramid's router.
See https://github.com/Pylons/pyramid/pull/1581

- pcreate when run without a scaffold argument will now print information on
the missing flag, as well as a list of available scaffolds.
See https://github.com/Pylons/pyramid/pull/1566 and
Expand Down
1 change: 1 addition & 0 deletions docs/api/request.rst
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,4 @@
that used as ``request.GET``, ``request.POST``, and ``request.params``),
see :class:`pyramid.interfaces.IMultiDict`.

.. autofunction:: apply_request_extensions(request)
4 changes: 2 additions & 2 deletions pyramid/config/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

from pyramid.util import (
action_method,
InstancePropertyMixin,
get_callable_name,
InstancePropertyHelper,
)


Expand Down Expand Up @@ -174,7 +174,7 @@ def add_request_method(self,

property = property or reify
if property:
name, callable = InstancePropertyMixin._make_property(
name, callable = InstancePropertyHelper.make_property(
callable, name=name, reify=reify)
elif name is None:
name = callable.__name__
Expand Down
3 changes: 1 addition & 2 deletions pyramid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,8 +591,7 @@ def __call__(request):
class IRequestFactory(Interface):
""" A utility which generates a request """
def __call__(environ):
""" Return an object implementing IRequest, e.g. an instance
of ``pyramid.request.Request``"""
""" Return an instance of ``pyramid.request.Request``"""

def blank(path):
""" Return an empty request object (see
Expand Down
26 changes: 25 additions & 1 deletion pyramid/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from pyramid.interfaces import (
IRequest,
IRequestExtensions,
IResponse,
ISessionFactory,
)
Expand All @@ -16,6 +17,7 @@
text_,
bytes_,
native_,
iteritems_,
)

from pyramid.decorator import reify
Expand All @@ -26,7 +28,10 @@
AuthorizationAPIMixin,
)
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
from pyramid.util import (
InstancePropertyHelper,
InstancePropertyMixin,
)

class TemplateContext(object):
pass
Expand Down Expand Up @@ -307,3 +312,22 @@ def call_app_with_subpath_as_path_info(request, app):
new_request.environ['PATH_INFO'] = new_path_info

return new_request.get_response(app)

def apply_request_extensions(request, extensions=None):
"""Apply request extensions (methods and properties) to an instance of
:class:`pyramid.interfaces.IRequest`. This method is dependent on the
``request`` containing a properly initialized registry.
After invoking this method, the ``request`` should have the methods
and properties that were defined using
:meth:`pyramid.config.Configurator.add_request_method`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we document how you would use extensions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explicitly left extensions off of the public api documentation. It's there just for pyramid's hot route in the router. The IRequestExtensions interface is private and shouldn't be used/passed-in directly by anyone. We do this in a few other places in the code for testing hooks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could name it _extensions if it would make you feel better.

"""
if extensions is None:
extensions = request.registry.queryUtility(IRequestExtensions)
if extensions is not None:
for name, fn in iteritems_(extensions.methods):
method = fn.__get__(request, request.__class__)
setattr(request, name, method)

InstancePropertyHelper.apply_properties(
request, extensions.descriptors)
3 changes: 2 additions & 1 deletion pyramid/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from pyramid.exceptions import PredicateMismatch
from pyramid.httpexceptions import HTTPNotFound
from pyramid.request import Request
from pyramid.request import apply_request_extensions
from pyramid.threadlocal import manager

from pyramid.traversal import (
Expand Down Expand Up @@ -213,7 +214,7 @@ def invoke_subrequest(self, request, use_tweens=False):
try:
extensions = self.request_extensions
if extensions is not None:
request._set_extensions(extensions)
apply_request_extensions(request, extensions=extensions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.request_extensions is just the interface that apply_request_extensions queries anyways, do we need to pass them in?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just an optimization to have the extensions queried from the registry only once at app-setup-time. This code is in pyramid's hot route, so I wanted to keep it as close to the original version as possible.

response = handle_request(request)

if request.response_callbacks:
Expand Down
8 changes: 3 additions & 5 deletions pyramid/scripting.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from pyramid.config import global_registries
from pyramid.exceptions import ConfigurationError
from pyramid.request import Request

from pyramid.interfaces import (
IRequestExtensions,
IRequestFactory,
IRootFactory,
)
from pyramid.request import Request
from pyramid.request import apply_request_extensions

from pyramid.threadlocal import manager as threadlocal_manager
from pyramid.traversal import DefaultRootFactory
Expand Down Expand Up @@ -77,9 +77,7 @@ def prepare(request=None, registry=None):
request.registry = registry
threadlocals = {'registry':registry, 'request':request}
threadlocal_manager.push(threadlocals)
extensions = registry.queryUtility(IRequestExtensions)
if extensions is not None:
request._set_extensions(extensions)
apply_request_extensions(request)
def closer():
threadlocal_manager.pop()
root_factory = registry.queryUtility(IRootFactory,
Expand Down
45 changes: 44 additions & 1 deletion pyramid/tests/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,50 @@ def test_subpath_path_info_and_script_name_have_utf8(self):
self.assertEqual(request.environ['SCRIPT_NAME'], '/' + encoded)
self.assertEqual(request.environ['PATH_INFO'], '/' + encoded)

class DummyRequest:
class Test_apply_request_extensions(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()

def tearDown(self):
testing.tearDown()

def _callFUT(self, request, extensions=None):
from pyramid.request import apply_request_extensions
return apply_request_extensions(request, extensions=extensions)

def test_it_with_registry(self):
from pyramid.interfaces import IRequestExtensions
extensions = Dummy()
extensions.methods = {'foo': lambda x, y: y}
extensions.descriptors = {'bar': property(lambda x: 'bar')}
self.config.registry.registerUtility(extensions, IRequestExtensions)
request = DummyRequest()
request.registry = self.config.registry
self._callFUT(request)
self.assertEqual(request.bar, 'bar')
self.assertEqual(request.foo('abc'), 'abc')

def test_it_override_extensions(self):
from pyramid.interfaces import IRequestExtensions
ignore = Dummy()
ignore.methods = {'x': lambda x, y, z: 'asdf'}
ignore.descriptors = {'bar': property(lambda x: 'asdf')}
self.config.registry.registerUtility(ignore, IRequestExtensions)
request = DummyRequest()
request.registry = self.config.registry

extensions = Dummy()
extensions.methods = {'foo': lambda x, y: y}
extensions.descriptors = {'bar': property(lambda x: 'bar')}
self._callFUT(request, extensions=extensions)
self.assertRaises(AttributeError, lambda: request.x)
self.assertEqual(request.bar, 'bar')
self.assertEqual(request.foo('abc'), 'abc')

class Dummy(object):
pass

class DummyRequest(object):
def __init__(self, environ=None):
if environ is None:
environ = {}
Expand Down
8 changes: 5 additions & 3 deletions pyramid/tests/test_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,18 +317,20 @@ def test_call_with_request_extensions(self):
from pyramid.interfaces import IRequestExtensions
from pyramid.interfaces import IRequest
from pyramid.request import Request
from pyramid.util import InstancePropertyHelper
context = DummyContext()
self._registerTraverserFactory(context)
class Extensions(object):
def __init__(self):
self.methods = {}
self.descriptors = {}
extensions = Extensions()
L = []
ext_method = lambda r: 'bar'
name, fn = InstancePropertyHelper.make_property(ext_method, name='foo')
extensions.descriptors[name] = fn
request = Request.blank('/')
request.request_iface = IRequest
request.registry = self.registry
request._set_extensions = lambda *x: L.extend(x)
def request_factory(environ):
return request
self.registry.registerUtility(extensions, IRequestExtensions)
Expand All @@ -342,7 +344,7 @@ def request_factory(environ):
router.request_factory = request_factory
start_response = DummyStartResponse()
router(environ, start_response)
self.assertEqual(L, [extensions])
self.assertEqual(view.request.foo, 'bar')

def test_call_view_registered_nonspecific_default_path(self):
from pyramid.interfaces import IViewClassifier
Expand Down
16 changes: 11 additions & 5 deletions pyramid/tests/test_scripting.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,15 @@ def test_it_with_request_context_already_set(self):
self.assertEqual(request.context, context)

def test_it_with_extensions(self):
exts = Dummy()
from pyramid.util import InstancePropertyHelper
exts = DummyExtensions()
ext_method = lambda r: 'bar'
name, fn = InstancePropertyHelper.make_property(ext_method, 'foo')
exts.descriptors[name] = fn
request = DummyRequest({})
registry = request.registry = self._makeRegistry([exts, DummyFactory])
info = self._callFUT(request=request, registry=registry)
self.assertEqual(request.extensions, exts)
self.assertEqual(request.foo, 'bar')
root, closer = info['root'], info['closer']
closer()

Expand Down Expand Up @@ -199,11 +203,13 @@ def push(self, item):
def pop(self):
self.popped.append(True)

class DummyRequest:
class DummyRequest(object):
matchdict = None
matched_route = None
def __init__(self, environ):
self.environ = environ

def _set_extensions(self, exts):
self.extensions = exts
class DummyExtensions:
def __init__(self):
self.descriptors = {}
self.methods = {}
Loading