diff options
| -rw-r--r-- | CHANGES.rst | 11 | ||||
| -rw-r--r-- | docs/api/interfaces.rst | 21 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 16 | ||||
| -rw-r--r-- | src/pyramid/config/predicates.py | 16 | ||||
| -rw-r--r-- | src/pyramid/interfaces.py | 85 | ||||
| -rw-r--r-- | tests/test_config/test_predicates.py | 6 |
6 files changed, 146 insertions, 9 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 400e6d896..e2d5dbbac 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -46,6 +46,10 @@ Features ``Referer`` header for privacy reasons. See https://github.com/Pylons/pyramid/pull/3512 +- Added ``pyramid.interfaces.IPredicateInfo`` which defines the object passed + to predicate factories as their second argument. + See https://github.com/Pylons/pyramid/pull/3514 + Deprecations ------------ @@ -109,6 +113,13 @@ Backward Incompatibilities ``config.scan(..., categories=None)``. See https://github.com/Pylons/pyramid/pull/3510 +- The second argument to predicate factories has been changed from ``config`` + to ``info``, an instance of ``pyramid.interfaces.IPredicateInfo``. This + limits the data available to predicates but still provides the package, + registry, settings and dotted-name resolver which should cover most use + cases and is largely backward compatible. + See https://github.com/Pylons/pyramid/pull/3514 + Documentation Changes --------------------- diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index e542a6be0..08c67c385 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -109,3 +109,24 @@ Other Interfaces .. autointerface:: IViewDeriverInfo :members: + + .. autointerface:: IPredicateFactory + :members: + + .. autointerface:: IPredicateInfo + :members: + + .. autointerface:: IPredicate + :members: + + .. autointerface:: IRoutePredicate + :inherited-members: + :members: + + .. autointerface:: ISubscriberPredicate + :inherited-members: + :members: + + .. autointerface:: IViewPredicate + :inherited-members: + :members: diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 1ca5c3a6d..4a594a8c9 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1482,7 +1482,7 @@ method. For example: :linenos: class ContentTypePredicate(object): - def __init__(self, val, config): + def __init__(self, val, info): self.val = val def text(self): @@ -1493,11 +1493,11 @@ method. For example: def __call__(self, context, request): return request.content_type == self.val -The constructor of a predicate factory takes two arguments: ``val`` and -``config``. The ``val`` argument will be the argument passed to -``view_config`` (or ``add_view``). In the example above, it will be the string -``File``. The second argument, ``config``, will be the Configurator instance -at the time of configuration. +The constructor of an :class:`pyramid.interfaces.IPredicateFactory`` takes two arguments: ``val`` and ``info``. +The ``val`` argument will be the argument passed to ``view_config`` (or ``add_view``). +In the example above, it will be the string ``File``. +The second argument, ``info``, will be an :class:`pyramid.interfaces.IPredicateInfo` instance created relative to the action configuring the predicate. +This means the ``info.package`` value is the package where the action is invoked passing in ``val`` and ``info.maybe_dotted`` is also relative to this package. The ``text`` method must return a string. It should be useful to describe the behavior of the predicate in error messages. @@ -1524,7 +1524,7 @@ a :term:`view predicate` or a :term:`route predicate`: performed using either the route's :term:`root factory` or the app's :term:`default root factory`. -In both cases the ``__call__`` method is expected to return ``True`` or +In all cases the ``__call__`` method is expected to return ``True`` or ``False``. It is possible to use the same predicate factory as both a view predicate and @@ -1560,7 +1560,7 @@ event type. :linenos: class RequestPathStartsWith(object): - def __init__(self, val, config): + def __init__(self, val, info): self.val = val def text(self): diff --git a/src/pyramid/config/predicates.py b/src/pyramid/config/predicates.py index 3eb07c17d..ffdff5c21 100644 --- a/src/pyramid/config/predicates.py +++ b/src/pyramid/config/predicates.py @@ -98,6 +98,14 @@ class not_(object): # over = before +class PredicateInfo(object): + def __init__(self, package, registry, settings, maybe_dotted): + self.package = package + self.registry = registry + self.settings = settings + self.maybe_dotted = maybe_dotted + + class PredicateList(object): def __init__(self): self.sorter = TopologicalSorter() @@ -134,6 +142,12 @@ class PredicateList(object): phash = md5() weights = [] preds = [] + info = PredicateInfo( + package=config.package, + registry=config.registry, + settings=config.get_settings(), + maybe_dotted=config.maybe_dotted, + ) for n, (name, predicate_factory) in enumerate(ordered): vals = kw.pop(name, None) if vals is None: # XXX should this be a sentinel other than None? @@ -146,7 +160,7 @@ class PredicateList(object): if isinstance(val, not_): realval = val.value notted = True - pred = predicate_factory(realval, config) + pred = predicate_factory(realval, info) if notted: pred = Notted(pred) hashes = pred.phash() diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 2d8b1ac40..688293509 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -1440,6 +1440,91 @@ class IPredicateList(Interface): """ Interface representing a predicate list """ +class IPredicateInfo(Interface): + package = Attribute( + 'The "current package" where the predicate ' + 'configuration statement was found' + ) + registry = Attribute( + 'The "current" application registry where the predicate was invoked' + ) + settings = Attribute( + 'The deployment settings dictionary related ' + 'to the current application' + ) + + def maybe_dotted(value): + """ Resolve the :term:`dotted Python name` ``dotted`` to a + global Python object. If ``dotted`` is not a string, return + it without attempting to do any name resolution. If + ``dotted`` is a relative dotted name (e.g. ``.foo.bar``, + consider it relative to the ``package``.""" + + +class IPredicateFactory(Interface): + def __call__(value, info): + """ + Create a a :class:`.IPredicate` instance for a specific value. + + """ + + +class IPredicate(Interface): + def text(): + """ + A textual description of the predicate used in the introspector. + + For example, ``'content_type = application/json'`` for a + ``ContentTypePredicate`` with a ``value == 'application/json'``. + + """ + + def phash(): + """ + A unique string for the predicate containing both the name and value. + + Often implementations simply set ``phash = text``. + + """ + + +class IRoutePredicate(IPredicate): + def __call__(info, request): + """ + The ``info`` object is a dictionary containing two keys: + + - "match" is a dictionary of parameters that becomes + ``request.matchdict`` if the route is selected + (all route predicates match). + + - "route" is the :class:`.IRoute` object being matched. + + Return ``True`` if the route should be selected or ``False`` otherwise. + + """ + + +class ISubscriberPredicate(IPredicate): + def __call__(*args): + """ + The ``args`` is usually just a single ``event`` argument sent to + ``registry.notify``. + + Return ``True`` if the subscriber should be executed for the given + arguments or ``False`` otherwise. + + """ + + +class IViewPredicate(IPredicate): + def __call__(context, request): + """ + Return ``True`` if the view should be selected for the given + arguments or ``False`` otherwise. + + """ + + class IViewDeriver(Interface): options = Attribute( 'A list of supported options to be passed to ' diff --git a/tests/test_config/test_predicates.py b/tests/test_config/test_predicates.py index c27b41639..f8abbbce4 100644 --- a/tests/test_config/test_predicates.py +++ b/tests/test_config/test_predicates.py @@ -476,5 +476,11 @@ class DummyRequest: class DummyConfigurator(object): + package = 'dummy package' + registry = 'dummy registry' + + def get_settings(self): + return {} + def maybe_dotted(self, thing): return thing |
