summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-08-16 11:12:24 -0400
committerChris McDonough <chrism@plope.com>2012-08-16 11:12:24 -0400
commit14f69c57c4d83355e769db46692cfb36e87a4f70 (patch)
tree2e7e9de785050c7b5276029d1fbed12d0b15a097
parent8c7b0f14b2c55e653bb0d4a18e3de299b7abe208 (diff)
parent2c25342383eed7b10e349b2396eed08d332cfb80 (diff)
downloadpyramid-14f69c57c4d83355e769db46692cfb36e87a4f70.tar.gz
pyramid-14f69c57c4d83355e769db46692cfb36e87a4f70.tar.bz2
pyramid-14f69c57c4d83355e769db46692cfb36e87a4f70.zip
Merge branch 'feature.instance-properties' of git://github.com/mmerickel/pyramid into mmerickel-feature.instance-properties
-rw-r--r--CHANGES.txt24
-rw-r--r--docs/api/config.rst4
-rw-r--r--docs/narr/advconfig.rst2
-rw-r--r--pyramid/config/factories.py126
-rw-r--r--pyramid/interfaces.py10
-rw-r--r--pyramid/tests/test_config/test_factories.py96
6 files changed, 201 insertions, 61 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index f02925585..b2f37355b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -64,13 +64,17 @@ Features
HEAD is a variant of GET that omits the body, and WebOb has special support
to return an empty body when a HEAD is used.
-- ``config.set_request_property`` now causes less code to be executed at
- request construction time.
-
-- Don't add a ``?`` to URLs generated by request.resource_url if the
+- ``config.set_request_method`` has been introduced to support extending
+ request objects with arbitrary callables. This method expands on the
+ previous ``config.set_request_property`` by supporting methods as well as
+ properties. This method now causes less code to be executed at
+ request construction time than ``config.set_request_property`` in
+ version 1.3.
+
+- Don't add a ``?`` to URLs generated by ``request.resource_url`` if the
``query`` argument is provided but empty.
-- Don't add a ``?`` to URLs generated by request.route_url if the
+- Don't add a ``?`` to URLs generated by ``request.route_url`` if the
``_query`` argument is provided but empty.
- The static view machinery now raises (rather than returns) ``HTTPNotFound``
@@ -100,3 +104,13 @@ Features
config = Configurator()
config.add_permission('view')
+
+Deprecations
+------------
+
+- The documentation for
+ ``pyramid.config.Configurator.set_request_property`` has been removed.
+ The method remains usable but the more featureful
+ ``pyramid.config.Configurator.set_request_method`` should be used in its
+ place. It has all of the same capabilities but can also extend the
+ request object with methods.
diff --git a/docs/api/config.rst b/docs/api/config.rst
index 1b887988a..8dbe6de91 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -38,9 +38,9 @@
.. automethod:: set_default_permission
.. automethod:: add_permission
- :methodcategory:`Setting Request Properties`
+ :methodcategory:`Extending the Request Object`
- .. automethod:: set_request_property
+ .. automethod:: set_request_method
:methodcategory:`Using I18N`
diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst
index 2949dc808..0ad2fdc95 100644
--- a/docs/narr/advconfig.rst
+++ b/docs/narr/advconfig.rst
@@ -296,7 +296,7 @@ These are the methods of the configurator which provide conflict detection:
:meth:`~pyramid.config.Configurator.add_renderer`,
:meth:`~pyramid.config.Configurator.set_request_factory`,
:meth:`~pyramid.config.Configurator.set_session_factory`,
-:meth:`~pyramid.config.Configurator.set_request_property`,
+:meth:`~pyramid.config.Configurator.set_request_method`,
:meth:`~pyramid.config.Configurator.set_root_factory`,
:meth:`~pyramid.config.Configurator.set_view_mapper`,
:meth:`~pyramid.config.Configurator.set_authentication_policy`,
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index ccbf3bbe9..e3637cbd2 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -1,10 +1,13 @@
+from zope.interface import implementer
+
+from pyramid.compat import iteritems_
from pyramid.config.util import action_method
from pyramid.interfaces import (
IDefaultRootFactory,
INewRequest,
IRequestFactory,
- IRequestProperties,
+ IRequestExtensions,
IRootFactory,
ISessionFactory,
)
@@ -93,55 +96,116 @@ class FactoriesConfiguratorMixin(object):
self.action(IRequestFactory, register, introspectables=(intr,))
@action_method
- def set_request_property(self, callable, name=None, reify=False):
- """ Add a property to the request object.
+ def set_request_method(self,
+ callable=None,
+ name=None,
+ property=False,
+ reify=False):
+ """ Add a property or method to the request object.
+
+ When adding a method to the request, ``callable`` may be any
+ function that receives the request object as the first
+ parameter. If ``name`` is ``None`` then it will be computed
+ from the name of the ``callable``.
- ``callable`` can either be a callable that accepts the request
- as its single positional parameter, or it can be a property
- descriptor. It may also be a :term:`dotted Python name` which
- refers to either a callable or a property descriptor.
+ When adding a property to the request, ``callable`` can either
+ be a callable that accepts the request as its single positional
+ parameter, or it can be a property descriptor. If ``name`` is
+ ``None``, the name of the property will be computed from the
+ name of the ``callable``.
If the ``callable`` is a property descriptor a ``ValueError``
will be raised if ``name`` is ``None`` or ``reify`` is ``True``.
- If ``name`` is None, the name of the property will be computed
- from the name of the ``callable``.
-
See :meth:`pyramid.request.Request.set_property` for more
- information on its usage.
+ details on ``property`` vs ``reify``. When ``reify`` is
+ ``True``, the value of ``property`` is assumed to also be
+ ``True``.
+
+ In all cases, ``callable`` may also be a
+ :term:`dotted Python name` which refers to either a callable or
+ a property descriptor.
+
+ If ``callable`` is ``None`` then the method is only used to
+ assist in conflict detection between different addons requesting
+ the same attribute on the request object.
This is the recommended method for extending the request object
and should be used in favor of providing a custom request
factory via
:meth:`pyramid.config.Configurator.set_request_factory`.
- .. versionadded:: 1.3
+ .. versionadded:: 1.4
"""
- callable = self.maybe_dotted(callable)
+ if callable is not None:
+ callable = self.maybe_dotted(callable)
- name, callable = InstancePropertyMixin._make_property(
- callable, name=name, reify=reify)
+ property = property or reify
+ if property:
+ name, callable = InstancePropertyMixin._make_property(
+ callable, name=name, reify=reify)
+ elif name is None:
+ name = callable.__name__
def register():
- plist = self.registry.queryUtility(IRequestProperties)
+ exts = self.registry.queryUtility(IRequestExtensions)
- if plist is None:
- plist = []
- self.registry.registerUtility(plist, IRequestProperties)
- self.registry.registerHandler(_set_request_properties,
+ if exts is None:
+ exts = _RequestExtensions()
+ self.registry.registerUtility(exts, IRequestExtensions)
+ self.registry.registerHandler(_set_request_extensions,
(INewRequest,))
- plist.append((name, callable))
+ plist = exts.descriptors if property else exts.methods
+ plist[name] = callable
+
+ if callable is None:
+ self.action(('request extensions', name), None)
+ elif property:
+ intr = self.introspectable('request extensions', name,
+ self.object_description(callable),
+ 'request property')
+ intr['callable'] = callable
+ intr['property'] = True
+ intr['reify'] = reify
+ self.action(('request extensions', name), register,
+ introspectables=(intr,))
+ else:
+ intr = self.introspectable('request extensions', name,
+ self.object_description(callable),
+ 'request method')
+ intr['callable'] = callable
+ intr['property'] = False
+ intr['reify'] = False
+ self.action(('request extensions', name), register,
+ introspectables=(intr,))
+
+ @action_method
+ def set_request_property(self, callable, name=None, reify=False):
+ """ Add a property to the request object.
+
+ .. warning::
+
+ This method has been replaced by
+ :meth:`pyramid.config.Configurator.set_request_method` in
+ version :app:`Pyramid` version 1.4, more details can be found
+ there.
+
+ .. versionadded:: 1.3
+ """
+ self.set_request_method(
+ callable, name=name, property=not reify, reify=reify)
- intr = self.introspectable('request properties', name,
- self.object_description(callable),
- 'request property')
- intr['callable'] = callable
- intr['reify'] = reify
- self.action(('request properties', name), register,
- introspectables=(intr,))
+@implementer(IRequestExtensions)
+class _RequestExtensions(object):
+ def __init__(self):
+ self.descriptors = {}
+ self.methods = {}
-def _set_request_properties(event):
+def _set_request_extensions(event):
request = event.request
- plist = request.registry.queryUtility(IRequestProperties)
- request._set_properties(plist)
+ exts = request.registry.queryUtility(IRequestExtensions)
+ for name, fn in iteritems_(exts.methods):
+ method = fn.__get__(request, request.__class__)
+ setattr(request, name, method)
+ request._set_properties(exts.descriptors)
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 114a01854..042b4487b 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -513,9 +513,13 @@ class IRequestHandler(Interface):
IRequest.combined = IRequest # for exception view lookups
-class IRequestProperties(Interface):
- """ Marker interface for storing a list of request properties which
- will be added to the request object."""
+class IRequestExtensions(Interface):
+ """ Marker interface for storing request extensions (properties and
+ methods) which will be added to the request object."""
+ descriptors = Attribute(
+ """A list of descriptors that will be added to each request.""")
+ methods = Attribute(
+ """A list of methods to be added to each request.""")
class IRouteRequest(Interface):
""" *internal only* interface used as in a utility lookup to find
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index 1dfeda34c..a95326772 100644
--- a/pyramid/tests/test_config/test_factories.py
+++ b/pyramid/tests/test_config/test_factories.py
@@ -68,39 +68,40 @@ class TestFactoriesMixin(unittest.TestCase):
dummyfactory)
def test_set_request_property_with_callable(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
callable = lambda x: None
config.set_request_property(callable, name='foo')
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_request_property_with_unnamed_callable(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
def foo(self): pass
config.set_request_property(foo, reify=True)
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_request_property_with_property(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
callable = property(lambda x: None)
config.set_request_property(callable, name='foo')
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
def test_set_multiple_request_properties(self):
- from pyramid.interfaces import IRequestProperties
+ from pyramid.interfaces import IRequestExtensions
config = self._makeOne()
def foo(self): pass
bar = property(lambda x: None)
config.set_request_property(foo, reify=True)
config.set_request_property(bar, name='bar')
config.commit()
- plist = config.registry.getUtility(IRequestProperties)
- self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar']))
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
+ self.assertTrue('bar' in exts.descriptors)
def test_set_multiple_request_properties_conflict(self):
from pyramid.exceptions import ConfigurationConflictError
@@ -124,19 +125,76 @@ class TestFactoriesMixin(unittest.TestCase):
request = DummyRequest(config.registry)
event = Event()
config.registry.notify(event)
- plist = event.request.plist
- self.assertEqual(set(p[0] for p in plist), set(['foo', 'bar']))
+ exts = event.request.extensions
+ self.assertTrue('foo' in exts[0])
+ self.assertTrue('bar' in exts[1])
+
+ def test_set_request_method_subscriber(self):
+ from zope.interface import implementer
+ from pyramid.interfaces import INewRequest
+ config = self._makeOne(autocommit=True)
+ def foo(r): return 'bar'
+ config.set_request_method(foo, name='foo')
+ @implementer(INewRequest)
+ class Event(object):
+ request = DummyRequest(config.registry)
+ event = Event()
+ config.registry.notify(event)
+ self.assertEqual('bar', event.request.foo())
+
+ def test_set_request_method_with_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ callable = lambda x: None
+ config.set_request_method(callable, name='foo')
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.methods)
+
+ def test_set_request_method_with_unnamed_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ def foo(self): pass
+ config.set_request_method(foo)
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.methods)
+
+ def test_set_multiple_request_methods_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
+ config = self._makeOne()
+ def foo(self): pass
+ def bar(self): pass
+ config.set_request_method(foo, name='bar')
+ config.set_request_method(bar, name='bar')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_set_request_method_with_None_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ config.set_request_method(name='foo')
+ exts = config.registry.queryUtility(IRequestExtensions)
+ self.assertTrue(exts is None)
+
+ def test_set_request_method_with_None_callable_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
+ config = self._makeOne()
+ def bar(self): pass
+ config.set_request_method(name='foo')
+ config.set_request_method(bar, name='foo')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_set_request_method_with_None_callable_and_no_name(self):
+ config = self._makeOne(autocommit=True)
+ self.assertRaises(AttributeError, config.set_request_method)
-
class DummyRequest(object):
- plist = None
+ extensions = None
def __init__(self, registry):
self.registry = registry
def _set_properties(self, properties):
- if self.plist is None:
- self.plist = []
- self.plist.extend(properties)
+ if self.extensions is None:
+ self.extensions = []
+ self.extensions.extend(properties)