diff options
| author | Michael Merickel <michael@merickel.org> | 2020-01-17 16:08:35 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-01-17 16:08:35 -0600 |
| commit | 03d3bbd2791918a844da49eb4449b4953b83a31b (patch) | |
| tree | 5899bebf50a3baf340b8fe650030c6a2b0525d0d | |
| parent | 9c153e1250e00faa06003c10c3a26886489e6210 (diff) | |
| parent | 108f026e1dcca554867ff8f7a211a5ce0668a26e (diff) | |
| download | pyramid-03d3bbd2791918a844da49eb4449b4953b83a31b.tar.gz pyramid-03d3bbd2791918a844da49eb4449b4953b83a31b.tar.bz2 pyramid-03d3bbd2791918a844da49eb4449b4953b83a31b.zip | |
Merge pull request #3560 from merwok/docs/rewrite-custom-predicates
Rewrite docs for custom predicates
| -rw-r--r-- | HISTORY.rst | 2 | ||||
| -rw-r--r-- | docs/glossary.rst | 2 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 10 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 176 | ||||
| -rw-r--r-- | docs/narr/viewconfig.rst | 20 | ||||
| -rw-r--r-- | docs/quick_tour.rst | 7 | ||||
| -rw-r--r-- | docs/whatsnew-1.4.rst | 2 | ||||
| -rw-r--r-- | src/pyramid/config/routes.py | 24 | ||||
| -rw-r--r-- | src/pyramid/config/views.py | 23 |
9 files changed, 134 insertions, 132 deletions
diff --git a/HISTORY.rst b/HISTORY.rst index 4fd13119d..8b0028065 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2530,7 +2530,7 @@ Features @view_config(abc=1) Similar features exist for ``add_route``, and ``add_subscriber``. See - "Adding A Third Party View, Route, or Subscriber Predicate" in the Hooks + "Adding A Custom View, Route, or Subscriber Predicate" in the Hooks chapter for more information. Note that changes made to support the above feature now means that only diff --git a/docs/glossary.rst b/docs/glossary.rst index 61ba34f45..7137f14a4 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -1091,7 +1091,7 @@ Glossary predicate factory A callable which is used by a third party during the registration of a route, view, or subscriber predicates to extend the configuration - system. See :ref:`registering_thirdparty_predicates` for more + system. See :ref:`registering_custom_predicates` for more information. add-on diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 4a594a8c9..c19d26236 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1416,10 +1416,10 @@ The ``ptweens`` command-line utility can be used to report the current implicit and explicit tween chains used by an application. See :ref:`displaying_tweens`. -.. _registering_thirdparty_predicates: +.. _registering_custom_predicates: -Adding a Third Party View, Route, or Subscriber Predicate ---------------------------------------------------------- +Adding a Custom View, Route, or Subscriber Predicate +---------------------------------------------------- .. versionadded:: 1.4 @@ -1446,7 +1446,7 @@ Likewise, a similar predicate can be used as a *route* predicate: config.add_route('name', '/foo', request_method='POST') Many other built-in predicates exists (``request_param``, and others). You can -add third-party predicates to the list of available predicates by using one of +add custom predicates to the list of available predicates by using one of :meth:`pyramid.config.Configurator.add_view_predicate` or :meth:`pyramid.config.Configurator.add_route_predicate`. The former adds a view predicate, the latter a route predicate. @@ -1564,7 +1564,7 @@ event type. self.val = val def text(self): - return 'path_startswith = %s' % (self.val,) + return 'request_path_startswith = %s' % (self.val,) phash = text diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 9372163e8..684e64395 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -1087,11 +1087,8 @@ A convenience context manager exists to set the route prefix for any Custom Route Predicates ----------------------- -Each of the predicate callables fed to the ``custom_predicates`` argument of -:meth:`~pyramid.config.Configurator.add_route` must be a callable accepting two -arguments. The first argument passed to a custom predicate is a dictionary -conventionally named ``info``. The second argument is the current -:term:`request` object. +A predicate is a callable that accepts two arguments: a dictionary +conventionally named ``info``, and the current :term:`request` object. The ``info`` dictionary has a number of contained values, including ``match`` and ``route``. ``match`` is a dictionary which represents the arguments matched @@ -1100,52 +1097,71 @@ was matched (see :class:`pyramid.interfaces.IRoute` for the API of such a route object). ``info['match']`` is useful when predicates need access to the route match. -For example: +Imagine you want to define a route when a part of the URL matches some +specific values. You could define three view configurations, each one +with its own ``match_param`` value (see :ref:`predicate_view_args`), but +you want to think of it as one route, and associate it with one view. +See this code: .. code-block:: python :linenos: - def any_of(segment_name, *allowed): - def predicate(info, request): - if info['match'][segment_name] in allowed: - return True - return predicate + class AnyOfPredicate: + def __init__(self, val, info): + self.segment_name = val[0] + self.allowed = tuple(val[0:]) + + def text(self): + args = (self.segment_name,) + self.allowed + return 'any_of = %s' % (args,) + + phash = text + + def __call__(self, info, request): + return info['match'][self.segment_name] in self.allowed - num_one_two_or_three = any_of('num', 'one', 'two', 'three') - config.add_route('route_to_num', '/{num}', - custom_predicates=(num_one_two_or_three,)) + config.add_route_predicate('any_of', AnyOfPredicate) + config.add_route('route_to_num', '/{num}', any_of=('num', 'one', 'two', 'three')) -The above ``any_of`` function generates a predicate which ensures that the -match value named ``segment_name`` is in the set of allowable values -represented by ``allowed``. We use this ``any_of`` function to generate a -predicate function named ``num_one_two_or_three``, which ensures that the -``num`` segment is one of the values ``one``, ``two``, or ``three`` , and use -the result as a custom predicate by feeding it inside a tuple to the -``custom_predicates`` argument to -:meth:`~pyramid.config.Configurator.add_route`. + +We register this class as a :term:`predicate factory` with the ``any_of`` keyword argument name. +Then we use that new keyword argument with :meth:`~pyramid.config.Configurator.add_route`. +When the route is requested, Pyramid instantiates the ``AnyOfPredicate`` class using the value passed to the ``any_of`` argument. +(The ``info`` parameter passed to the factory contains some metadata, you can ignore it for now.) +The resulting instance is a :term:`predicate`. +It will determine whether incoming requests satisfy its condition. +In the example above, a request for ``/three`` would match the route's URL pattern and satisfy the route's predicate because ``three`` is one of the allowed values, so the route would be matched. +However a request for ``/millions`` will match the route's URL pattern but would not satisfy the route's predicate, and the route would not be matched. A custom route predicate may also *modify* the ``match`` dictionary. For instance, a predicate might do some type conversion of values: .. code-block:: python - :linenos: + :linenos: - def integers(*segment_names): - def predicate(info, request): - match = info['match'] - for segment_name in segment_names: - try: - match[segment_name] = int(match[segment_name]) - except (TypeError, ValueError): - pass - return True - return predicate + class IntegersPredicate: + def __init__(self, val, info): + self.segment_names = val - ymd_to_int = integers('year', 'month', 'day') + def text(self): + return 'integers = %s' % (self.segment_names,) - config.add_route('ymd', '/{year}/{month}/{day}', - custom_predicates=(ymd_to_int,)) + phash = text + + def __call__(self, info, request): + match = info['match'] + for segment_name in self.segment_names: + try: + match[segment_name] = int(match[segment_name]) + except (TypeError, ValueError): + pass + return True + + + config.add_route_predicate('integers', IntegersPredicate) + config.add_route('ymd', '/{year}/{month}/{day}', + integers=('year', 'month', 'day')) Note that a conversion predicate is still a predicate, so it must return ``True`` or ``False``. A predicate that does *only* conversion, such as the one @@ -1157,18 +1173,25 @@ expressions specifying requirements for that marker. For instance: .. code-block:: python :linenos: - def integers(*segment_names): - def predicate(info, request): + class IntegersPredicate: + def __init__(self, val, info): + self.segment_names = val + + def text(self): + return 'integers = %s' % (self.segment_names,) + + phash = text + + def __call__(self, info, request): match = info['match'] - for segment_name in segment_names: + for segment_name in self.segment_names: match[segment_name] = int(match[segment_name]) return True - return predicate - ymd_to_int = integers('year', 'month', 'day') - config.add_route('ymd', '/{year:\d+}/{month:\d+}/{day:\d+}', - custom_predicates=(ymd_to_int,)) + config.add_route_predicate('integers', IntegersPredicate) + config.add_route('ymd', r'/{year:\d+}/{month:\d+}/{day:\d+}', + integers=('year', 'month', 'day')) Now the try/except is no longer needed because the route will not match at all unless these markers match ``\d+`` which requires them to be valid digits for @@ -1199,61 +1222,40 @@ route in a set of route predicates: .. code-block:: python :linenos: - def twenty_ten(info, request): - if info['route'].name in ('ymd', 'ym', 'y'): - return info['match']['year'] == '2010' - - config.add_route('y', '/{year}', custom_predicates=(twenty_ten,)) - config.add_route('ym', '/{year}/{month}', custom_predicates=(twenty_ten,)) - config.add_route('ymd', '/{year}/{month}/{day}', - custom_predicates=(twenty_ten,)) - -The above predicate, when added to a number of route configurations ensures -that the year match argument is '2010' if and only if the route name is 'ymd', -'ym', or 'y'. - -You can also caption the predicates by setting the ``__text__`` attribute. This -will help you with the ``pviews`` command (see -:ref:`displaying_application_routes`) and the ``pyramid_debugtoolbar``. - -If a predicate is a class, just add ``__text__`` property in a standard manner. - -.. code-block:: python - :linenos: + class TwentyTenPredicate: + def __init__(self, val, info): + pass - class DummyCustomPredicate1(object): - def __init__(self): - self.__text__ = 'my custom class predicate' + def text(self): + return "twenty_ten = True" - class DummyCustomPredicate2(object): - __text__ = 'my custom class predicate' + phash = text -If a predicate is a method, you'll need to assign it after method declaration -(see `PEP 232 <https://www.python.org/dev/peps/pep-0232/>`_). + def __call__(self, info, request): + if info['route'].name in ('ymd', 'ym', 'y'): + return info['match']['year'] == '2010' -.. code-block:: python - :linenos: + config.add_route_predicate('twenty_ten', TwentyTenPredicate) + config.add_route('y', '/{year}', twenty_ten=True) + config.add_route('ym', '/{year}/{month}', twenty_ten=True) + config.add_route('ymd', '/{year}/{month}/{day}', twenty_ten=True) - def custom_predicate(): - pass - custom_predicate.__text__ = 'my custom method predicate' +The above predicate, when added to a number of route configurations ensures that the year match +argument is ``2010`` if and only if the route name is ``ymd``, ``ym``, or ``y``. -If a predicate is a classmethod, using ``@classmethod`` will not work, but you -can still easily do it by wrapping it in a classmethod call. +The ``text`` method is a way to caption the predicates. This will help you with the ``pviews`` +command (see :ref:`displaying_application_routes`) and the :term:`pyramid_debugtoolbar`. -.. code-block:: python - :linenos: - - def classmethod_predicate(): - pass - classmethod_predicate.__text__ = 'my classmethod predicate' - classmethod_predicate = classmethod(classmethod_predicate) +The ``phash`` ("predicate hash") method should return a string that uniquely identifies a specific predicate. +A good way to do that is to use the same argument name and value that are in the call to ``add_route``, +like in the examples above. -The same will work with ``staticmethod``, using ``staticmethod`` instead of -``classmethod``. .. seealso:: + See :ref:`registering_custom_predicates` for more information about + custom view, route, and subscriber predicates. + See also :class:`pyramid.interfaces.IRoute` for more API documentation about route objects. diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 6a49e02a5..5ba965def 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -317,6 +317,8 @@ Non-Predicate Arguments .. versionadded:: 1.8 +.. _predicate_view_args: + Predicate Arguments +++++++++++++++++++ @@ -506,21 +508,23 @@ configured view. ``custom_predicates`` If ``custom_predicates`` is specified, it must be a sequence of references to - custom predicate callables. Use custom predicates when no set of predefined - predicates do what you need. Custom predicates can be combined with + custom predicate callables. Custom predicates can be combined with predefined predicates as necessary. Each custom predicate callable should accept two arguments, ``context`` and ``request``, and should return either ``True`` or ``False`` after doing arbitrary evaluation of the context resource and/or the request. If all callables return ``True``, the associated view callable will be considered viable for a given request. + This parameter is kept around for backward compatibility. - If ``custom_predicates`` is not specified, no custom predicates are used. + .. deprecated:: 1.5 + See section below for new-style custom predicates. -``predicates`` - Pass a key/value pair here to use a third-party predicate registered via - :meth:`pyramid.config.Configurator.add_view_predicate`. More than one - key/value pair can be used at the same time. See - :ref:`view_and_route_predicates` for more information about third-party +``**predicates`` + Extra keyword parameters are used to invoke custom predicates, defined + in your app or by third-party packages extending Pyramid and registered via + :meth:`pyramid.config.Configurator.add_view_predicate`. Use custom predicates + when no set of predefined predicates do what you need. See + :ref:`view_and_route_predicates` for more information about custom predicates. .. versionadded:: 1.4a1 diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 062693d24..c4ab0b3e8 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -478,12 +478,11 @@ more to offer: - One route leading to multiple views, based on information in the request or data such as ``request_param``, ``request_method``, ``accept``, ``header``, - ``xhr``, ``containment``, and ``custom_predicates`` + ``xhr``, ``containment``, and custom predicates. .. seealso:: See also: - :ref:`Quick Tutorial View Classes <qtut_view_classes>`, :ref:`Quick - Tutorial More View Classes <qtut_more_view_classes>`, and - :ref:`class_as_view`. + :ref:`View Classes <qtut_view_classes>` and :ref:`More View Classes <qtut_more_view_classes>` + in the Quick Tutorial, :ref:`class_as_view`, and :ref:`view_and_route_predicates`. Quick project startup with cookiecutters diff --git a/docs/whatsnew-1.4.rst b/docs/whatsnew-1.4.rst index fce889854..6c9a9bf9b 100644 --- a/docs/whatsnew-1.4.rst +++ b/docs/whatsnew-1.4.rst @@ -31,7 +31,7 @@ Third-Party Predicates Similar features exist for :meth:`pyramid.config.Configurator.add_route`, and :meth:`pyramid.config.Configurator.add_subscriber`. See - :ref:`registering_thirdparty_predicates` for more information. + :ref:`registering_custom_predicates` for more information. Easy Custom JSON Serialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index 44fbb9c46..78e708d1e 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -40,13 +40,10 @@ class RoutesConfiguratorMixin(object): inherit_slash=None, **predicates ): - """ Add a :term:`route configuration` to the current - configuration state, as well as possibly a :term:`view - configuration` to be used to specify a :term:`view callable` - that will be invoked when this route matches. The arguments - to this method are divided into *predicate*, *non-predicate*, - and *view-related* types. :term:`Route predicate` arguments - narrow the circumstances in which a route will be match a + """ Add a :term:`route configuration` to the current configuration + state. Arguments to ``add_route`` are divided into *predicate* + and *non-predicate* types. :term:`Route predicate` arguments + narrow the circumstances in which a route will match a request; non-predicate arguments are informational. Non-Predicate Arguments @@ -283,6 +280,8 @@ class RoutesConfiguratorMixin(object): .. versionadded:: 1.4a4 + .. deprecated:: 2.0 + custom_predicates .. deprecated:: 1.5 @@ -302,14 +301,13 @@ class RoutesConfiguratorMixin(object): :ref:`custom_route_predicates` for more information about ``info``. - predicates + \\*\\*predicates - Pass a key/value pair here to use a third-party predicate - registered via + Pass extra keyword parameters to use custom predicates registered via :meth:`pyramid.config.Configurator.add_route_predicate`. More than - one key/value pair can be used at the same time. See + one custom predicate can be used at the same time. See :ref:`view_and_route_predicates` for more information about - third-party predicates. + custom predicates. .. versionadded:: 1.4 @@ -321,7 +319,7 @@ class RoutesConfiguratorMixin(object): 'Configurator.add_route is deprecated as of Pyramid 1.5. ' 'Use "config.add_route_predicate" and use the registered ' 'route predicate as a predicate argument to add_route ' - 'instead. See "Adding A Third Party View, Route, or ' + 'instead. See "Adding A Custom View, Route, or ' 'Subscriber Predicate" in the "Hooks" chapter of the ' 'documentation for more information.' ), diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 2cc5e8144..c4fb54dd8 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -723,31 +723,30 @@ class ViewsConfiguratorMixin(object): .. versionadded:: 1.4a4 + .. deprecated:: 2.0 + custom_predicates .. deprecated:: 1.5 This value should be a sequence of references to custom - predicate callables. Use custom predicates when no set of - predefined predicates do what you need. Custom predicates - can be combined with predefined predicates as necessary. - Each custom predicate callable should accept two arguments: + predicate callables. Each custom predicate callable + should accept two arguments: ``context`` and ``request`` and should return either ``True`` or ``False`` after doing arbitrary evaluation of - the context and/or the request. The ``predicates`` argument - to this method and the ability to register third-party view - predicates via + the context and/or the request. The ability to register + custom view predicates via :meth:`pyramid.config.Configurator.add_view_predicate` obsoletes this argument, but it is kept around for backwards compatibility. - view_options + \\*\\*view_options - Pass a key/value pair here to use a third-party predicate or set a - value for a view deriver. See + Pass extra keyword parameters to use custom predicates + or set a value for a view deriver. See :meth:`pyramid.config.Configurator.add_view_predicate` and :meth:`pyramid.config.Configurator.add_view_deriver`. See :ref:`view_and_route_predicates` for more information about - third-party predicates and :ref:`view_derivers` for information + custom predicates and :ref:`view_derivers` for information about view derivers. .. versionadded: 1.4a1 @@ -769,7 +768,7 @@ class ViewsConfiguratorMixin(object): 'Configurator.add_view is deprecated as of Pyramid 1.5. ' 'Use "config.add_view_predicate" and use the registered ' 'view predicate as a predicate argument to add_view ' - 'instead. See "Adding A Third Party View, Route, or ' + 'instead. See "Adding A Custom View, Route, or ' 'Subscriber Predicate" in the "Hooks" chapter of the ' 'documentation for more information.' ), |
