diff options
| author | Chris McDonough <chrism@agendaless.com> | 2010-09-08 04:25:35 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2010-09-08 04:25:35 +0000 |
| commit | 74409d12f7eb085bc992a200cc74799e4d1ff355 (patch) | |
| tree | 14b10948171be45b425f87122be156a7dc11c117 | |
| parent | 68469214646debcdcea662f34b41f41e0ae8db12 (diff) | |
| download | pyramid-74409d12f7eb085bc992a200cc74799e4d1ff355.tar.gz pyramid-74409d12f7eb085bc992a200cc74799e4d1ff355.tar.bz2 pyramid-74409d12f7eb085bc992a200cc74799e4d1ff355.zip | |
- The ``repoze.bfg.urldispatch.Route`` constructor (not an API) now
accepts a different ordering of arguments. Previously it was
``(pattern, name, factory=None, predicates=())``. It is now
``(name, pattern, factory=None, predicates=())``. This is in
support of consistency with ``configurator.add_route``.
- The ``repoze.bfg.urldispatch.RoutesMapper.connect`` method (not an
API) now accepts a different ordering of arguments. Previously it
was ``(pattern, name, factory=None, predicates=())``. It is now
``(name, pattern, factory=None, predicates=())``. This is in
support of consistency with ``configurator.add_route``.
- The ``repoze.bfg.urldispatch.RoutesMapper`` object now has a
``get_route`` method which returns a single Route object or
``None``.
- A new interface ``repoze.bfg.interfaces.IRoute`` was added. The
``repoze.bfg.urldispatch.Route`` object implements this interface.
- The canonical attribute for accessing the routing pattern from a
route object is now ``pattern`` rather than ``path``.
- The argument to ``repoze.bfg.configuration.Configurator.add_route``
which was previously called ``path`` is now called ``pattern`` for
better explicability. For backwards compatibility purposes, passing
a keyword argument named ``path`` to ``add_route`` will still work
indefinitely.
- The ``path`` attribute to the ZCML ``route`` directive is now named
``pattern`` for better explicability. The older ``path`` attribute
will continue to work indefinitely.
- All narrative, API, and tutorial docs which referred to a route
pattern as a ``path`` have now been updated to refer to them as a
``pattern``.
- The routesalchemy template has been updated to use ``pattern`` in
its route declarations rather than ``path``.
22 files changed, 456 insertions, 256 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 4e2e54dcf..c32c0909b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -18,9 +18,45 @@ Bug Fixes when more than one person or thread is trying to execute the same view at the same time: https://bugs.launchpad.net/karl3/+bug/621364 +Features +-------- + +- The argument to ``repoze.bfg.configuration.Configurator.add_route`` + which was previously called ``path`` is now called ``pattern`` for + better explicability. For backwards compatibility purposes, passing + a keyword argument named ``path`` to ``add_route`` will still work + indefinitely. + +- The ``path`` attribute to the ZCML ``route`` directive is now named + ``pattern`` for better explicability. The older ``path`` attribute + will continue to work indefinitely. + +Documentation +------------- + +- All narrative, API, and tutorial docs which referred to a route + pattern as a ``path`` have now been updated to refer to them as a + ``pattern``. + +Paster Templates +---------------- + +- The routesalchemy template has been updated to use ``pattern`` in + its route declarations rather than ``path``. + Internal -------- +- The ``repoze.bfg.urldispatch.RoutesMapper`` object now has a + ``get_route`` method which returns a single Route object or + ``None``. + +- A new interface ``repoze.bfg.interfaces.IRoute`` was added. The + ``repoze.bfg.urldispatch.Route`` object implements this interface. + +- The canonical attribute for accessing the routing pattern from a + route object is now ``pattern`` rather than ``path``. + - Use ``hash()`` rather than ``id()`` when computing the "phash" of a custom route/view predicate in order to allow the custom predicate some control over which predicates are "equal". @@ -30,6 +66,18 @@ Internal ``repoze.bfg.request.add_global_response_headers`` in case the response is not a WebOb response. +- The ``repoze.bfg.urldispatch.Route`` constructor (not an API) now + accepts a different ordering of arguments. Previously it was + ``(pattern, name, factory=None, predicates=())``. It is now + ``(name, pattern, factory=None, predicates=())``. This is in + support of consistency with ``configurator.add_route``. + +- The ``repoze.bfg.urldispatch.RoutesMapper.connect`` method (not an + API) now accepts a different ordering of arguments. Previously it + was ``(pattern, name, factory=None, predicates=())``. It is now + ``(name, pattern, factory=None, predicates=())``. This is in + support of consistency with ``configurator.add_route``. + 1.3a11 (2010-09-05) =================== diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index be2689c68..021162d0b 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -40,13 +40,13 @@ to code will often have declarations like this within :term:`ZCML`: :linenos: <route - path=":foo/:bar" + pattern=":foo/:bar" name="foobar" view=".views.foobar" /> <route - path=":baz/:buz" + pattern=":baz/:buz" name="bazbuz" view=".views.bazbuz" /> @@ -105,9 +105,9 @@ application than it is like a "pure" URL-dispatch based application. But unlike in a "pure" traversal-based application, in a hybrid application, :term:`traversal` is performed during a request after a route has already matched. This means that the URL pattern that -represents the ``path`` argument of a route must match the -``PATH_INFO`` of a request, and after the route path has matched, most -of the "normal" rules of traversal with respect to :term:`context +represents the ``pattern`` argument of a route must match the +``PATH_INFO`` of a request, and after the route pattern has matched, +most of the "normal" rules of traversal with respect to :term:`context finding` and :term:`view lookup` apply. There are only four real differences between a purely traversal-based @@ -125,7 +125,7 @@ application and a hybrid application: underlying :term:`WSGI` environment is used wholesale as a traversal path; in a hybrid application, the traversal path is not the entire ``PATH_INFO`` string, but a portion of the URL determined by a - matching pattern in the matched route configuration's path. + matching pattern in the matched route configuration's pattern. - In a purely traversal based application, view configurations which do not mention a ``route_name`` argument are considered during @@ -151,7 +151,7 @@ application except: To create a hybrid mode application, use a :term:`route configuration` that implies a particular :term:`root factory` and which also includes -a ``path`` argument that contains a special dynamic part: either +a ``pattern`` argument that contains a special dynamic part: either ``*traverse`` or ``*subpath``. The Root Object for a Route Match @@ -188,32 +188,32 @@ match is straightforward. When a route is matched: root factory were explained previously within :ref:`the_object_graph`. -.. _using_traverse_in_a_route_path: +.. _using_traverse_in_a_route_pattern: -Using ``*traverse`` In a Route Path -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Using ``*traverse`` In a Route Pattern +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A hybrid application most often implies the inclusion of a route configuration that contains the special token ``*traverse`` at the end -of a route's path: +of a route's pattern: .. code-block:: xml :linenos: <route - path=":foo/:bar/*traverse" + pattern=":foo/:bar/*traverse" name="home" /> -A ``*traverse`` token at the end of the path in a route's +A ``*traverse`` token at the end of the pattern in a route's configuration implies a "remainder" *capture* value. When it is used, it will match the remainder of the path segments of the URL. This remainder becomes the path used to perform traversal. .. note:: - The ``*remainder`` route path pattern syntax is explained in more - detail within :ref:`route_path_pattern_syntax`. + The ``*remainder`` route pattern syntax is explained in more + detail within :ref:`route_pattern_syntax`. Note that unlike the examples provided within :ref:`urldispatch_chapter`, the ``<route>`` configuration named @@ -223,7 +223,7 @@ hybrid mode application relies on :term:`traversal` to do invoking a specific view callable named directly within the matched route's configuration. -Because the path of the above route ends with ``*traverse``, when this +Because the pattern of the above route ends with ``*traverse``, when this route configuration is matched during a request, :mod:`repoze.bfg` will attempt to use :term:`traversal` against the :term:`root` object implied by the :term:`root factory` implied by the route's @@ -263,7 +263,7 @@ route configuration statement: :linenos: <route - path=":foo/:bar/*traverse" + pattern=":foo/:bar/*traverse" name="home" factory=".routes.root_factory" /> @@ -288,15 +288,15 @@ to do. global ``root_factory`` function to generate a root object. When the route configuration named ``home`` above is matched during a -request, the matchdict generated will be based on its path: +request, the matchdict generated will be based on its pattern: ``:foo/:bar/*traverse``. The "capture value" implied by the -``*traverse`` element in the path pattern will be used to traverse the +``*traverse`` element in the pattern will be used to traverse the graph in order to find a context, starting from the root object returned from the root factory. In the above example, the :term:`root` object found will be the instance named ``root`` in ``routes.py``. -If the URL that matched a route with the path ``:foo/:bar/*traverse``, +If the URL that matched a route with the pattern ``:foo/:bar/*traverse``, is ``http://example.com/one/two/a/b/c``, the traversal path used against the root object will be ``a/b/c``. As a result, :mod:`repoze.bfg` will attempt to traverse through the edges ``a``, @@ -319,7 +319,7 @@ invoked after a route matches: :linenos: <route - path=":foo/:bar/*traverse" + pattern=":foo/:bar/*traverse" name="home" factory=".routes.root_factory" /> @@ -355,7 +355,7 @@ when a hybrid route is matched: :linenos: <route - path=":foo/:bar/*traverse" + pattern=":foo/:bar/*traverse" name="home" factory=".routes.root_factory" /> @@ -395,8 +395,8 @@ arguments). Using the ``traverse`` Argument In a Route Definition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Rather than using the ``*traverse`` remainder marker in a path -pattern, you can use the ``traverse`` argument to the +Rather than using the ``*traverse`` remainder marker in a pattern, you +can use the ``traverse`` argument to the :meth:`repoze.bfg.configuration.Configurator.add_route`` method or the ``traverse`` attribute of the :ref:`route_directive` ZCML directive. (either method is equivalent). @@ -414,16 +414,16 @@ declaration: <route name="abc" - path="/articles/:article/edit" + pattern="/articles/:article/edit" traverse="/articles/:article" /> The syntax of the ``traverse`` argument is the same as it is for -``path``. +``pattern``. -If, as above, the ``path`` provided is ``articles/:article/edit``, and -the ``traverse`` argument provided is ``/:article``, when a request -comes in that causes the route to match in such a way that the +If, as above, the ``pattern`` provided is ``articles/:article/edit``, +and the ``traverse`` argument provided is ``/:article``, when a +request comes in that causes the route to match in such a way that the ``article`` match value is ``1`` (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's ``__getitem__`` will be called with @@ -432,12 +432,12 @@ exists, it will become the :term:`context` of the request. :ref:`traversal_chapter` has more information about traversal. If the traversal path contains segment marker names which are not -present in the path argument, a runtime error will occur. The +present in the pattern argument, a runtime error will occur. The ``traverse`` pattern should not contain segment markers that do not exist in the ``path``. Note that the ``traverse`` argument is ignored when attached to a -route that has a ``*traverse`` remainder marker in its path. +route that has a ``*traverse`` remainder marker in its pattern. Traversal will begin at the root object implied by this route (either the global root, or the object returned by the ``factory`` associated @@ -448,7 +448,7 @@ Making Global Views Match By default, view configurations that don't mention a ``route_name`` will be not found by view lookup when a route that mentions a -``*traverse`` in its path matches. You can make these match forcibly +``*traverse`` in its pattern matches. You can make these match forcibly by adding the ``use_global_views`` flag to the route definition. For example, the ``views.bazbuz`` view below will be found if the route named ``abc`` below is matched and the ``PATH_INFO`` is @@ -459,7 +459,7 @@ have the ``route_name="abc"`` attribute. :linenos: <route - path="/abc/*traverse" + pattern="/abc/*traverse" name="abc" use_global_views="True" /> @@ -475,8 +475,8 @@ have the ``route_name="abc"`` attribute. .. _star_subpath: -Using ``*subpath`` in a Route Path -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Using ``*subpath`` in a Route Pattern +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are certain extremely rare cases when you'd like to influence the traversal :term:`subpath` when a route matches without actually @@ -486,16 +486,16 @@ performing traversal. For instance, the ``PATH_INFO`` from the request's subpath, so it's useful to be able to influence this value. -When ``*subpath`` exists in a path pattern, no path is actually -traversed, but the traversal algorithm will return a :term:`subpath` -list implied by the capture value of ``*subpath``. You'll see this -pattern most commonly in route declarations that look like this: +When ``*subpath`` exists in a pattern, no path is actually traversed, +but the traversal algorithm will return a :term:`subpath` list implied +by the capture value of ``*subpath``. You'll see this pattern most +commonly in route declarations that look like this: .. code-block:: xml :linenos: <route - path="/static/*subpath" + pattern="/static/*subpath" name="static" view=".views.static_view" /> @@ -523,7 +523,7 @@ For example, this pair of route/view ZCML declarations will generate a :linenos: <route - path=":foo/:bar/*traverse" + pattern=":foo/:bar/*traverse" name="home" view=".views.home" /> @@ -542,7 +542,7 @@ example, this ``<route>`` statement: :linenos: <route - path=":foo/:bar/*traverse" + pattern=":foo/:bar/*traverse" name="home" view=".views.home" /> @@ -553,7 +553,7 @@ Can also be spelled like so: :linenos: <route - path=":foo/:bar/*traverse" + pattern=":foo/:bar/*traverse" name="home" /> @@ -565,8 +565,8 @@ Can also be spelled like so: The two spellings are logically equivalent. In fact, the former is just a syntactical shortcut for the latter. -Binding Extra Views Against a Route Configuration that Doesn't Have a ``*traverse`` Element In Its Path -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Binding Extra Views Against a Route Configuration that Doesn't Have a ``*traverse`` Element In Its Patttern +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here's another corner case that just makes no sense. @@ -574,7 +574,7 @@ Here's another corner case that just makes no sense. :linenos: <route - path="/abc" + pattern="/abc" name="abc" view=".views.abc" /> @@ -592,13 +592,13 @@ when the route matches, because the default view is always invoked when a route matches and when no post-match traversal is performed. To make the above ``<view>`` declaration non-useless, the special -``*traverse`` token must end the route's path. For example: +``*traverse`` token must end the route's pattern. For example: .. code-block:: xml :linenos: <route - path="/abc/*traverse" + pattern="/abc/*traverse" name="abc" view=".views.abc" /> diff --git a/docs/narr/router.rst b/docs/narr/router.rst index 8c6407a72..025d70157 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -33,9 +33,9 @@ processing? configuration, the :mod:`repoze.bfg` :term:`router` calls a :term:`URL dispatch` "route mapper." The job of the mapper is to examine the ``PATH_INFO`` implied by the request to determine - whether any user-defined :term:`route` matches the current WSGI - environment. The :term:`router` passes the request as an argument - to the mapper. + whether any user-defined :term:`route` pattern matches the current + WSGI environment. The :term:`router` passes the request as an + argument to the mapper. #. If any route matches, the WSGI environment is mutated; a ``bfg.routes.route`` key and a ``bfg.routes.matchdict`` are added diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 7c8bb7ce6..6a5a24c81 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -65,12 +65,12 @@ Route Configuration ------------------- :term:`Route configuration` is the act of adding a new :term:`route` -to an application. A route has a *path*, representing a pattern meant -to match against the ``PATH_INFO`` portion of a URL, and a *name*, -which is used by developers within a :mod:`repoze.bfg` application to -uniquely identify a particular route when generating a URL. It also -optionally has a ``factory``, a set of :term:`route predicate` -parameters, and a set of :term:`view` parameters. +to an application. A route has a *pattern*, representing a pattern +meant to match against the ``PATH_INFO`` portion of a URL, and a +*name*, which is used by developers within a :mod:`repoze.bfg` +application to uniquely identify a particular route when generating a +URL. It also optionally has a ``factory``, a set of :term:`route +predicate` parameters, and a set of :term:`view` parameters. A route configuration may be added to the system via :term:`imperative configuration` or via :term:`ZCML`. Both are completely equivalent. @@ -92,7 +92,7 @@ registry`. Here's an example: # repoze.bfg.configuration.Configurator class; "myview" is assumed # to be a "view callable" function from views import myview - config.add_route(name='myroute', path='/prefix/:one/:two', view=myview) + config.add_route('myroute', '/prefix/:one/:two', view=myview) .. index:: single: ZCML directive; route @@ -111,7 +111,7 @@ application. <route name="myroute" - path="/prefix/:one/:two" + pattern="/prefix/:one/:two" view=".views.myview" /> @@ -149,22 +149,21 @@ Here's an example route configuration that references a view callable: <route name="myroute" - path="/prefix/:one/:two" + pattern="/prefix/:one/:two" view="mypackage.views.myview" /> When a route configuration names a ``view`` attribute, the :term:`view callable` named as that ``view`` attribute will always be found and -invoked when the associated route path pattern matches during a -request. +invoked when the associated route pattern matches during a request. The purpose of making it possible to specify a view callable within a route configuration is to prevent developers from needing to deeply understand the details of :term:`context finding` and :term:`view lookup`. When a route names a view callable, and a request enters the -system which matches the path of the route, the result is simple: the -view callable associated with the route is invoked with the request -that caused the invocation. +system which matches the pattern of the route, the result is simple: +the view callable associated with the route is invoked with the +request that caused the invocation. For most usage, you needn't understand more than this; how it works is an implementation detail. In the interest of completeness, however, @@ -176,8 +175,8 @@ Route View Callable Registration and Lookup Details When a ``view`` attribute is attached to a route configuration, :mod:`repoze.bfg` ensures that a :term:`view configuration` is -registered that will always be found when the route path pattern is -matched during a request. To do so: +registered that will always be found when the route pattern is matched +during a request. To do so: - A special route-specific :term:`interface` is created at startup time for each route configuration declaration. @@ -194,7 +193,7 @@ matched during a request. To do so: - The fact that the request is decorated with a route-specific interface causes the view lookup machinery to always use the view callable registered using that interface by the route configuration - to service requests that match the route path pattern. + to service requests that match the route pattern. In this way, we supply a shortcut to the developer. Under the hood, :mod:`repoze.bfg` still consumes the :term:`context finding` and @@ -207,19 +206,19 @@ various exceptional cases as documented in :ref:`hybrid_chapter`. .. index:: single: route path pattern syntax -.. _route_path_pattern_syntax: +.. _route_pattern_syntax: -Route Path Pattern Syntax -~~~~~~~~~~~~~~~~~~~~~~~~~ +Route Pattern Syntax +~~~~~~~~~~~~~~~~~~~~ The syntax of the pattern matching language used by :mod:`repoze.bfg` -URL dispatch in the *path* argument is straightforward; it is close to -that of the :term:`Routes` system used by :term:`Pylons`. +URL dispatch in the *pattern* argument is straightforward; it is close +to that of the :term:`Routes` system used by :term:`Pylons`. -The *path* used in route configuration may start with a slash -character. If the path does not start with a slash character, an +The *pattern* used in route configuration may start with a slash +character. If the pattern does not start with a slash character, an implicit slash will be prepended to it at matching time. For example, -the following paths are equivalent: +the following patterns are equivalent: .. code-block:: text @@ -231,10 +230,10 @@ and: /:foo/bar/baz -A path segment (an individual item between ``/`` characters in the -path) may either be a literal string (e.g. ``foo``) *or* it may be a -segment replacement marker (e.g. ``:foo``) or a certain combination of -both. +A patttern segment (an individual item between ``/`` characters in the +pattern) may either be a literal string (e.g. ``foo``) *or* it may be +a segment replacement marker (e.g. ``:foo``) or a certain combination +of both. A segment replacement marker is in the format ``:name``, where this means "accept any characters up to the next nonalphaunumeric character @@ -314,8 +313,8 @@ decoded): If the pattern has a ``*`` in it, the name which follows it is considered a "remainder match". A remainder match *must* come at the -end of the path pattern. Unlike segment replacement markers, it does -not need to be preceded by a slash. For example: +end of the pattern. Unlike segment replacement markers, it does not +need to be preceded by a slash. For example: .. code-block:: text @@ -405,7 +404,7 @@ found via :term:`view lookup`. .. code-block:: xml <route - path="/abc" + pattern="/abc" name="abc" view=".views.theview" factory=".models.root_factory" @@ -463,7 +462,7 @@ represent neither predicates nor view configuration information. the object returned by the ``factory`` associated with this route). The syntax of the ``traverse`` argument is the same as it is for - ``path``. For example, if the ``path`` provided is + ``pattern``. For example, if the ``pattern`` provided is ``articles/:article/edit``, and the ``traverse`` argument provided is ``/:article``, when a request comes in that causes the route to match in such a way that the ``article`` match value is '1' (when @@ -475,27 +474,31 @@ represent neither predicates nor view configuration information. information about traversal. If the traversal path contains segment marker names which are not - present in the path argument, a runtime error will occur. The - ``traverse`` pattern should not contain segment markers that do not - exist in the ``path``. + present in the ``pattern`` argument, a runtime error will occur. + The ``traverse`` pattern should not contain segment markers that do + not exist in the ``pattern``. A similar combining of routing and traversal is available when a route is matched which contains a ``*traverse`` remainder marker in - its path (see :ref:`using_traverse_in_a_route_path`). The + its pattern (see :ref:`using_traverse_in_a_route_pattern`). The ``traverse`` argument allows you to associate route patterns with an arbitrary traversal path without using a a ``*traverse`` remainder marker; instead you can use other match information. Note that the ``traverse`` argument is ignored when attached to a - route that has a ``*traverse`` remainder marker in its path. + route that has a ``*traverse`` remainder marker in its pattern. **Predicate Arguments** -``path`` +``pattern`` The path of the route e.g. ``ideas/:idea``. This argument is required. See :ref:`route_path_pattern_syntax` for information about the syntax of route paths. If the path doesn't match the - current URL, route matching continues. + current URL, route matching continues. + + .. note:: In :mod:`repoze.bfg` 1.2 and prior, this argument existed + as ``path``. ``path`` continues to work as an alias for + ``pattern``. ``xhr`` This value should be either ``True`` or ``False``. If this value is @@ -726,9 +729,9 @@ predicate which performs the ``match`` modification will receive the custom object. Just do all that in a single custom predicate. The ``route`` object in the ``info`` dict is an object that has two -useful attributes: ``name`` and ``path``. The ``name`` attribute is -the route name. The ``path`` attribute is the route pattern. An -example of using the route in a set of route predicates: +useful attributes: ``name`` and ``pattern``. The ``name`` attribute +is the route name. The ``pattern`` attribute is the route pattern. +An example of using the route in a set of route predicates: .. code-block:: python :linenos: @@ -787,12 +790,12 @@ finding` and :term:`view lookup`. The Matchdict ~~~~~~~~~~~~~ -When the URL path pattern associated with a particular route -configuration is matched by a request, a dictionary named -``matchdict`` is added as an attribute of the :term:`request` object. -Thus, ``request.matchdict`` will contain the values that match -replacement patterns in the ``path`` element. The keys in a matchdict -will be strings. The values will be Unicode objects. +When the URL pattern associated with a particular route configuration +is matched by a request, a dictionary named ``matchdict`` is added as +an attribute of the :term:`request` object. Thus, +``request.matchdict`` will contain the values that match replacement +patterns in the ``pattern`` element. The keys in a matchdict will be +strings. The values will be Unicode objects. .. note:: @@ -822,12 +825,12 @@ The simplest route declaration which configures a route match to <route name="idea" - path="site/:id" + pattern="site/:id" view="mypackage.views.site_view" /> When a route configuration with a ``view`` attribute is added to the -system, and an incoming request matches the *path* of the route +system, and an incoming request matches the *pattern* of the route configuration, the :term:`view callable` named as the ``view`` attribute of the route configuration will be invoked. @@ -835,15 +838,15 @@ In the case of the above example, when the URL of a request matches ``/site/:id``, the view callable at the Python dotted path name ``mypackage.views.site_view`` will be called with the request. In other words, we've associated a view callable directly with a route -path. +pattern. -When the ``/site/:id`` route path pattern matches during a request, -the ``site_view`` view callable is invoked with that request as its -sole argument. When this route matches, a ``matchdict`` will be -generated and attached to the request as ``request.matchdict``. If -the specific URL matched is ``/site/1``, the ``matchdict`` will be a -dictionary with a single key, ``id``; the value will be the string -``'1'``, ex.: ``{'id':'1'}``. +When the ``/site/:id`` route pattern matches during a request, the +``site_view`` view callable is invoked with that request as its sole +argument. When this route matches, a ``matchdict`` will be generated +and attached to the request as ``request.matchdict``. If the specific +URL matched is ``/site/1``, the ``matchdict`` will be a dictionary +with a single key, ``id``; the value will be the string ``'1'``, ex.: +``{'id':'1'}``. The ``mypackage.views`` module referred to above might look like so: @@ -857,7 +860,7 @@ The ``mypackage.views`` module referred to above might look like so: The view has access to the matchdict directly via the request, and can access variables within it that match keys present as a result of the -route path pattern. +route pattern. See :ref:`views_chapter` for more information about views. @@ -872,19 +875,19 @@ might add to your application: <route name="idea" - path="ideas/:idea" + pattern="ideas/:idea" view="mypackage.views.idea_view" /> <route name="user" - path="users/:user" + pattern="users/:user" view="mypackage.views.user_view" /> <route name="tag" - path="tags/:tag" + pattern="tags/:tag" view="mypackage.views.tag_view" /> @@ -941,7 +944,7 @@ An example of using a route with a factory: <route name="idea" - path="ideas/:idea" + pattern="ideas/:idea" view=".views.idea_view" factory=".models.Idea" /> @@ -972,7 +975,7 @@ a ``view`` declaration. <route name="idea" - path="site/:id" + pattern="site/:id" /> <view @@ -989,7 +992,7 @@ completely equivalent to this example provided in <route name="idea" - path="site/:id" + pattern="site/:id" view="mypackage.views.site_view" /> @@ -1007,26 +1010,26 @@ in :ref:`hybrid_chapter`. Matching the Root URL --------------------- -It's not entirely obvious how to use a route path pattern to match the -root URL ("/"). To do so, give the empty string as a path in a ZCML +It's not entirely obvious how to use a route pattern to match the root +URL ("/"). To do so, give the empty string as a pattern in a ZCML ``route`` declaration: .. code-block:: xml :linenos: <route - path="" + pattern="" name="root" view=".views.root_view" /> -Or provide the literal string ``/`` as the path: +Or provide the literal string ``/`` as the pattern: .. code-block:: xml :linenos: <route - path="/" + pattern="/" name="root" view=".views.root_view" /> @@ -1039,9 +1042,9 @@ Generating Route URLs --------------------- Use the :func:`repoze.bfg.url.route_url` function to generate URLs -based on route paths. For example, if you've configured a route in -ZCML with the ``name`` "foo" and the ``path`` ":a/:b/:c", you might do -this. +based on route patterns. For example, if you've configured a route in +ZCML with the ``name`` "foo" and the ``pattern`` ":a/:b/:c", you might +do this. .. ignore-next-block .. code-block:: python @@ -1070,8 +1073,8 @@ For behavior like Django's ``APPEND_SLASH=True``, use the Found view (indicating that no view was found), and any routes have been defined in the configuration of your application, if the value of ``PATH_INFO`` does not already end in a slash, and if the value of -``PATH_INFO`` *plus* a slash matches any route's path, it does an HTTP -redirect to the slash-appended ``PATH_INFO``. +``PATH_INFO`` *plus* a slash matches any route's pattern, it does an +HTTP redirect to the slash-appended ``PATH_INFO``. Let's use an example, because this behavior is a bit magical. If the ``append_slash_notfound_view`` is configured in your application and @@ -1082,12 +1085,12 @@ your route configuration looks like so: <route view=".views.no_slash" - path="no_slash" + pattern="no_slash" /> <route view=".views.has_slash" - path="has_slash/" + pattern="has_slash/" /> If a request enters the application with the ``PATH_INFO`` value of diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 4d60f8a9e..5b55e7e12 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -1240,7 +1240,7 @@ Predicate Arguments declaration (see :ref:`urldispatch_chapter`) that must match before this view will be called. Note that the ``route`` configuration referred to by ``route_name`` usually has a ``*traverse`` token in - the value of its ``path``, representing a part of the path that will + the value of its ``pattern``, representing a part of the path that will be used by :term:`traversal` against the result of the route's :term:`root factory`. diff --git a/docs/tutorials/bfgwiki2/basiclayout.rst b/docs/tutorials/bfgwiki2/basiclayout.rst index a284b2eb4..e09316fab 100644 --- a/docs/tutorials/bfgwiki2/basiclayout.rst +++ b/docs/tutorials/bfgwiki2/basiclayout.rst @@ -35,7 +35,7 @@ following: #. *Lines 6-11*. Register a ``<route>`` :term:`route configuration` that will be used when the URL is ``/``. Since this ``<route>`` - has an empty ``path`` attribute, it is the "default" route. The + has an empty ``pattern`` attribute, it is the "default" route. The attribute named ``view`` with the value ``.views.my_view`` is the dotted name to a *function* we write (generated by the ``bfg_routesalchemy`` template) that is given a ``request`` object diff --git a/docs/tutorials/bfgwiki2/definingviews.rst b/docs/tutorials/bfgwiki2/definingviews.rst index 775334b51..c33795883 100644 --- a/docs/tutorials/bfgwiki2/definingviews.rst +++ b/docs/tutorials/bfgwiki2/definingviews.rst @@ -22,12 +22,13 @@ callable is assumed to return a :term:`response` object. The request passed to every view that is called as the result of a route match has an attribute named ``matchdict`` that contains the -elements placed into the URL by the ``path`` of a ``route`` statement. -For instance, if a route statement in ``configure.zcml`` had the path -``:one/:two``, and the URL at ``http://example.com/foo/bar`` was -invoked, matching this path, the matchdict dictionary attached to the -request passed to the view would have a ``one`` key with the value -``foo`` and a ``two`` key with the value ``bar``. +elements placed into the URL by the ``pattern`` of a ``route`` +statement. For instance, if a route statement in ``configure.zcml`` +had the pattern ``:one/:two``, and the URL at +``http://example.com/foo/bar`` was invoked, matching this pattern, the +matchdict dictionary attached to the request passed to the view would +have a ``one`` key with the value ``foo`` and a ``two`` key with the +value ``bar``. The source code for this tutorial stage can be browsed at `docs.repoze.org <http://docs.repoze.org/bfgwiki2-1.3/views>`_. @@ -275,21 +276,21 @@ Note that the *ordering* of these declarations is very important. ``route`` declarations are matched in the order they're found in the ``configure.zcml`` file. -#. Add a declaration which maps the empty path (signifying the root +#. Add a declaration which maps the empty pattern (signifying the root URL) to the view named ``view_wiki`` in our ``views.py`` file with the name ``view_wiki``. This is the :term:`default view` for the wiki. -#. Add a declaration which maps the path pattern ``:pagename`` to the +#. Add a declaration which maps the pattern ``:pagename`` to the view named ``view_page`` in our ``views.py`` file with the view name ``view_page``. This is the regular view for a page. -#. Add a declaration which maps the path pattern +#. Add a declaration which maps the pattern ``:pagename/edit_page`` to the view named ``edit_page`` in our ``views.py`` file with the name ``edit_page``. This is the edit view for a page. -#. Add a declaration which maps the path pattern +#. Add a declaration which maps the pattern ``add_page/:pagename`` to the view named ``add_page`` in our ``views.py`` file with the name ``add_page``. This is the add view for a new page. diff --git a/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml index e51a67d70..213573d7a 100644 --- a/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml +++ b/docs/tutorials/bfgwiki2/src/authorization/tutorial/configure.zcml @@ -4,38 +4,38 @@ <include package="repoze.bfg.includes" /> <static + pattern="templates/static" name="static" - path="templates/static" /> <route - path="login" + pattern="login" name="login" view=".login.login" view_renderer="templates/login.pt" /> <route - path="logout" + pattern="logout" name="logout" view=".login.logout" /> <route - path="" + pattern="" name="view_wiki" view=".views.view_wiki" /> <route - path=":pagename" + pattern=":pagename" name="view_page" view=".views.view_page" view_renderer="templates/view.pt" /> <route - path="add_page/:pagename" + pattern="add_page/:pagename" name="add_page" view=".views.add_page" view_renderer="templates/edit.pt" @@ -43,7 +43,7 @@ /> <route - path=":pagename/edit_page" + pattern=":pagename/edit_page" name="edit_page" view=".views.edit_page" view_renderer="templates/edit.pt" diff --git a/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml index 143f58a45..f04eec9b4 100644 --- a/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml +++ b/docs/tutorials/bfgwiki2/src/basiclayout/tutorial/configure.zcml @@ -3,15 +3,16 @@ <!-- this must be included for the view declarations to work --> <include package="repoze.bfg.includes" /> - <route path="" + <route + pattern="" name="home" view=".views.my_view" view_renderer="templates/mytemplate.pt" /> <static + pattern="templates/static" name="static" - path="templates/static" /> </configure> diff --git a/docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml index 143f58a45..f04eec9b4 100644 --- a/docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml +++ b/docs/tutorials/bfgwiki2/src/models/tutorial/configure.zcml @@ -3,15 +3,16 @@ <!-- this must be included for the view declarations to work --> <include package="repoze.bfg.includes" /> - <route path="" + <route + pattern="" name="home" view=".views.my_view" view_renderer="templates/mytemplate.pt" /> <static + pattern="templates/static" name="static" - path="templates/static" /> </configure> diff --git a/docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml b/docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml index 7a8576a56..f86468186 100644 --- a/docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml +++ b/docs/tutorials/bfgwiki2/src/views/tutorial/configure.zcml @@ -4,32 +4,32 @@ <include package="repoze.bfg.includes" /> <static + pattern="templates/static" name="static" - path="templates/static" /> <route - path="" + pattern="" name="view_wiki" view=".views.view_wiki" /> <route - path=":pagename" + pattern=":pagename" name="view_page" view=".views.view_page" view_renderer="templates/view.pt" /> <route - path="add_page/:pagename" + pattern="add_page/:pagename" name="add_page" view=".views.add_page" view_renderer="templates/edit.pt" /> <route - path=":pagename/edit_page" + pattern=":pagename/edit_page" name="edit_page" view=".views.edit_page" view_renderer="templates/edit.pt" diff --git a/docs/zcml/route.rst b/docs/zcml/route.rst index b4a318043..4290ea3ae 100644 --- a/docs/zcml/route.rst +++ b/docs/zcml/route.rst @@ -9,10 +9,14 @@ the :term:`application registry`. Attributes ~~~~~~~~~~ -``path`` - The path of the route e.g. ``ideas/:idea``. This attribute is - required. See :ref:`route_path_pattern_syntax` for information - about the syntax of route paths. +``pattern`` + The pattern of the route e.g. ``ideas/:idea``. This attribute is + required. See :ref:`route_pattern_syntax` for information + about the syntax of route patterns. + + .. note:: For backwards compatibility purposes, the ``path`` + attribute can also be used instead of ``pattern``. ``pattern`` + is the preferred spelling as of :mod:`repoze.bfg` 1.3. ``name`` The name of the route, e.g. ``myroute``. This attribute is @@ -49,25 +53,26 @@ Attributes the object returned by the ``factory`` associated with this route). The syntax of the ``traverse`` argument is the same as it is for - ``path``. For example, if the ``path`` provided to the ``route`` - directive is ``articles/:article/edit``, and the ``traverse`` - argument provided to the ``route`` directive is ``/:article``, when - a request comes in that causes the route to match in such a way that - the ``article`` match value is '1' (when the request URI is - ``/articles/1/edit``), the traversal path will be generated as - ``/1``. This means that the root object's ``__getitem__`` will be - called with the name ``1`` during the traversal phase. If the ``1`` - object exists, it will become the :term:`context` of the request. - :ref:`traversal_chapter` has more information about traversal. + ``pattern``. For example, if the ``pattern`` provided to the + ``route`` directive is ``articles/:article/edit``, and the + ``traverse`` argument provided to the ``route`` directive is + ``/:article``, when a request comes in that causes the route to + match in such a way that the ``article`` match value is '1' (when + the request URI is ``/articles/1/edit``), the traversal path will be + generated as ``/1``. This means that the root object's + ``__getitem__`` will be called with the name ``1`` during the + traversal phase. If the ``1`` object exists, it will become the + :term:`context` of the request. :ref:`traversal_chapter` has more + information about traversal. If the traversal path contains segment marker names which are not - present in the path argument, a runtime error will occur. The - ``traverse`` pattern should not contain segment markers that do not - exist in the ``path``. + present in the ``pattern`` argument, a runtime error will occur. + The ``traverse`` pattern should not contain segment markers that do + not exist in the ``pattern``. A similar combining of routing and traversal is available when a route is matched which contains a ``*traverse`` remainder marker in - its path (see :ref:`using_traverse_in_a_route_path`). The + its ``pattern`` (see :ref:`using_traverse_in_a_route_pattern`). The ``traverse`` argument to the ``route`` directive allows you to associate route patterns with an arbitrary traversal path without using a a ``*traverse`` remainder marker; instead you can use other @@ -75,7 +80,7 @@ Attributes Note that the ``traverse`` argument to the ``route`` directive is ignored when attached to a route that has a ``*traverse`` remainder - marker in its path. + marker in its pattern. .. note:: This feature is new as of :mod:`repoze.bfg` 1.3. diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index 488761ec4..962ba8f25 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -1005,7 +1005,7 @@ class Configurator(object): def add_route(self, name, - path, + pattern=None, view=None, view_for=None, permission=None, @@ -1025,6 +1025,7 @@ class Configurator(object): view_context=None, view_attr=None, use_global_views=False, + path=None, _info=u''): """ Add a :term:`route configuration` to the current configuration state, as well as possibly a :term:`view @@ -1064,7 +1065,7 @@ class Configurator(object): route). The syntax of the ``traverse`` argument is the same as it is - for ``path``. For example, if the ``path`` provided to + for ``pattern``. For example, if the ``pattern`` provided to ``add_route`` is ``articles/:article/edit``, and the ``traverse`` argument provided to ``add_route`` is ``/:article``, when a request comes in that causes the route @@ -1078,14 +1079,15 @@ class Configurator(object): traversal. If the traversal path contains segment marker names which - are not present in the path argument, a runtime error will - occur. The ``traverse`` pattern should not contain segment - markers that do not exist in the ``path``. + are not present in the ``pattern`` argument, a runtime error + will occur. The ``traverse`` pattern should not contain + segment markers that do not exist in the ``pattern`` + argument. A similar combining of routing and traversal is available when a route is matched which contains a ``*traverse`` - remainder marker in its path (see - :ref:`using_traverse_in_a_route_path`). The ``traverse`` + remainder marker in its pattern (see + :ref:`using_traverse_in_a_route_pattern`). The ``traverse`` argument to add_route allows you to associate route patterns with an arbitrary traversal path without using a a ``*traverse`` remainder marker; instead you can use other @@ -1093,18 +1095,26 @@ class Configurator(object): Note that the ``traverse`` argument to ``add_route`` is ignored when attached to a route that has a ``*traverse`` - remainder marker in its path. + remainder marker in its pattern. .. note:: This feature is new as of :mod:`repoze.bfg` 1.3. Predicate Arguments - path + pattern - The path of the route e.g. ``ideas/:idea``. This argument - is required. See :ref:`route_path_pattern_syntax` for - information about the syntax of route paths. If the path - doesn't match the current URL, route matching continues. + The pattern of the route e.g. ``ideas/:idea``. This + argument is required. See :ref:`route_path_pattern_syntax` + for information about the syntax of route patterns. If the + pattern doesn't match the current URL, route matching + continues. + + .. note:: For backwards compatibility purposes (as of + :mod:`repoze.bfg` 1.3), a ``path`` keyword argument + passed to this function will be used to represent the + pattern value if the ``pattern`` argument is ``None``. + If both ``path`` and ``pattern`` are passed, ``pattern`` + wins. xhr @@ -1333,7 +1343,11 @@ class Configurator(object): mapper = RoutesMapper() self.registry.registerUtility(mapper, IRoutesMapper) factory = self.maybe_dotted(factory) - return mapper.connect(path, name, factory, predicates=predicates) + if pattern is None: + pattern = path + if pattern is None: + raise ConfigurationError('"pattern" argument may not be None') + return mapper.connect(name, pattern, factory, predicates=predicates) def scan(self, package=None, categories=None, _info=u''): """ Scan a Python package and any of its subpackages for diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 86e2206e3..7082846e4 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -234,12 +234,48 @@ class IDebugLogger(Interface): ILogger = IDebugLogger # b/c +class IRoute(Interface): + """ Interface representing the type of object returned from + ``IRoutesMapper.get_route``""" + name = Attribute('The route name') + pattern = Attribute('The route pattern') + factory = Attribute( + 'The :term:`root factory` used by the :mod:`repoze.bfg` router ' + 'when this route matches (or ``None``)') + predicates = Attribute( + 'A sequence of :term:`route predicate` objects used to ' + 'determine if a request matches this route or not or not after ' + 'basic pattern matching has been completed.') + def match(path): + """ + If the ``path`` passed to this function can be matched by the + ``pattern`` of this route, return a dictionary (the + 'matchdict'), which will contain keys representing the dynamic + segment markers in the pattern mapped to values extracted from + the provided ``path``. + + If the ``path`` passed to this function cannot be matched by + the ``pattern`` of this route, return ``None``. + """ + def generate(kw): + """ + Generate a URL based on filling in the dynamic segment markers + in the pattern using the ``kw`` dictionary provided. + """ + class IRoutesMapper(Interface): """ Interface representing a Routes ``Mapper`` object """ def get_routes(): """ Return a sequence of Route objects registered in the mapper.""" - def connect(path, name, factory=None, predicates=()): + def has_routes(): + """ Returns ``True`` if any route has been registered. """ + + def get_route(name): + """ Returns an ``IRoute`` object if a route with the name ``name`` + was registered, otherwise return ``None``.""" + + def connect(name, pattern, factory=None, predicates=()): """ Add a new route. """ def generate(name, kw): @@ -247,8 +283,11 @@ class IRoutesMapper(Interface): keywords implied by kw""" def __call__(request): - """ Return a matchdict for the request; the ``route`` key will - either be a Route object or ``None`` if no route matched.""" + """ Return a dictionary containing matching information for + the request; the ``route`` key of this dictionary will either + be a Route object or ``None`` if no route matched; the + ``match``key will be the matchdict or ``None`` if no route + matched.""" class IContextURL(Interface): """ An adapter which deals with URLs related to a context. diff --git a/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml b/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml index 1a8d3e0bf..6d16bd089 100644 --- a/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml +++ b/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml @@ -4,7 +4,7 @@ <include package="repoze.bfg.includes" /> <route - path="" + pattern="" name="home" view=".views.my_view" view_renderer="templates/mytemplate.pt" diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py index 5502cb3d2..c99ba8706 100644 --- a/repoze/bfg/testing.py +++ b/repoze/bfg/testing.py @@ -290,12 +290,12 @@ def registerSubscriber(subscriber, iface=Interface): config = Configurator(registry) return config.add_subscriber(subscriber, iface=iface) -def registerRoute(path, name, factory=None): - """ Register a new :term:`route` using a path +def registerRoute(pattern, name, factory=None): + """ Register a new :term:`route` using a pattern (e.g. ``:pagename``), a name (e.g. ``home``), and an optional root factory. - The ``path`` argument implies the route path. The ``name`` + The ``pattern`` argument implies the route pattern. The ``name`` argument implies the route name. The ``factory`` argument implies a :term:`root factory` associated with the route. @@ -311,7 +311,7 @@ def registerRoute(path, name, factory=None): """ reg = get_current_registry() config = Configurator(registry=reg) - return config.add_route(name, path, factory=factory) + return config.add_route(name, pattern, factory=factory) def registerRoutesMapper(root_factory=None): """ Register a routes 'mapper' object. diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index 1ee2c1018..fc01e3ed4 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -1936,6 +1936,17 @@ class ConfiguratorTests(unittest.TestCase): self._assertRoute(config, 'name', 'path') self.failUnless(hasattr(wrapper, '__call_permissive__')) + def test_add_route_no_pattern_with_path(self): + config = self._makeOne() + route = config.add_route('name', path='path') + self._assertRoute(config, 'name', 'path') + self.assertEqual(route.name, 'name') + + def test_add_route_no_path_no_pattern(self): + from repoze.bfg.exceptions import ConfigurationError + config = self._makeOne() + self.assertRaises(ConfigurationError, config.add_route, 'name') + def test__override_not_yet_registered(self): from repoze.bfg.interfaces import IPackageOverrides package = DummyPackage('package') diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 1ce499e41..c243d739e 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -18,14 +18,14 @@ class TestRouter(unittest.TestCase): self.registry.registerUtility(iface, IRouteRequest, name=name) return iface - def _connectRoute(self, path, name, factory=None): + def _connectRoute(self, name, path, factory=None): from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.urldispatch import RoutesMapper mapper = self.registry.queryUtility(IRoutesMapper) if mapper is None: mapper = RoutesMapper() self.registry.registerUtility(mapper, IRoutesMapper) - mapper.connect(path, name, factory) + mapper.connect(name, path, factory) def _registerLogger(self): from repoze.bfg.interfaces import IDebugLogger @@ -444,7 +444,7 @@ class TestRouter(unittest.TestCase): root = object() def factory(request): return root - self._connectRoute('archives/:action/:article', 'foo', factory) + self._connectRoute('foo', 'archives/:action/:article', factory) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -485,7 +485,7 @@ class TestRouter(unittest.TestCase): root = object() def factory(request): return root - self._connectRoute('archives/:action/:article', 'foo', factory) + self._connectRoute('foo', 'archives/:action/:article', factory) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -727,7 +727,7 @@ class TestRouter(unittest.TestCase): from repoze.bfg.interfaces import IViewClassifier from repoze.bfg.interfaces import IExceptionViewClassifier req_iface = self._registerRouteRequest('foo') - self._connectRoute('archives/:action/:article', 'foo', None) + self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() @@ -746,7 +746,7 @@ class TestRouter(unittest.TestCase): from repoze.bfg.interfaces import IExceptionViewClassifier from repoze.bfg.interfaces import IRequest req_iface = self._registerRouteRequest('foo') - self._connectRoute('archives/:action/:article', 'foo', None) + self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, IRequest, None) response = DummyResponse() @@ -764,7 +764,7 @@ class TestRouter(unittest.TestCase): from repoze.bfg.interfaces import IExceptionViewClassifier from repoze.bfg.interfaces import IRequest req_iface = self._registerRouteRequest('foo') - self._connectRoute('archives/:action/:article', 'foo', None) + self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() @@ -787,7 +787,7 @@ class TestRouter(unittest.TestCase): class SubException(SuperException): pass req_iface = self._registerRouteRequest('foo') - self._connectRoute('archives/:action/:article', 'foo', None) + self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=SuperException) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() @@ -809,7 +809,7 @@ class TestRouter(unittest.TestCase): class SubException(SuperException): pass req_iface = self._registerRouteRequest('foo') - self._connectRoute('archives/:action/:article', 'foo', None) + self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=SubException) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() @@ -832,7 +832,7 @@ class TestRouter(unittest.TestCase): class AnotherException(Exception): pass req_iface = self._registerRouteRequest('foo') - self._connectRoute('archives/:action/:article', 'foo', None) + self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=MyException) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() @@ -850,7 +850,7 @@ class TestRouter(unittest.TestCase): from repoze.bfg.interfaces import IExceptionViewClassifier from repoze.bfg.interfaces import IRequest req_iface = self._registerRouteRequest('foo') - self._connectRoute('archives/:action/:article', 'foo', None) + self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() @@ -874,7 +874,7 @@ class TestRouter(unittest.TestCase): from repoze.bfg.interfaces import IExceptionViewClassifier req_iface = self._registerRouteRequest('foo') another_req_iface = self._registerRouteRequest('bar') - self._connectRoute('archives/:action/:article', 'foo', None) + self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py index f51bf7e87..bf43750ea 100644 --- a/repoze/bfg/tests/test_urldispatch.py +++ b/repoze/bfg/tests/test_urldispatch.py @@ -9,9 +9,15 @@ class TestRoute(unittest.TestCase): def _makeOne(self, *arg): return self._getTargetClass()(*arg) + def test_provides_IRoute(self): + from repoze.bfg.interfaces import IRoute + from zope.interface.verify import verifyObject + verifyObject(IRoute, self._makeOne('name', 'pattern')) + def test_ctor(self): import types - route = self._makeOne(':path', 'name', 'factory') + route = self._makeOne('name', ':path', 'factory') + self.assertEqual(route.pattern, ':path') self.assertEqual(route.path, ':path') self.assertEqual(route.name, 'name') self.assertEqual(route.factory, 'factory') @@ -20,19 +26,20 @@ class TestRoute(unittest.TestCase): def test_ctor_defaults(self): import types - route = self._makeOne(':path') + route = self._makeOne('name', ':path') + self.assertEqual(route.pattern, ':path') self.assertEqual(route.path, ':path') - self.assertEqual(route.name, None) + self.assertEqual(route.name, 'name') self.assertEqual(route.factory, None) self.failUnless(route.generate.__class__ is types.FunctionType) self.failUnless(route.match.__class__ is types.FunctionType) def test_match(self): - route = self._makeOne(':path') + route = self._makeOne('name', ':path') self.assertEqual(route.match('/whatever'), {'path':'whatever'}) def test_generate(self): - route = self._makeOne(':path') + route = self._makeOne('name', ':path') self.assertEqual(route.generate({'path':'abc'}), '/abc') class RoutesMapperTests(unittest.TestCase): @@ -60,6 +67,11 @@ class RoutesMapperTests(unittest.TestCase): klass = self._getTargetClass() return klass() + def test_provides_IRoutesMapper(self): + from repoze.bfg.interfaces import IRoutesMapper + from zope.interface.verify import verifyObject + verifyObject(IRoutesMapper, self._makeOne()) + def test_no_route_matches(self): mapper = self._makeOne() request = self._getRequest(PATH_INFO='/') @@ -69,18 +81,18 @@ class RoutesMapperTests(unittest.TestCase): def test_connect_name_exists_removes_old(self): mapper = self._makeOne() - mapper.connect('archives/:action/:article', 'foo') - mapper.connect('archives/:action/:article2', 'foo') + mapper.connect('foo', 'archives/:action/:article') + mapper.connect('foo', 'archives/:action/:article2') self.assertEqual(len(mapper.routelist), 1) self.assertEqual(len(mapper.routes), 1) - self.assertEqual(mapper.routes['foo'].path, + self.assertEqual(mapper.routes['foo'].pattern, 'archives/:action/:article2') - self.assertEqual(mapper.routelist[0].path, + self.assertEqual(mapper.routelist[0].pattern, 'archives/:action/:article2') def test___call__route_matches(self): mapper = self._makeOne() - mapper.connect('archives/:action/:article', 'foo') + mapper.connect('foo', 'archives/:action/:article') request = self._getRequest(PATH_INFO='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['foo']) @@ -89,7 +101,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__route_matches_with_predicates(self): mapper = self._makeOne() - mapper.connect('archives/:action/:article', 'foo', + mapper.connect('foo', 'archives/:action/:article', predicates=[lambda *arg: True]) request = self._getRequest(PATH_INFO='/archives/action1/article1') result = mapper(request) @@ -99,9 +111,9 @@ class RoutesMapperTests(unittest.TestCase): def test___call__route_fails_to_match_with_predicates(self): mapper = self._makeOne() - mapper.connect('archives/:action/article1', 'foo', + mapper.connect('foo', 'archives/:action/article1', predicates=[lambda *arg: True, lambda *arg: False]) - mapper.connect('archives/:action/:article', 'bar') + mapper.connect('bar', 'archives/:action/:article') request = self._getRequest(PATH_INFO='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['bar']) @@ -114,7 +126,7 @@ class RoutesMapperTests(unittest.TestCase): self.assertEqual(info['match'], {'action':u'action1'}) self.assertEqual(info['route'], mapper.routes['foo']) return True - mapper.connect('archives/:action/article1', 'foo', predicates=[pred]) + mapper.connect('foo', 'archives/:action/article1', predicates=[pred]) request = self._getRequest(PATH_INFO='/archives/action1/article1') mapper(request) @@ -122,9 +134,9 @@ class RoutesMapperTests(unittest.TestCase): # "unordered" as reported in IRC by author of # http://labs.creativecommons.org/2010/01/13/cc-engine-and-web-non-frameworks/ mapper = self._makeOne() - mapper.connect('licenses/:license_code/:license_version/rdf', 'rdf') - mapper.connect('licenses/:license_code/:license_version/:jurisdiction', - 'juri') + mapper.connect('rdf', 'licenses/:license_code/:license_version/rdf') + mapper.connect('juri', + 'licenses/:license_code/:license_version/:jurisdiction') request = self._getRequest(PATH_INFO='/licenses/1/v2/rdf') result = mapper(request) @@ -141,7 +153,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_matches(self): mapper = self._makeOne() - mapper.connect('', 'root') + mapper.connect('root', '') request = self._getRequest(PATH_INFO='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) @@ -149,7 +161,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_matches2(self): mapper = self._makeOne() - mapper.connect('/', 'root') + mapper.connect('root', '/') request = self._getRequest(PATH_INFO='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) @@ -157,7 +169,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_when_path_info_empty(self): mapper = self._makeOne() - mapper.connect('/', 'root') + mapper.connect('root', '/') request = self._getRequest(PATH_INFO='') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) @@ -165,7 +177,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__no_path_info(self): mapper = self._makeOne() - mapper.connect('/', 'root') + mapper.connect('root', '/') request = self._getRequest() result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) @@ -186,6 +198,17 @@ class RoutesMapperTests(unittest.TestCase): self.assertEqual(len(routes), 1) self.assertEqual(routes[0].__class__, Route) + def test_get_route_matches(self): + mapper = self._makeOne() + mapper.connect('whatever', 'archives/:action/:article') + result = mapper.get_route('whatever') + self.assertEqual(result.pattern, 'archives/:action/:article') + + def test_get_route_misses(self): + mapper = self._makeOne() + result = mapper.get_route('whatever') + self.assertEqual(result, None) + def test_generate(self): mapper = self._makeOne() def generator(kw): @@ -195,9 +218,9 @@ class RoutesMapperTests(unittest.TestCase): self.assertEqual(mapper.generate('abc', {}), 123) class TestCompileRoute(unittest.TestCase): - def _callFUT(self, path): + def _callFUT(self, pattern): from repoze.bfg.urldispatch import _compile_route - return _compile_route(path) + return _compile_route(pattern) def test_no_star(self): matcher, generator = self._callFUT('/foo/:baz/biz/:buz/bar') diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index a01b52840..4cd7f88d3 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -514,7 +514,7 @@ class TestRouteDirective(unittest.TestCase): from repoze.bfg.zcml import route return route(*arg, **kw) - def _assertRoute(self, name, path, num_predicates=0): + def _assertRoute(self, name, pattern, num_predicates=0): from repoze.bfg.threadlocal import get_current_registry from repoze.bfg.interfaces import IRoutesMapper reg = get_current_registry() @@ -523,7 +523,7 @@ class TestRouteDirective(unittest.TestCase): route = routes[0] self.assertEqual(len(routes), 1) self.assertEqual(route.name, name) - self.assertEqual(route.path, path) + self.assertEqual(route.pattern, pattern) self.assertEqual(len(routes[0].predicates), num_predicates) return route @@ -535,7 +535,7 @@ class TestRouteDirective(unittest.TestCase): from repoze.bfg.interfaces import IRouteRequest context = DummyContext() view = lambda *arg: 'OK' - self._callFUT(context, 'name', 'path', view=view) + self._callFUT(context, 'name', 'pattern', view=view) actions = context.actions self.assertEqual(len(actions), 2) @@ -544,7 +544,7 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] self.assertEqual(route_discriminator, ('route', 'name', False, None, None, None, None,None)) - self._assertRoute('name', 'path') + self._assertRoute('name', 'pattern') view_action = actions[1] reg = get_current_registry() @@ -563,7 +563,8 @@ class TestRouteDirective(unittest.TestCase): from repoze.bfg.interfaces import IRouteRequest context = DummyContext() view = lambda *arg: 'OK' - self._callFUT(context, 'name', 'path', view=view, view_context=IDummy) + self._callFUT(context, 'name', 'pattern', view=view, + view_context=IDummy) actions = context.actions self.assertEqual(len(actions), 2) @@ -572,7 +573,7 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] self.assertEqual(route_discriminator, ('route', 'name', False, None, None, None, None,None)) - self._assertRoute('name', 'path') + self._assertRoute('name', 'pattern') view_action = actions[1] reg = get_current_registry() @@ -593,8 +594,8 @@ class TestRouteDirective(unittest.TestCase): view = lambda *arg: 'OK' class Foo: pass - self._callFUT(context, 'name', 'path', view=view, view_context=IDummy, - view_for=Foo) + self._callFUT(context, 'name', 'pattern', view=view, + view_context=IDummy, view_for=Foo) actions = context.actions self.assertEqual(len(actions), 2) @@ -603,7 +604,7 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] self.assertEqual(route_discriminator, ('route', 'name', False, None, None, None, None,None)) - self._assertRoute('name', 'path') + self._assertRoute('name', 'pattern') view_action = actions[1] reg = get_current_registry() @@ -630,7 +631,7 @@ class TestRouteDirective(unittest.TestCase): context = DummyContext() view = lambda *arg: 'OK' - self._callFUT(context, 'name', 'path', view=view, + self._callFUT(context, 'name', 'pattern', view=view, renderer='fixtureapp/templates/foo.pt') actions = context.actions self.assertEqual(len(actions), 2) @@ -640,7 +641,7 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] self.assertEqual(route_discriminator, ('route', 'name', False, None, None, None, None,None)) - self._assertRoute('name', 'path') + self._assertRoute('name', 'pattern') view_action = actions[1] request_type = reg.getUtility(IRouteRequest, 'name') @@ -660,7 +661,8 @@ class TestRouteDirective(unittest.TestCase): preds = tuple(sorted([pred1, pred2])) context = DummyContext() - self._callFUT(context, 'name', 'path', custom_predicates=(pred1, pred2)) + self._callFUT(context, 'name', 'pattern', + custom_predicates=(pred1, pred2)) actions = context.actions self.assertEqual(len(actions), 1) @@ -670,7 +672,39 @@ class TestRouteDirective(unittest.TestCase): self.assertEqual( route_discriminator, ('route', 'name', False, None, None, None, None,None) + preds) - self._assertRoute('name', 'path', 2) + self._assertRoute('name', 'pattern', 2) + + def test_with_path_argument_no_pattern(self): + context = DummyContext() + self._callFUT(context, 'name', path='pattern') + actions = context.actions + self.assertEqual(len(actions), 1) + + route_action = actions[0] + route_action['callable']() + route_discriminator = route_action['discriminator'] + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None,None)) + self._assertRoute('name', 'pattern') + + def test_with_path_argument_and_pattern(self): + context = DummyContext() + self._callFUT(context, 'name', pattern='pattern', path='path') + actions = context.actions + self.assertEqual(len(actions), 1) + + route_action = actions[0] + route_action['callable']() + route_discriminator = route_action['discriminator'] + self.assertEqual(route_discriminator, + ('route', 'name', False, None, None, None, None,None)) + self._assertRoute('name', 'pattern') + + + def test_with_neither_path_nor_pattern(self): + from repoze.bfg.exceptions import ConfigurationError + context = DummyContext() + self.assertRaises(ConfigurationError, self._callFUT, context, 'name') class TestStaticDirective(unittest.TestCase): def setUp(self): @@ -706,7 +740,7 @@ class TestStaticDirective(unittest.TestCase): mapper = reg.getUtility(IRoutesMapper) routes = mapper.get_routes() self.assertEqual(len(routes), 1) - self.assertEqual(routes[0].path, 'name/*subpath') + self.assertEqual(routes[0].pattern, 'name/*subpath') self.assertEqual(routes[0].name, 'name/') view_action = actions[1] diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py index a07f902f6..f9def62df 100644 --- a/repoze/bfg/urldispatch.py +++ b/repoze/bfg/urldispatch.py @@ -1,5 +1,9 @@ import re from urllib import unquote +from zope.interface import implements + +from repoze.bfg.interfaces import IRoutesMapper +from repoze.bfg.interfaces import IRoute from repoze.bfg.compat import all from repoze.bfg.encode import url_quote @@ -7,17 +11,21 @@ from repoze.bfg.exceptions import URLDecodeError from repoze.bfg.traversal import traversal_path from repoze.bfg.traversal import quote_path_segment + _marker = object() class Route(object): - def __init__(self, path, name=None, factory=None, predicates=()): - self.path = path - self.match, self.generate = _compile_route(path) + implements(IRoute) + def __init__(self, name, pattern, factory=None, predicates=()): + self.pattern = pattern + self.path = pattern # indefinite b/w compat, not in interface + self.match, self.generate = _compile_route(pattern) self.name = name self.factory = factory self.predicates = predicates class RoutesMapper(object): + implements(IRoutesMapper) def __init__(self): self.routelist = [] self.routes = {} @@ -28,11 +36,14 @@ class RoutesMapper(object): def get_routes(self): return self.routelist - def connect(self, path, name, factory=None, predicates=()): + def get_route(self, name): + return self.routes.get(name) + + def connect(self, name, pattern, factory=None, predicates=()): if name in self.routes: oldroute = self.routes[name] self.routelist.remove(oldroute) - route = Route(path, name, factory, predicates) + route = Route(name, pattern, factory, predicates) self.routelist.append(route) self.routes[name] = route return route diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index 820dc9442..5320554bc 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -221,7 +221,9 @@ class IRouteDirective(Interface): """ The interface for the ``route`` ZCML directive """ name = TextLine(title=u'name', required=True) - path = TextLine(title=u'path', required=True) + pattern = TextLine(title=u'pattern', required=False) + # alias for pattern + path = TextLine(title=u'path', required=False) factory = GlobalObject(title=u'context factory', required=False) view = GlobalObject(title=u'view', required=False) @@ -263,7 +265,7 @@ class IRouteDirective(Interface): def route(_context, name, - path, + pattern=None, view=None, view_for=None, permission=None, @@ -282,7 +284,8 @@ def route(_context, view_renderer=None, view_context=None, traverse=None, - use_global_views=False): + use_global_views=False, + path=None): """ Handle ``route`` ZCML directives """ # the strange ordering of the request kw args above is for b/w @@ -300,11 +303,17 @@ def route(_context, if view_renderer and '.' in view_renderer: view_renderer = path_spec(_context, view_renderer) + if pattern is None: + pattern = path + + if pattern is None: + raise ConfigurationError('route directive must include a "pattern"') + def register(): config = Configurator(reg, package=_context.package) config.add_route( name, - path, + pattern, factory=factory, header=header, xhr=xhr, |
