summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bangert <ben@groovie.org>2010-02-28 16:24:15 -0800
committerBen Bangert <ben@groovie.org>2010-02-28 16:24:15 -0800
commite9b9c275df3c11931c7b1b17be03a982b43519d3 (patch)
tree9dd78938b87a335c70fca7b889b5b5288952685e
parent93adcc44b9de85012f3c79360c949f5a8fa69c1d (diff)
downloadroutes-e9b9c275df3c11931c7b1b17be03a982b43519d3.tar.gz
Major re-org of the docs
--HG-- branch : trunk
-rw-r--r--.hgignore2
-rw-r--r--CHANGELOG1
-rw-r--r--docs/conf.py6
-rw-r--r--docs/contents.rst12
-rw-r--r--docs/generating.rst208
-rw-r--r--docs/introduction.rst58
-rw-r--r--docs/manual.rst1054
-rw-r--r--docs/modules.rst27
-rw-r--r--docs/modules/index.rst15
-rw-r--r--docs/modules/lru.rst9
-rw-r--r--docs/modules/mapper.rst16
-rw-r--r--docs/modules/middleware.rst12
-rw-r--r--docs/modules/route.rst11
-rw-r--r--docs/modules/routes.rst12
-rw-r--r--docs/modules/util.rst20
-rw-r--r--docs/restful.rst211
-rw-r--r--docs/setting_up.rst357
-rw-r--r--docs/uni_redirect_rest.rst212
-rw-r--r--routes/__init__.py4
-rw-r--r--routes/mapper.py7
-rw-r--r--routes/threadinglocal.py36
21 files changed, 1158 insertions, 1132 deletions
diff --git a/.hgignore b/.hgignore
index 66f46d3..baf3acd 100644
--- a/.hgignore
+++ b/.hgignore
@@ -9,4 +9,6 @@ syntax:glob
build
dist
docs/_build
+*.xml
+html_coverage
.hgignore
diff --git a/CHANGELOG b/CHANGELOG
index 154bbdd..c1e5b45 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,7 @@ Routes Changelog
Release 1.12 (**tip**)
======================
+* Split up the Routes docs.
* Fix bug with relative URL's using qualified merging host and URL without
including the appropriate slash. Fixes #13.
* Fix bug with mapper.extend and Routes modifying their original args.
diff --git a/docs/conf.py b/docs/conf.py
index e652254..d40135c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -36,15 +36,15 @@ master_doc = 'contents'
# General substitutions.
project = 'Routes'
-copyright = '2009, Ben Bangert, Mike Orr'
+copyright = '2010, Ben Bangert, Mike Orr'
# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The short X.Y version.
-version = '1.11'
+version = '1.12'
# The full version, including alpha/beta/rc tags.
-release = '1.11'
+release = '1.12'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
diff --git a/docs/contents.rst b/docs/contents.rst
index 374c7dc..fdb999a 100644
--- a/docs/contents.rst
+++ b/docs/contents.rst
@@ -4,7 +4,11 @@ Routes Documentation
.. toctree::
:maxdepth: 2
- manual
+ introduction
+ setting_up
+ generating
+ restful
+ uni_redirect_rest
.. toctree::
:maxdepth: 1
@@ -25,8 +29,6 @@ Module Listing
--------------
.. toctree::
- :maxdepth: 3
+ :maxdepth: 2
- modules
-
-
+ modules/index
diff --git a/docs/generating.rst b/docs/generating.rst
new file mode 100644
index 0000000..cebad5d
--- /dev/null
+++ b/docs/generating.rst
@@ -0,0 +1,208 @@
+Generation
+==========
+
+To generate URLs, use the ``url`` or ``url_for`` object provided by your
+framework. ``url`` is an instance of Routes ``URLGenerator``, while
+``url_for`` is the older ``routes.url_for()`` function. ``url_for`` is being
+phased out, so new applications should use ``url``.
+
+To generate a named route, specify the route name as a positional argument::
+
+ url("home") => "/"
+
+If the route contains path variables, you must specify values for them using
+keyword arguments::
+
+ url("blog", year=2008, month=10, day=2)
+
+Non-string values are automatically converted to strings using ``str()``.
+(This may break with Unicode values containing non-ASCII characters.)
+
+However, if the route defines an extra variable with the same name as a path
+variable, the extra variable is used as the default if that keyword is not
+specified. Example::
+
+ m.connect("archives", "/archives/{id}",
+ controller="archives", action="view", id=1)
+ url("blog", id=123) => "/blog/123"
+ url("blog") => "/blog/1"
+
+(The extra variable is *not* used for matching unless minimization is enabled.)
+
+Any keyword args that do not correspond to path variables will be put in the
+query string. Append a "_" if the variable name collides with a Python
+keyword::
+
+ map.connect("archive", "/archive/{year}")
+ url("archive", year=2009, font=large) => "/archive/2009?font=large"
+ url("archive", year=2009, print_=1) => "/archive/2009?print=1"
+
+If the application is mounted at a subdirectory of the URL space,
+all generated URLs will have the application prefix. The application prefix is
+the "SCRIPT_NAME" variable in the request's WSGI environment.
+
+If the positional argument corresponds to no named route, it is assumed to be a
+literal URL. The application's mount point is prefixed to it, and keyword args
+are converted to query parameters::
+
+ url("/search", q="My question") => "/search?q=My+question"
+
+If there is no positional argument, Routes will use the keyword args to choose
+a route. The first route that has all path variables specified by keyword args
+and the fewest number of extra variables not overridden by keyword args will be
+chosen. This was common in older versions of Routes but can cause application
+bugs if an unexpected route is chosen, so using route names is much preferable
+because that guarantees only the named route will be chosen. The most common
+use for unnamed generation is when you have a seldom-used controller with a lot
+of ad hoc methods; e.g., ``url(controller="admin", action="session")``.
+
+An exception is raised if no route corresponds to the arguments. The exception
+is ``routes.util.GenerationException``. (Prior to Routes 1.9, ``None`` was
+returned instead. It was changed to an exception to prevent invalid blank URLs
+from being insered into templates.)
+
+You'll also get this exception if Python produces a Unicode URL (which could
+happen if the route path or a variable value is Unicode). Routes generates
+only ``str`` URLs.
+
+The following keyword args are special:
+
+ anchor
+
+ Specifies the URL anchor (the part to the right of "#"). ::
+
+ url("home", "summary") => "/#summary"
+
+ host
+
+ Make the URL fully qualified and override the host (domain).
+
+ protocol
+
+ Make the URL fully qualified and override the protocol (e.g., "ftp").
+
+ qualified
+
+ Make the URL fully qualified (i.e., add "protocol://host:port" prefix).
+
+ sub_domain
+
+ See "Generating URLs with subdomains" below.
+
+The syntax in this section is the same for both ``url`` and ``url_for``.
+
+*New in Routes 1.10: ``url`` and the ``URLGenerator`` class behind it.*
+
+Generating routes based on the current URL
+------------------------------------------
+
+``url.current()`` returns the URL of the current request, without the query
+string. This is called "route memory", and works only if the RoutesMiddleware
+is in the middleware stack. Keyword arguments override path variables or are
+put on the query string.
+
+``url_for`` combines the behavior of ``url`` and ``url_current``. This is
+deprecated because nameless routes and route memory have the same syntax, which
+can lead to the wrong route being chosen in some cases.
+
+Here's an example of route memory::
+
+ m.connect("/archives/{year}/{month}/{day}", year=2004)
+
+ # Current URL is "/archives/2005/10/4".
+ # Routing variables are {"controller": "archives", "action": "view",
+ "year": "2005", "month": "10", "day": "4"}
+
+ url.current(day=6) => "/archives/2005/10/6"
+ url.current(month=4) => "/archives/2005/4/4"
+ url.current() => "/archives/2005/10/4"
+
+Route memory can be disabled globally with ``map.explicit = True``.
+
+Generation-only routes (aka. static routes)
+-------------------------------------------
+
+A static route is used only for generation -- not matching -- and it must be
+named. To define a static route, use the argument ``_static=True``.
+
+This example provides a convenient way to link to a search::
+
+ map.connect("google", "http://google.com/", _static=True)
+ url("google", q="search term") => "/http://google.com/?q=search+term")
+
+This example generates a URL to a static image in a Pylons public directory.
+Pylons serves the public directory in a way that bypasses Routes, so there's no
+reason to match URLs under it. ::
+
+ map.connect("attachment", "/images/attachments/{category}/{id}.jpg",
+ _static=True)
+ url("attachment", category="dogs", id="Mastiff") =>
+ "/images/attachments/dogs/Mastiff.jpg"
+
+Starting in Routes 1.10, static routes are exactly the same as regular routes
+except they're not added to the internal match table. In previous versions of
+Routes they could not contain path variables and they had to point to external
+URLs.
+
+Filter functions
+----------------
+
+A filter function modifies how a named route is generated. Don't confuse it
+with a function condition, which is used in matching. A filter function is its
+opposite counterpart.
+
+One use case is when you have a ``story`` object with attributes for year,
+month, and day. You don't want to hardcode these attributes in every ``url``
+call because the interface may change someday. Instead you pass the story as a
+pseudo-argument, and the filter produces the actual generation args. Here's an
+example::
+
+ class Story(object):
+ def __init__(self, year, month, day):
+ self.year = year
+ self.month = month
+ self.day = day
+
+ @staticmethod
+ def expand(kw):
+ try:
+ story = kw["story"]
+ except KeyError:
+ pass # Don't modify dict if ``story`` key not present.
+ else:
+ # Set the actual generation args from the story.
+ kw["year"] = story.year
+ kw["month"] = story.month
+ kw["day"] = story.day
+ return kw
+
+ m.connect("archives", "/archives/{year}/{month}/{day}",
+ controller="archives", action="view", _filter=Story.expand)
+
+ my_story = Story(2009, 1, 2)
+ url("archives", story=my_story) => "/archives/2009/1/2"
+
+The ``_filter`` argument can be any function that takes a dict and returns a
+dict. In the example we've used a static method of the ``Story`` class to keep
+everything story-related together, but you may prefer to use a standalone
+function to keep Routes-related code away from your model.
+
+Generating URLs with subdomains
+-------------------------------
+
+If subdomain support is enabled and the ``sub_domain`` arg is passed to
+``url_for``, Routes ensures the generated route points to that subdomain. ::
+
+ # Enable subdomain support.
+ map.sub_domains = True
+
+ # Ignore the www subdomain.
+ map.sub_domains_ignore = "www"
+
+ map.connect("/users/{action}")
+
+ # Add a subdomain.
+ url_for(action="update", sub_domain="fred") => "http://fred.example.com/users/update"
+
+ # Delete a subdomain. Assume current URL is fred.example.com.
+ url_for(action="new", sub_domain=None) => "http://example.com/users/new"
diff --git a/docs/introduction.rst b/docs/introduction.rst
new file mode 100644
index 0000000..8555c35
--- /dev/null
+++ b/docs/introduction.rst
@@ -0,0 +1,58 @@
+Introduction
+============
+
+.. image:: routes-logo.png
+ :width: 100px
+ :height: 171px
+ :align: left
+
+Routes tackles an interesting problem that comes up frequently in web
+development, *how do you map URLs to your application's actions*? That is, how
+do you say that *this* should be accessed as "/blog/2008/01/08", and "/login"
+should do *that*? Many web frameworks have a fixed dispatching system; e.g.,
+"/A/B/C" means to read file "C" in directory "B", or to call method "C" of
+class "B" in module "A.B". These work fine until you need to refactor your code
+and realize that moving a method changes its public URL and invalidates users'
+bookmarks. Likewise, if you want to reorganize your URLs and make a section
+into a subsection, you have to change your carefully-tested logic code.
+
+Routes takes a different approach. You determine your URL hierarchy and and
+actions separately, and then link them together in whichever ways you decide.
+If you change your mind about a particular URL, just change one line in your
+route map and never touch your action logic. You can even have multiple URLs
+pointing to the same action; e.g., to support legacy bookmarks. Routes was
+originally inspired by the dispatcher in Ruby on Rails but has since diverged.
+
+Routes is the primary dispatching system in the Pylons web framework, and an
+optional choice in CherryPy. It can be added to any
+framework without much fuss, and used for an entire site or a URL subtree.
+It can also forward subtrees to other dispatching systems, which is how
+TurboGears 2 is implemented on top of Pylons.
+
+Current features:
+
+* Sophisticated route lookup and URL generation
+* Named routes
+* Redirect routes
+* Wildcard paths before and after static parts
+* Sub-domain support built-in
+* Conditional matching based on domain, cookies, HTTP method (RESTful), and more
+* Easily extensible utilizing custom condition functions and route generation
+ functions
+* Extensive unit tests
+
+Buzzword compliance: REST, DRY.
+
+If you're new to Routes or have not read the Routes 1.11 manual before, we
+recommend reading the `Glossary <glossary.html>`_ before continuing.
+
+This manual is written from the user's perspective: how to use Routes in a
+framework that already supports it. The `Porting <porting.html>`_
+manual describes how to add Routes support to a new framework.
+
+You may have heard about a development version called "Routes 2". Routes 2 is
+now called "Routes-experimental". It was originally intended to be a
+refactoring with a new API. Instead its features are being incorporated into
+Routes 1 in a compatible manner. There may be another Routes 2 in the future
+that drops deprecated features, but it's too early to say when/if that might
+happen.
diff --git a/docs/manual.rst b/docs/manual.rst
deleted file mode 100644
index f5ffcd2..0000000
--- a/docs/manual.rst
+++ /dev/null
@@ -1,1054 +0,0 @@
-Routes Manual
-%%%%%%%%%%%%%
-
-*Updated 2009-09-04 for Routes 1.11*
-
-Introduction
-============
-
-.. image:: routes-logo.png
- :width: 100px
- :height: 171px
- :align: left
-
-Routes tackles an interesting problem that comes up frequently in web
-development, *how do you map URLs to your application's actions*? That is, how
-do you say that *this* should be accessed as "/blog/2008/01/08", and "/login"
-should do *that*? Many web frameworks have a fixed dispatching system; e.g.,
-"/A/B/C" means to read file "C" in directory "B", or to call method "C" of
-class "B" in module "A.B". These work fine until you need to refactor your code
-and realize that moving a method changes its public URL and invalidates users'
-bookmarks. Likewise, if you want to reorganize your URLs and make a section
-into a subsection, you have to change your carefully-tested logic code.
-
-Routes takes a different approach. You determine your URL hierarchy and and
-actions separately, and then link them together in whichever ways you decide.
-If you change your mind about a particular URL, just change one line in your
-route map and never touch your action logic. You can even have multiple URLs
-pointing to the same action; e.g., to support legacy bookmarks. Routes was
-originally inspired by the dispatcher in Ruby on Rails but has since diverged.
-
-Routes is the primary dispatching system in the Pylons web framework, and an
-optional choice in CherryPy. It can be added to any
-framework without much fuss, and used for an entire site or a URL subtree.
-It can also forward subtrees to other dispatching systems, which is how
-TurboGears 2 is implemented on top of Pylons.
-
-Current features:
-
-* Sophisticated route lookup and URL generation
-* Named routes
-* Redirect routes
-* Wildcard paths before and after static parts
-* Sub-domain support built-in
-* Conditional matching based on domain, cookies, HTTP method (RESTful), and more
-* Easily extensible utilizing custom condition functions and route generation
- functions
-* Extensive unit tests
-
-Buzzword compliance: REST, DRY.
-
-If you're new to Routes or have not read the Routes 1.11 manual before, we
-recommend reading the `Glossary <glossary.html>`_ before continuing.
-
-This manual is written from the user's perspective: how to use Routes in a
-framework that already supports it. The `Porting <porting.html>`_
-manual describes how to add Routes support to a new framework.
-
-You may have heard about a development version called "Routes 2". Routes 2 is
-now called "Routes-experimental". It was originally intended to be a
-refactoring with a new API. Instead its features are being incorporated into
-Routes 1 in a compatible manner. There may be another Routes 2 in the future
-that drops deprecated features, but it's too early to say when/if that might
-happen.
-
-Setting up routes
-=================
-
-It is assumed that you are using a framework that has preconfigured Routes for
-you. In Pylons, you define your routes in the ``make_map`` function in your
-*myapp/config/routing.py* module. Here is a typical configuration::
-
- 1 from routes import Mapper
- 2 map = Mapper()
- 3 map.connect(None, "/error/{action}/{id}, controller="error")
- 4 map.connect("home", "/", controller="main", action="index")
- 5 # ADD CUSTOM ROUTES HERE
- 6 map.connect(None, "/{controller}/{action}")
- 7 map.connect(None, "/{controller}/{action}/{id}")
-
-Lines 1 and 2 create a mapper.
-
-Line 3 matches any three-component route that starts with "/error", and sets
-the "controller" variable to a constant, so that a URL
-"/error/images/arrow.jpg" would produce::
-
- {"controller": "error", "action": "images", "id": "arrow.jpg"}
-
-Line 4 matches the single URL "/", and sets both the controller and action to
-constants. It also has a route name "home", which can be used in generation.
-(The other routes have ``None`` instead of a name, so they don't have names.
-It's recommended to name all routes that may be used in generation, but it's
-not necessary to name other routes.)
-
-Line 6 matches any two-component URL, and line 7 matches any 3-component URL.
-These are used as catchall routes if we're too lazy to define a separate route
-for every action. If you *have* defined a route for every action, you can
-delete these two routes.
-
-Note that a URL "/error/images/arrow.jpg" could match both line 3 and line 7.
-The mapper resolves this by trying routes in the order defined, so this URL
-would match line 3.
-
-If no routes match the URL, the mapper returns a "match failed" condition,
-which is seen in Pylons as HTTP 404 "Not Found".
-
-Here are some more examples of valid routes::
-
- m.connect("/feeds/{category}/atom.xml", controller="feeds", action="atom")
- m.connect("history", "/archives/by_eon/{century}", controller="archives",
- action="aggregate")
- m.connect("article", "/article/{section}/{slug}/{page}.html",
- controller="article", action="view")
-
-Extra variables may be any Python type, not just strings. However, if the
-route is used in generation, ``str()`` will be called on the value unless
-the generation call specifies an overriding value.
-
-Other argument syntaxes are allowed for compatibility with earlier versions of
-Routes. These are described in the ``Backward Compatibility`` section.
-
-Route paths should always begin with a slash ("/"). Earlier versions of
-Routes allowed slashless paths, but their behavior now is undefined.
-
-
-Requirements
-------------
-
-It's possible to restrict a path variable to a regular expression; e.g., to
-match only a numeric component or a restricted choice of words. There are two
-syntaxes for this: inline and the ``requirements`` argument. An inline
-requirement looks like this::
-
- map.connect(R"/blog/{id:\d+}")
- map.connect(R"/download/{platform:windows|mac}/{filename}")
-
-This matches "/blog/123" but not "/blog/12A". The equivalent ``requirements``
-syntax is::
-
- map.connect("/blog/{id}", requirements={"id": R"\d+"}
- map.connect("/download/{platform}/{filename}",
- requirements={"platform": R"windows|mac"})
-
-Note the use of raw string syntax (``R""``) for regexes which might contain
-backslashes. Without the R you'd have to double every backslash.
-
-Another example::
-
- m.connect("archives/{year}/{month}/{day}", controller="archives",
- action="view", year=2004,
- requirements=dict(year=R"\d{2,4}", month=R"\d{1,2}"))
-
-The inline syntax was added in Routes (XXX 1.10?? not in changelog). Previous
-versions had only the ``requirements`` argument. Two advantages of the
-``requirements`` argument are that if you have several variables with identical
-requirements, you can set one variable or even the entire argument to a
-global::
-
- NUMERIC = R"\d+"
- map.connect(..., requirements={"id": NUMERIC})
-
- ARTICLE_REQS = {"year": R"\d\d\d\d", "month": R"\d\d", "day": R"\d\d"}
- map.connect(..., requirements=ARTICLE_REQS)
-
-Because the argument ``requirements`` is reserved, you can't define a routing
-variable by that name.
-
-Magic path_info
----------------
-
-If the "path_info" variable is used at the end of the URL, Routes moves
-everything preceding it into the "SCRIPT_NAME" environment variable. This is
-useful when delegating to another WSGI application that does its own routing:
-the subapplication will route on the remainder of the URL rather than the
-entire URL. You still
-need the ":.*" requirement to capture the following URL components into the
-variable. ::
-
- map.connect(None, "/cards/{path_info:.*}",
- controller="main", action="cards")
- # Incoming URL "/cards/diamonds/4.png"
- => {"controller": "main", action: "cards", "path_info": "/diamonds/4.png"}
- # Second WSGI application sees:
- # SCRIPT_NAME="/cards" PATH_INFO="/diamonds/4.png"
-
-This route does not match "/cards" because it requires a following slash.
-Add another route to get around this::
-
- map.connect("cards", "/cards", controller="main", action="cards",
- path_info="/")
-
-.. tip::
-
- You may think you can combine the two with the following route::
-
- map.connect("cards", "/cards{path_info:.*}",
- controller="main", action="cards")
-
- There are two problems with this, however. One, it would also match
- "/cardshark". Two, Routes 1.10 has a bug: it forgets to take
- the suffix off the SCRIPT_NAME.
-
-A future version of Routes may delegate directly to WSGI applications, but for
-now this must be done in the framework. In Pylons, you can do this in a
-controller action as follows::
-
- from paste.fileapp import DirectoryApp
- def cards(self, environ, start_response):
- app = DirectoryApp("/cards-directory")
- return app(environ, start_response)
-
-Or create a fake controller module with a ``__controller__`` variable set to
-the WSGI application::
-
- from paste.fileapp import DirectoryApp
- __controller__ = DirectoryApp("/cards-directory")
-
-Conditions
-----------
-
-Conditions impose additional constraints on what kinds of requests can match.
-The ``conditions`` argument is a dict with up to three keys:
-
- method
-
- A list of uppercase HTTP methods. The request must be one of the
- listed methods.
-
- sub_domain
-
- Can be a list of subdomains, ``True``, ``False``, or ``None``. If a
- list, the request must be for one of the specified subdomains. If
- ``True``, the request must contain a subdomain but it can be anything.
- If ``False`` or ``None``, do not match if there's a subdomain.
-
- *New in Routes 1.10: ``False`` and ``None`` values.*
-
- function
-
- A function that evaluates the request. Its signature must be
- ``func(environ, match_dict) => bool``. It should return true if the
- match is successful or false otherwise. The first arg is the WSGI
- environment; the second is the routing variables that would be
- returned if the match succeeds. The function can modify ``match_dict``
- in place to affect which variables are returned. This allows a wide
- range of transformations.
-
-Examples::
-
- # Match only if the HTTP method is "GET" or "HEAD".
- m.connect("/user/list", controller="user", action="list",
- conditions=dict(method=["GET", "HEAD"]))
-
- # A sub-domain should be present.
- m.connect("/", controller="user", action="home",
- conditions=dict(sub_domain=True))
-
- # Sub-domain should be either "fred" or "george".
- m.connect("/", controller="user", action="home",
- conditions=dict(sub_domain=["fred", "george"]))
-
- # Put the referrer into the resulting match dictionary.
- # This function always returns true, so it never prevents the match
- # from succeeding.
- def referals(environ, result):
- result["referer"] = environ.get("HTTP_REFERER")
- return True
- m.connect("/{controller}/{action}/{id}",
- conditions=dict(function=referals))
-
-Wildcard routes
----------------
-
-By default, path variables do not match a slash. This ensures that each
-variable will match exactly one component. You can use requirements to
-override this::
-
- map.connect("/static/{filename:.*?}")
-
-This matches "/static/foo.jpg", "/static/bar/foo.jpg", etc.
-
-Beware that careless regexes may eat the entire rest of the URL and cause
-components to the right of it not to match::
-
- # OK because the following component is static and the regex has a "?".
- map.connect("/static/{filename:.*?}/download")
-
-The lesson is to always test wildcard patterns.
-
-Format extensions
------------------
-
-A path component of ``{.format}`` will match an optional format extension (e.g.
-".html" or ".json"), setting the format variable to the part after the "."
-(e.g. "html" or "json") if there is one, or to ``None`` otherwise. For example::
-
- map.connect('/entries/{id}{.format}')
-
-will match "/entries/1" and "/entries/1.mp3". You can use requirements to
-limit which extensions will match, for example::
-
- map.connect('/entries/{id:\d+}{.format:json}')
-
-will match "/entries/1" and "/entries/1.json" but not "/entries/1.mp3".
-
-As with wildcard routes, it's important to understand and test this. Without
-the ``\d+`` requirement on the ``id`` variable above, "/entries/1.mp3" would match
-successfully, with the ``id`` variable capturing "1.mp3".
-
-*New in Routes 1.12.*
-
-Submappers
-----------
-
-A submapper lets you add several similar routes
-without having to repeat identical keyword arguments. There are two syntaxes,
-one using a Python ``with`` block, and the other avoiding it. ::
-
- # Using 'with'
- with map.submapper(controller="home") as m:
- m.connect("home", "/", action="splash")
- m.connect("index", "/index", action="index")
-
- # Not using 'with'
- m = map.submapper(controller="home")
- m.connect("home", "/", action="splash")
- m.connect("index", "/index", action="index")
-
- # Both of these syntaxes create the following routes::
- # "/" => {"controller": "home", action="splash"}
- # "/index" => {"controller": "home", action="index"}
-
-You can also specify a common path prefix for your routes::
-
- with map.submapper(path_prefix="/admin", controller="admin") as m:
- m.connect("admin_users", "/users", action="users")
- m.connect("admin_databases", "/databases", action="databases")
-
- # /admin/users => {"controller": "admin", "action": "users"}
- # /admin/databases => {"controller": "admin", "action": "databases"}
-
-All arguments to ``.submapper`` must be keyword arguments.
-
-The submapper is *not* a complete mapper. It's just a temporary object
-with a ``.connect`` method that adds routes to the mapper it was spawned
-from.
-
-*New in Routes 1.11.*
-
-Submapper helpers
------------------
-
-Submappers contain a number of helpers that further simplify routing
-configuration. This::
-
- with map.submapper(controller="home") as m:
- m.connect("home", "/", action="splash")
- m.connect("index", "/index", action="index")
-
-can be written::
-
- with map.submapper(controller="home", path_prefix="/") as m:
- m.action("home", action="splash")
- m.link("index")
-
-The ``action`` helper generates a route for one or more HTTP methods ('GET' is
-assumed) at the submapper's path ('/' in the example above). The ``link``
-helper generates a route at a relative path.
-
-There are specific helpers corresponding to the standard ``index``, ``new``,
-``create``, ``show``, ``edit``, ``update`` and ``delete`` actions.
-You can use these directly::
-
- with map.submapper(controller="entries", path_prefix="/entries") as entries:
- entries.index()
- with entries.submapper(path_prefix="/{id}") as entry:
- entry.show()
-
-or indirectly::
-
- with map.submapper(controller="entries", path_prefix="/entries",
- actions=["index"]) as entries:
- entries.submapper(path_prefix="/{id}", actions=["show"])
-
-Collection/member submappers nested in this way are common enough that there is
-helper for this too::
-
- map.collection(collection_name="entries", member_name="entry",
- controller="entries",
- collection_actions=["index"], member_actions["show"])
-
-This returns a submapper instance to which further routes may be added; it has
-a ``member`` property (a nested submapper) to which which member-specific routes
-can be added. When ``collection_actions`` or ``member_actions`` are omitted,
-the full set of actions is generated (see the example under "Printing" below).
-
-See "RESTful services" below for ``map.resource``, a precursor to
-``map.collection`` that does not use submappers.
-
-*New in Routes 1.12.*
-
-Adding routes from a nested application
----------------------------------------
-
-*New in Routes 1.11.* Sometimes in nested applications, the child application
-gives the parent a list of routes to add to its mapper. These can be added
-with the ``.extend`` method, optionally providing a path prefix::
-
- routes = [
- Route("index", "/index.html", controller="home", action="index"),
- ]
-
- map.extend(routes)
- # /index.html => {"controller": "home", "action": "index"}
-
- map.extend(routes, "/subapp")
- # /subapp/index.html => {"controller": "home", "action": "index"}
-
-This does not exactly add the route objects to the mapper. It creates
-identical new route objects and adds those to the mapper.
-
-*New in Routes 1.11.*
-
-
-Generation
-==========
-
-To generate URLs, use the ``url`` or ``url_for`` object provided by your
-framework. ``url`` is an instance of Routes ``URLGenerator``, while
-``url_for`` is the older ``routes.url_for()`` function. ``url_for`` is being
-phased out, so new applications should use ``url``.
-
-To generate a named route, specify the route name as a positional argument::
-
- url("home") => "/"
-
-If the route contains path variables, you must specify values for them using
-keyword arguments::
-
- url("blog", year=2008, month=10, day=2)
-
-Non-string values are automatically converted to strings using ``str()``.
-(This may break with Unicode values containing non-ASCII characters.)
-
-However, if the route defines an extra variable with the same name as a path
-variable, the extra variable is used as the default if that keyword is not
-specified. Example::
-
- m.connect("archives", "/archives/{id}",
- controller="archives", action="view", id=1)
- url("blog", id=123) => "/blog/123"
- url("blog") => "/blog/1"
-
-(The extra variable is *not* used for matching unless minimization is enabled.)
-
-Any keyword args that do not correspond to path variables will be put in the
-query string. Append a "_" if the variable name collides with a Python
-keyword::
-
- map.connect("archive", "/archive/{year}")
- url("archive", year=2009, font=large) => "/archive/2009?font=large"
- url("archive", year=2009, print_=1) => "/archive/2009?print=1"
-
-If the application is mounted at a subdirectory of the URL space,
-all generated URLs will have the application prefix. The application prefix is
-the "SCRIPT_NAME" variable in the request's WSGI environment.
-
-If the positional argument corresponds to no named route, it is assumed to be a
-literal URL. The application's mount point is prefixed to it, and keyword args
-are converted to query parameters::
-
- url("/search", q="My question") => "/search?q=My+question"
-
-If there is no positional argument, Routes will use the keyword args to choose
-a route. The first route that has all path variables specified by keyword args
-and the fewest number of extra variables not overridden by keyword args will be
-chosen. This was common in older versions of Routes but can cause application
-bugs if an unexpected route is chosen, so using route names is much preferable
-because that guarantees only the named route will be chosen. The most common
-use for unnamed generation is when you have a seldom-used controller with a lot
-of ad hoc methods; e.g., ``url(controller="admin", action="session")``.
-
-An exception is raised if no route corresponds to the arguments. The exception
-is ``routes.util.GenerationException``. (Prior to Routes 1.9, ``None`` was
-returned instead. It was changed to an exception to prevent invalid blank URLs
-from being insered into templates.)
-
-You'll also get this exception if Python produces a Unicode URL (which could
-happen if the route path or a variable value is Unicode). Routes generates
-only ``str`` URLs.
-
-The following keyword args are special:
-
- anchor
-
- Specifies the URL anchor (the part to the right of "#"). ::
-
- url("home", "summary") => "/#summary"
-
- host
-
- Make the URL fully qualified and override the host (domain).
-
- protocol
-
- Make the URL fully qualified and override the protocol (e.g., "ftp").
-
- qualified
-
- Make the URL fully qualified (i.e., add "protocol://host:port" prefix).
-
- sub_domain
-
- See "Generating URLs with subdomains" below.
-
-The syntax in this section is the same for both ``url`` and ``url_for``.
-
-*New in Routes 1.10: ``url`` and the ``URLGenerator`` class behind it.*
-
-Generating routes based on the current URL
-------------------------------------------
-
-``url.current()`` returns the URL of the current request, without the query
-string. This is called "route memory", and works only if the RoutesMiddleware
-is in the middleware stack. Keyword arguments override path variables or are
-put on the query string.
-
-``url_for`` combines the behavior of ``url`` and ``url_current``. This is
-deprecated because nameless routes and route memory have the same syntax, which
-can lead to the wrong route being chosen in some cases.
-
-Here's an example of route memory::
-
- m.connect("/archives/{year}/{month}/{day}", year=2004)
-
- # Current URL is "/archives/2005/10/4".
- # Routing variables are {"controller": "archives", "action": "view",
- "year": "2005", "month": "10", "day": "4"}
-
- url.current(day=6) => "/archives/2005/10/6"
- url.current(month=4) => "/archives/2005/4/4"
- url.current() => "/archives/2005/10/4"
-
-Route memory can be disabled globally with ``map.explicit = True``.
-
-Generation-only routes (aka. static routes)
--------------------------------------------
-
-A static route is used only for generation -- not matching -- and it must be
-named. To define a static route, use the argument ``_static=True``.
-
-This example provides a convenient way to link to a search::
-
- map.connect("google", "http://google.com/", _static=True)
- url("google", q="search term") => "/http://google.com/?q=search+term")
-
-This example generates a URL to a static image in a Pylons public directory.
-Pylons serves the public directory in a way that bypasses Routes, so there's no
-reason to match URLs under it. ::
-
- map.connect("attachment", "/images/attachments/{category}/{id}.jpg",
- _static=True)
- url("attachment", category="dogs", id="Mastiff") =>
- "/images/attachments/dogs/Mastiff.jpg"
-
-Starting in Routes 1.10, static routes are exactly the same as regular routes
-except they're not added to the internal match table. In previous versions of
-Routes they could not contain path variables and they had to point to external
-URLs.
-
-Filter functions
-----------------
-
-A filter function modifies how a named route is generated. Don't confuse it
-with a function condition, which is used in matching. A filter function is its
-opposite counterpart.
-
-One use case is when you have a ``story`` object with attributes for year,
-month, and day. You don't want to hardcode these attributes in every ``url``
-call because the interface may change someday. Instead you pass the story as a
-pseudo-argument, and the filter produces the actual generation args. Here's an
-example::
-
- class Story(object):
- def __init__(self, year, month, day):
- self.year = year
- self.month = month
- self.day = day
-
- @staticmethod
- def expand(kw):
- try:
- story = kw["story"]
- except KeyError:
- pass # Don't modify dict if ``story`` key not present.
- else:
- # Set the actual generation args from the story.
- kw["year"] = story.year
- kw["month"] = story.month
- kw["day"] = story.day
- return kw
-
- m.connect("archives", "/archives/{year}/{month}/{day}",
- controller="archives", action="view", _filter=Story.expand)
-
- my_story = Story(2009, 1, 2)
- url("archives", story=my_story) => "/archives/2009/1/2"
-
-The ``_filter`` argument can be any function that takes a dict and returns a
-dict. In the example we've used a static method of the ``Story`` class to keep
-everything story-related together, but you may prefer to use a standalone
-function to keep Routes-related code away from your model.
-
-Generating URLs with subdomains
--------------------------------
-
-If subdomain support is enabled and the ``sub_domain`` arg is passed to
-``url_for``, Routes ensures the generated route points to that subdomain. ::
-
- # Enable subdomain support.
- map.sub_domains = True
-
- # Ignore the www subdomain.
- map.sub_domains_ignore = "www"
-
- map.connect("/users/{action}")
-
- # Add a subdomain.
- url_for(action="update", sub_domain="fred") => "http://fred.example.com/users/update"
-
- # Delete a subdomain. Assume current URL is fred.example.com.
- url_for(action="new", sub_domain=None) => "http://example.com/users/new"
-
-Unicode
-=======
-
-Routes assumes UTF-8 encoding on incoming URLs, and ``url`` and ``url_for``
-also generate UTF-8. You can change the encoding with the ``map.charset``
-attribute::
-
- map.charset = "latin-1"
-
-New in Routes 1.10: several bugfixes.
-
-RESTful services
-================
-
-Routes makes it easy to configure RESTful web services. ``map.resource``
-creates a set of add/modify/delete routes conforming to the Atom publishing
-protocol.
-
-A resource route addresses *members* in a *collection*, and the collection
-itself. Normally a collection is a plural word, and a member is the
-corresponding singular word. For instance, consider a collection of messages::
-
- map.resource("message", "messages")
-
- # The above command sets up several routes as if you had typed the
- # following commands:
- map.connect("messages", "/messages",
- controller="messages", action="create",
- conditions=dict(method=["POST"]))
- map.connect("messages", "/messages",
- controller="messages", action="index",
- conditions=dict(method=["GET"]))
- map.connect("formatted_messages", "/messages.{format}",
- controller="messages", action="index",
- conditions=dict(method=["GET"]))
- map.connect("new_message", "/messages/new",
- controller="messages", action="new",
- conditions=dict(method=["GET"]))
- map.connect("formatted_new_message", "/messages/new.{format}",
- controller="messages", action="new",
- conditions=dict(method=["GET"]))
- map.connect("/messages/{id}",
- controller="messages", action="update",
- conditions=dict(method=["PUT"]))
- map.connect("/messages/{id}",
- controller="messages", action="delete",
- conditions=dict(method=["DELETE"]))
- map.connect("edit_message", "/messages/{id}/edit",
- controller="messages", action="edit",
- conditions=dict(method=["GET"]))
- map.connect("formatted_edit_message", "/messages/{id}.{format}/edit",
- controller="messages", action="edit",
- conditions=dict(method=["GET"]))
- map.connect("message", "/messages/{id}",
- controller="messages", action="show",
- conditions=dict(method=["GET"]))
- map.connect("formatted_message", "/messages/{id}.{format}",
- controller="messages", action="show",
- conditions=dict(method=["GET"]))
-
-This establishes the following convention::
-
- GET /messages => messages.index() => url("messages")
- POST /messages => messages.create() => url("messages")
- GET /messages/new => messages.new() => url("new_message")
- PUT /messages/1 => messages.update(id) => url("message", id=1)
- DELETE /messages/1 => messages.delete(id) => url("message", id=1)
- GET /messages/1 => messages.show(id) => url("message", id=1)
- GET /messages/1/edit => messages.edit(id) => url("edit_message", id=1)
-
-Thus, you GET the collection to see an index of links to members ("index"
-method). You GET a member to see it ("show"). You GET "COLLECTION/new" to
-obtain a new message form ("new"), which you POST to the collection ("create").
-You GET "MEMBER/edit" to obtain an edit for ("edit"), which you PUT to the
-member ("update"). You DELETE the member to delete it. Note that there are
-only four route names because multiple actions are doubled up on the same URLs.
-
-This URL structure may look strange if you're not used to the Atom protocol.
-REST is a vague term, and some people think it means proper URL syntax (every
-component contains the one on its right), others think it means not putting IDs
-in query parameters, and others think it means using HTTP methods beyond GET
-and POST. ``map.resource`` does all three, but it may be overkill for
-applications that don't need Atom compliance or prefer to stick with GET and
-POST. ``map.resource`` has the advantage that many automated tools and
-non-browser agents will be able to list and modify your resources without any
-programming on your part. But you don't have to use it if you prefer a simpler
-add/modify/delete structure.
-
-HTML forms can produce only GET and POST requests. As a workaround, if a POST
-request contains a ``_method`` parameter, the Routes middleware changes the
-HTTP method to whatever the parameter specifies, as if it had been requested
-that way in the first place. This convention is becoming increasingly common
-in other frameworks. If you're using WebHelpers, the The WebHelpers ``form``
-function has a ``method`` argument which automatically sets the HTTP method and
-"_method" parameter.
-
-Several routes are paired with an identical route containing the ``format``
-variable. The intention is to allow users to obtain different formats by means
-of filename suffixes; e.g., "/messages/1.xml". This produces a routing
-variable "xml", which in Pylons will be passed to the controller action if it
-defines a formal argument for it. In generation you can pass the ``format``
-argument to produce a URL with that suffix::
-
- url("message", id=1, format="xml") => "/messages/1.xml"
-
-Routes does not recognize any particular formats or know which ones are valid
-for your application. It merely passes the ``format`` attribute through if it
-appears.
-
-New in Routes 1.7.3: changed URL suffix from ";edit" to "/edit". Semicolons
-are not allowed in the path portion of a URL except to delimit path parameters,
-which nobody uses.
-
-Resource options
-----------------
-
-The ``map.resource`` method recognizes a number of keyword args which modifies
-its behavior:
-
-controller
-
- Use the specified controller rather than deducing it from the collection
- name.
-
-collection
-
- Additional URLs to allow for the collection. Example::
-
- map.resource("message", "messages", collection={"rss": "GET"})
- # "GET /message/rss" => ``Messages.rss()``.
- # Defines a named route "rss_messages".
-
-member
-
- Additional URLs to allow for a member. Example::
-
- map.resource('message', 'messages', member={'mark':'POST'})
- # "POST /message/1/mark" => ``Messages.mark(1)``
- # also adds named route "mark_message"
-
- This can be used to display a delete confirmation form::
-
- map.resource("message", "messages", member={"ask_delete": "GET"}
- # "GET /message/1/ask_delete" => ``Messages.ask_delete(1)``.
- # Also adds a named route "ask_delete_message".
-
-new
-
- Additional URLs to allow for new-member functionality. ::
-
- map.resource("message", "messages", new={"preview": "POST"})
- # "POST /messages/new/preview"
-
-path_prefix
-
- Prepend the specified prefix to all URL patterns. The prefix may include
- path variables. This is mainly used to nest resources within resources.
-
-name_prefix
-
- Prefix the specified string to all route names. This is most often
- combined with ``path_prefix`` to nest resources::
-
- map.resource("message", "messages", controller="categories",
- path_prefix="/category/{category_id}",
- name_prefix="category_")
- # GET /category/7/message/1
- # Adds named route "category_message"
-
-parent_resource
-
- A dict containing information about the parent resource, for creating a
- nested resource. It should contain the member_name and collection_name
- of the parent resource. This dict will be available via the associated
- Route object which can be accessed during a request via
- ``request.environ["routes.route"]``.
-
- If parent_resource is supplied and path_prefix isn't, path_prefix will
- be generated from parent_resource as "<parent collection name>/:<parent
- member name>_id".
-
- If parent_resource is supplied and name_prefix isn't, name_prefix will
- be generated from parent_resource as "<parent member name>_".
-
- Example::
-
- >>> m = Mapper()
- >>> m.resource('location', 'locations',
- ... parent_resource=dict(member_name='region',
- ... collection_name='regions'))
- >>> # path_prefix is "regions/:region_id"
- >>> # name prefix is "region_"
- >>> url('region_locations', region_id=13)
- '/regions/13/locations'
- >>> url('region_new_location', region_id=13)
- '/regions/13/locations/new'
- >>> url('region_location', region_id=13, id=60)
- '/regions/13/locations/60'
- >>> url('region_edit_location', region_id=13, id=60)
- '/regions/13/locations/60/edit'
-
- Overriding generated path_prefix:
-
- >>> m = Mapper()
- >>> m.resource('location', 'locations',
- ... parent_resource=dict(member_name='region',
- ... collection_name='regions'),
- ... path_prefix='areas/:area_id')
- >>> # name prefix is "region_"
- >>> url('region_locations', area_id=51)
- '/areas/51/locations'
-
- Overriding generated name_prefix:
-
- >>> m = Mapper()
- >>> m.resource('location', 'locations',
- ... parent_resource=dict(member_name='region',
- ... collection_name='regions'),
- ... name_prefix='')
- >>> # path_prefix is "regions/:region_id"
- >>> url('locations', region_id=51)
- '/regions/51/locations'
-
-
-Redirect routes
-===============
-
-Redirect routes allow you to specify redirects in the route map, similar to
-RewriteRule in an Apache configuration. This avoids the need to define dummy
-controller actions just to handle redirects. It's especially useful when the
-URL structure changes and you want to redirect legacy URLs to their new
-equivalents. The redirection is done by the Routes middleware, and the WSGI
-application is not called.
-
-``map.redirect`` takes two positional arguments: the route path and the
-destination URL. Redirect routes do not have a name. Both paths can contain
-variables, and the route path can take inline requirements. Keyword arguments
-are the same as ``map.connect``, both in regards to extra variables and to route
-options. ::
-
- map.redirect("/legacyapp/archives/{url:.*}", "/archives/{url}")
-
- map.redirect("/legacyapp/archives/{url:.*}", "/archives/{url}")
-
-By default a "302 Found" HTTP status is issued. You can override this with the
-``_redirect_code`` keyword argument. The value must be an entire status
-string. ::
-
- map.redirect("/home/index", "/", _redirect_code="301 Moved Permanently")
-
-*New in Routes 1.10.*
-
-Printing
-========
-
-Mappers now have a formatted string representation. In your python shell,
-simply print your application's mapper::
-
- >>> map.collection("entries", "entry")
- >>> print map
- Route name Methods Path
- entries GET /entries{.format}
- create_entry POST /entries{.format}
- new_entry GET /entries/new{.format}
- entry GET /entries/{id}{.format}
- update_entry PUT /entries/{id}{.format}
- delete_entry DELETE /entries/{id}{.format}
- edit_entry GET /entries/{id}/edit{.format}
-
-*New in Routes 1.12.*
-
-
-Introspection
-=============
-
-The mapper attribute ``.matchlist`` contains the list of routes to be matched
-against incoming URLs. You can iterate this list to see what routes are
-defined. This can be useful when debugging route configurations.
-
-
-
-Other
-=====
-
-If your application is behind an HTTP proxy such a load balancer on another
-host, the WSGI environment will refer to the internal server rather than to the
-proxy, which will mess up generated URLs. Use the ProxyMiddleware in
-PasteDeploy to fix the WSGI environment to what it would have been without the
-proxy.
-
-To debug routes, turn on debug logging for the "routes.middleware" logger.
-(See Python's ``logging`` module to set up your logging configuration.)
-
-Backward compatibility
-======================
-
-The following syntaxes are allowed for compatibility with previous versions
-of Routes. They may be removed in the future.
-
-Omitting the name arg
----------------------
-
-In the tutorial we said that nameless routes can be defined by passing ``None``
-as the first argument. You can also omit the first argument entirely::
-
- map.connect(None, "/{controller}/{action}")
- map.connect("/{controller}/{action}")
-
-The syntax with ``None`` is preferred to be forward-compatible with future
-versions of Routes. It avoids the path argument changing position between
-the first and second arguments, which is unpythonic.
-
-:varname
---------
-
-Path variables were defined in the format ``:varname`` and ``:(varname)``
-prior to Routes 1.9. The form with parentheses was called "grouping", used
-to delimit the variable name from a following letter or number. Thus the old
-syntax "/:controller/:(id)abc" corresponds to the new syntax
-"/{controller}/{id}abc".
-
-The older wildcard syntax is ``*varname`` or ``*(varname)``::
-
- # OK because the following component is static.
- map.connect("/static/*filename/download")
-
- # Deprecated syntax. WRONG because the wildcard will eat the rest of the
- # URL, leaving nothing for the following variable, which will cause the
- # match to fail.
- map.connect("/static/*filename/:action")
-
-
-Minimization
-------------
-
-Minimization was a misfeature which was intended to save typing, but which
-often resulted in the wrong route being chosen. Old applications that still
-depend on it must now enable it by putting ``map.minimization = True`` in
-their route definitions.
-
-Without minimization, the URL must contain values for all path variables in
-the route::
-
- map.connect("basic", "/{controller}/{action}",
- controller="mycontroller", action="myaction", weather="sunny")
-
-This route matches any two-component URL, for instance "/help/about". The
-resulting routing variables would be::
-
- {"controller": "help", "action": "about", "weather": "sunny"}
-
-The path variables are taken from the URL, and any extra variables are added as
-constants. The extra variables for "controller" and "action" are *never used*
-in matching, but are available as default values for generation::
-
- url("basic", controller="help") => "/help/about?weather=sunny"
-
-With minimization, the same route path would also match shorter URLs such as
-"/help", "/foo", and "/". Missing values on the right of the URL would be
-taken from the extra variables. This was intended to lessen the number of
-routes you had to write. In practice it led to obscure application bugs
-because sometimes an unexpected route would be matched. Thus Routes 1.9
-introduced non-minimization and recommended "map.minimization = False" for
-all new applications.
-
-A corollary problem was generating the wrong route. Routes 1.9 tightened up
-the rule for generating named routes. If a route name is specified in
-``url()`` or ``url_for()``, *only* that named route will be chosen. In
-previous versions, it might choose another route based on the keyword args.
-
-Implicit defaults and route memory
-----------------------------------
-
-Implicit defaults worked with minimization to provide automatic default values
-for the "action" and "id" variables. If a route was defined as
-``map.connect("/{controller}/{action}/{id}") and the URL "/archives"`` was
-requested, Routes would implicitly add ``action="index", id=None`` to the
-routing variables.
-
-To enable implicit defaults, set ``map.minimization = True; map.explicit =
-False``. You can also enable implicit defaults on a per-route basis by setting
-``map.explicit = True`` and defining each route with a keyword argument ``explicit=False``.
-
-Previous versions also had implicit default values for "controller",
-"action", and "id". These are now disabled by default, but can be enabled via
-``map.explicit = True``. This also enables route memory
-
-url_for()
----------
-
-``url_for`` was a route generation function which was replaced by the ``url``
-object. Usage is the same except that ``url_for`` uses route memory in some
-cases and ``url`` never does. Route memory is where variables from the current
-URL (the current request) are injected into the generated URL. To use route
-memory with ``url``, call ``url.current()`` passing the variables you want to
-override. Any other variables needed by the route will be taken from the
-current routing variables.
-
-In other words, ``url_for`` combines ``url`` and ``url.current()`` into one
-function. The location of ``url_for`` is also different. ``url_for`` is
-properly imported from ``routes``::
-
- from routes import url_for
-
-``url_for`` was traditionally imported into WebHelpers, and it's still used in
-some tests and in ``webhelpers.paginate``. Many old Pylons applications
-contain ``h.url_for()`` based on its traditional importation to helpers.py.
-However, its use in new applications is discouraged both because of its
-ambiguous syntax and because its implementation depends on an ugly singleton.
-
-The ``url`` object is created by the RoutesMiddleware and inserted into the
-WSGI environment. Pylons makes it available as ``pylons.url``, and in
-templates as ``url``.
-
-redirect_to()
--------------
-
-This combined ``url_for`` with a redirect. Instead, please use your
-framework's redirect mechanism with a ``url`` call. For instance in Pylons::
-
- from pylons.controllers.util import redirect
- redirect(url("login"))
diff --git a/docs/modules.rst b/docs/modules.rst
deleted file mode 100644
index 0ad616d..0000000
--- a/docs/modules.rst
+++ /dev/null
@@ -1,27 +0,0 @@
-:mod:`routes` -- Routes module
-================================================
-
-.. automodule:: routes
-
-Module Contents
----------------
-
-.. currentmodule:: routes
-
-.. autofunction:: request_config
-
-.. autoclass:: Mapper
- :members: connect, create_regs, generate, match, redirect, resource, routematch
-
-.. autoclass:: URLGenerator
-
-.. autofunction:: url_for
-
-.. autofunction:: redirect_to
-
-.. currentmodule:: routes.mapper
-.. autofunction:: strip_slashes
-
-.. currentmodule:: routes.middleware
-
-.. autoclass:: RoutesMiddleware
diff --git a/docs/modules/index.rst b/docs/modules/index.rst
new file mode 100644
index 0000000..d01338c
--- /dev/null
+++ b/docs/modules/index.rst
@@ -0,0 +1,15 @@
+.. _modules:
+
+==============
+Routes Modules
+==============
+
+.. toctree::
+ :maxdepth: 2
+
+ routes
+ mapper
+ route
+ middleware
+ lru
+ util
diff --git a/docs/modules/lru.rst b/docs/modules/lru.rst
new file mode 100644
index 0000000..c55f405
--- /dev/null
+++ b/docs/modules/lru.rst
@@ -0,0 +1,9 @@
+:mod:`routes.lru` -- LRU caching class and decorator
+====================================================
+
+.. automodule:: routes.lru
+
+Module Contents
+---------------
+
+.. autoclass:: LRUCache
diff --git a/docs/modules/mapper.rst b/docs/modules/mapper.rst
new file mode 100644
index 0000000..e968abe
--- /dev/null
+++ b/docs/modules/mapper.rst
@@ -0,0 +1,16 @@
+:mod:`routes.mapper` -- Mapper and Sub-Mapper
+=============================================
+
+.. automodule:: routes.mapper
+
+Module Contents
+---------------
+
+.. autoclass:: SubMapperParent
+ :members:
+ :undoc-members:
+.. autoclass:: SubMapper
+ :members:
+ :undoc-members:
+.. autoclass:: Mapper
+ :members:
diff --git a/docs/modules/middleware.rst b/docs/modules/middleware.rst
new file mode 100644
index 0000000..9b12e79
--- /dev/null
+++ b/docs/modules/middleware.rst
@@ -0,0 +1,12 @@
+:mod:`routes.middleware` -- Routes WSGI Middleware
+==================================================
+
+.. automodule:: routes.middleware
+
+Module Contents
+---------------
+
+.. autoclass:: RoutesMiddleware
+ :members:
+ :undoc-members:
+.. autofunction:: is_form_post
diff --git a/docs/modules/route.rst b/docs/modules/route.rst
new file mode 100644
index 0000000..6540ee4
--- /dev/null
+++ b/docs/modules/route.rst
@@ -0,0 +1,11 @@
+:mod:`routes.route` -- Route
+============================
+
+.. automodule:: routes.route
+
+Module Contents
+---------------
+
+.. autoclass:: Route
+ :members:
+ :undoc-members:
diff --git a/docs/modules/routes.rst b/docs/modules/routes.rst
new file mode 100644
index 0000000..3878628
--- /dev/null
+++ b/docs/modules/routes.rst
@@ -0,0 +1,12 @@
+:mod:`routes` -- Routes Common Classes and Functions
+====================================================
+
+.. automodule:: routes
+
+Module Contents
+---------------
+
+.. autofunction:: request_config
+.. autoclass:: _RequestConfig
+ :members:
+ :undoc-members:
diff --git a/docs/modules/util.rst b/docs/modules/util.rst
new file mode 100644
index 0000000..fc24a96
--- /dev/null
+++ b/docs/modules/util.rst
@@ -0,0 +1,20 @@
+:mod:`routes.util` -- URL Generator and utility functions
+=========================================================
+
+.. automodule:: routes.util
+
+Module Contents
+---------------
+
+.. autoexception:: RoutesException
+.. autoexception:: MatchException
+.. autoexception:: GenerationException
+.. autoclass:: URLGenerator
+ :members:
+ :undoc-members:
+.. autofunction:: url_for
+
+.. autofunction:: _url_quote
+.. autofunction:: _str_encode
+.. autofunction:: _screenargs
+.. autofunction:: _subdomain_check
diff --git a/docs/restful.rst b/docs/restful.rst
new file mode 100644
index 0000000..9ab8eee
--- /dev/null
+++ b/docs/restful.rst
@@ -0,0 +1,211 @@
+RESTful services
+================
+
+Routes makes it easy to configure RESTful web services. ``map.resource``
+creates a set of add/modify/delete routes conforming to the Atom publishing
+protocol.
+
+A resource route addresses *members* in a *collection*, and the collection
+itself. Normally a collection is a plural word, and a member is the
+corresponding singular word. For instance, consider a collection of messages::
+
+ map.resource("message", "messages")
+
+ # The above command sets up several routes as if you had typed the
+ # following commands:
+ map.connect("messages", "/messages",
+ controller="messages", action="create",
+ conditions=dict(method=["POST"]))
+ map.connect("messages", "/messages",
+ controller="messages", action="index",
+ conditions=dict(method=["GET"]))
+ map.connect("formatted_messages", "/messages.{format}",
+ controller="messages", action="index",
+ conditions=dict(method=["GET"]))
+ map.connect("new_message", "/messages/new",
+ controller="messages", action="new",
+ conditions=dict(method=["GET"]))
+ map.connect("formatted_new_message", "/messages/new.{format}",
+ controller="messages", action="new",
+ conditions=dict(method=["GET"]))
+ map.connect("/messages/{id}",
+ controller="messages", action="update",
+ conditions=dict(method=["PUT"]))
+ map.connect("/messages/{id}",
+ controller="messages", action="delete",
+ conditions=dict(method=["DELETE"]))
+ map.connect("edit_message", "/messages/{id}/edit",
+ controller="messages", action="edit",
+ conditions=dict(method=["GET"]))
+ map.connect("formatted_edit_message", "/messages/{id}.{format}/edit",
+ controller="messages", action="edit",
+ conditions=dict(method=["GET"]))
+ map.connect("message", "/messages/{id}",
+ controller="messages", action="show",
+ conditions=dict(method=["GET"]))
+ map.connect("formatted_message", "/messages/{id}.{format}",
+ controller="messages", action="show",
+ conditions=dict(method=["GET"]))
+
+This establishes the following convention::
+
+ GET /messages => messages.index() => url("messages")
+ POST /messages => messages.create() => url("messages")
+ GET /messages/new => messages.new() => url("new_message")
+ PUT /messages/1 => messages.update(id) => url("message", id=1)
+ DELETE /messages/1 => messages.delete(id) => url("message", id=1)
+ GET /messages/1 => messages.show(id) => url("message", id=1)
+ GET /messages/1/edit => messages.edit(id) => url("edit_message", id=1)
+
+Thus, you GET the collection to see an index of links to members ("index"
+method). You GET a member to see it ("show"). You GET "COLLECTION/new" to
+obtain a new message form ("new"), which you POST to the collection ("create").
+You GET "MEMBER/edit" to obtain an edit for ("edit"), which you PUT to the
+member ("update"). You DELETE the member to delete it. Note that there are
+only four route names because multiple actions are doubled up on the same URLs.
+
+This URL structure may look strange if you're not used to the Atom protocol.
+REST is a vague term, and some people think it means proper URL syntax (every
+component contains the one on its right), others think it means not putting IDs
+in query parameters, and others think it means using HTTP methods beyond GET
+and POST. ``map.resource`` does all three, but it may be overkill for
+applications that don't need Atom compliance or prefer to stick with GET and
+POST. ``map.resource`` has the advantage that many automated tools and
+non-browser agents will be able to list and modify your resources without any
+programming on your part. But you don't have to use it if you prefer a simpler
+add/modify/delete structure.
+
+HTML forms can produce only GET and POST requests. As a workaround, if a POST
+request contains a ``_method`` parameter, the Routes middleware changes the
+HTTP method to whatever the parameter specifies, as if it had been requested
+that way in the first place. This convention is becoming increasingly common
+in other frameworks. If you're using WebHelpers, the The WebHelpers ``form``
+function has a ``method`` argument which automatically sets the HTTP method and
+"_method" parameter.
+
+Several routes are paired with an identical route containing the ``format``
+variable. The intention is to allow users to obtain different formats by means
+of filename suffixes; e.g., "/messages/1.xml". This produces a routing
+variable "xml", which in Pylons will be passed to the controller action if it
+defines a formal argument for it. In generation you can pass the ``format``
+argument to produce a URL with that suffix::
+
+ url("message", id=1, format="xml") => "/messages/1.xml"
+
+Routes does not recognize any particular formats or know which ones are valid
+for your application. It merely passes the ``format`` attribute through if it
+appears.
+
+New in Routes 1.7.3: changed URL suffix from ";edit" to "/edit". Semicolons
+are not allowed in the path portion of a URL except to delimit path parameters,
+which nobody uses.
+
+Resource options
+----------------
+
+The ``map.resource`` method recognizes a number of keyword args which modifies
+its behavior:
+
+controller
+
+ Use the specified controller rather than deducing it from the collection
+ name.
+
+collection
+
+ Additional URLs to allow for the collection. Example::
+
+ map.resource("message", "messages", collection={"rss": "GET"})
+ # "GET /message/rss" => ``Messages.rss()``.
+ # Defines a named route "rss_messages".
+
+member
+
+ Additional URLs to allow for a member. Example::
+
+ map.resource('message', 'messages', member={'mark':'POST'})
+ # "POST /message/1/mark" => ``Messages.mark(1)``
+ # also adds named route "mark_message"
+
+ This can be used to display a delete confirmation form::
+
+ map.resource("message", "messages", member={"ask_delete": "GET"}
+ # "GET /message/1/ask_delete" => ``Messages.ask_delete(1)``.
+ # Also adds a named route "ask_delete_message".
+
+new
+
+ Additional URLs to allow for new-member functionality. ::
+
+ map.resource("message", "messages", new={"preview": "POST"})
+ # "POST /messages/new/preview"
+
+path_prefix
+
+ Prepend the specified prefix to all URL patterns. The prefix may include
+ path variables. This is mainly used to nest resources within resources.
+
+name_prefix
+
+ Prefix the specified string to all route names. This is most often
+ combined with ``path_prefix`` to nest resources::
+
+ map.resource("message", "messages", controller="categories",
+ path_prefix="/category/{category_id}",
+ name_prefix="category_")
+ # GET /category/7/message/1
+ # Adds named route "category_message"
+
+parent_resource
+
+ A dict containing information about the parent resource, for creating a
+ nested resource. It should contain the member_name and collection_name
+ of the parent resource. This dict will be available via the associated
+ Route object which can be accessed during a request via
+ ``request.environ["routes.route"]``.
+
+ If parent_resource is supplied and path_prefix isn't, path_prefix will
+ be generated from parent_resource as "<parent collection name>/:<parent
+ member name>_id".
+
+ If parent_resource is supplied and name_prefix isn't, name_prefix will
+ be generated from parent_resource as "<parent member name>_".
+
+ Example::
+
+ >>> m = Mapper()
+ >>> m.resource('location', 'locations',
+ ... parent_resource=dict(member_name='region',
+ ... collection_name='regions'))
+ >>> # path_prefix is "regions/:region_id"
+ >>> # name prefix is "region_"
+ >>> url('region_locations', region_id=13)
+ '/regions/13/locations'
+ >>> url('region_new_location', region_id=13)
+ '/regions/13/locations/new'
+ >>> url('region_location', region_id=13, id=60)
+ '/regions/13/locations/60'
+ >>> url('region_edit_location', region_id=13, id=60)
+ '/regions/13/locations/60/edit'
+
+ Overriding generated path_prefix:
+
+ >>> m = Mapper()
+ >>> m.resource('location', 'locations',
+ ... parent_resource=dict(member_name='region',
+ ... collection_name='regions'),
+ ... path_prefix='areas/:area_id')
+ >>> # name prefix is "region_"
+ >>> url('region_locations', area_id=51)
+ '/areas/51/locations'
+
+ Overriding generated name_prefix:
+
+ >>> m = Mapper()
+ >>> m.resource('location', 'locations',
+ ... parent_resource=dict(member_name='region',
+ ... collection_name='regions'),
+ ... name_prefix='')
+ >>> # path_prefix is "regions/:region_id"
+ >>> url('locations', region_id=51)
+ '/regions/51/locations'
diff --git a/docs/setting_up.rst b/docs/setting_up.rst
new file mode 100644
index 0000000..594e60a
--- /dev/null
+++ b/docs/setting_up.rst
@@ -0,0 +1,357 @@
+Setting up routes
+=================
+
+It is assumed that you are using a framework that has preconfigured Routes for
+you. In Pylons, you define your routes in the ``make_map`` function in your
+*myapp/config/routing.py* module. Here is a typical configuration::
+
+ 1 from routes import Mapper
+ 2 map = Mapper()
+ 3 map.connect(None, "/error/{action}/{id}, controller="error")
+ 4 map.connect("home", "/", controller="main", action="index")
+ 5 # ADD CUSTOM ROUTES HERE
+ 6 map.connect(None, "/{controller}/{action}")
+ 7 map.connect(None, "/{controller}/{action}/{id}")
+
+Lines 1 and 2 create a mapper.
+
+Line 3 matches any three-component route that starts with "/error", and sets
+the "controller" variable to a constant, so that a URL
+"/error/images/arrow.jpg" would produce::
+
+ {"controller": "error", "action": "images", "id": "arrow.jpg"}
+
+Line 4 matches the single URL "/", and sets both the controller and action to
+constants. It also has a route name "home", which can be used in generation.
+(The other routes have ``None`` instead of a name, so they don't have names.
+It's recommended to name all routes that may be used in generation, but it's
+not necessary to name other routes.)
+
+Line 6 matches any two-component URL, and line 7 matches any 3-component URL.
+These are used as catchall routes if we're too lazy to define a separate route
+for every action. If you *have* defined a route for every action, you can
+delete these two routes.
+
+Note that a URL "/error/images/arrow.jpg" could match both line 3 and line 7.
+The mapper resolves this by trying routes in the order defined, so this URL
+would match line 3.
+
+If no routes match the URL, the mapper returns a "match failed" condition,
+which is seen in Pylons as HTTP 404 "Not Found".
+
+Here are some more examples of valid routes::
+
+ m.connect("/feeds/{category}/atom.xml", controller="feeds", action="atom")
+ m.connect("history", "/archives/by_eon/{century}", controller="archives",
+ action="aggregate")
+ m.connect("article", "/article/{section}/{slug}/{page}.html",
+ controller="article", action="view")
+
+Extra variables may be any Python type, not just strings. However, if the
+route is used in generation, ``str()`` will be called on the value unless
+the generation call specifies an overriding value.
+
+Other argument syntaxes are allowed for compatibility with earlier versions of
+Routes. These are described in the ``Backward Compatibility`` section.
+
+Route paths should always begin with a slash ("/"). Earlier versions of
+Routes allowed slashless paths, but their behavior now is undefined.
+
+
+Requirements
+------------
+
+It's possible to restrict a path variable to a regular expression; e.g., to
+match only a numeric component or a restricted choice of words. There are two
+syntaxes for this: inline and the ``requirements`` argument. An inline
+requirement looks like this::
+
+ map.connect(R"/blog/{id:\d+}")
+ map.connect(R"/download/{platform:windows|mac}/{filename}")
+
+This matches "/blog/123" but not "/blog/12A". The equivalent ``requirements``
+syntax is::
+
+ map.connect("/blog/{id}", requirements={"id": R"\d+"}
+ map.connect("/download/{platform}/{filename}",
+ requirements={"platform": R"windows|mac"})
+
+Note the use of raw string syntax (``R""``) for regexes which might contain
+backslashes. Without the R you'd have to double every backslash.
+
+Another example::
+
+ m.connect("archives/{year}/{month}/{day}", controller="archives",
+ action="view", year=2004,
+ requirements=dict(year=R"\d{2,4}", month=R"\d{1,2}"))
+
+The inline syntax was added in Routes (XXX 1.10?? not in changelog). Previous
+versions had only the ``requirements`` argument. Two advantages of the
+``requirements`` argument are that if you have several variables with identical
+requirements, you can set one variable or even the entire argument to a
+global::
+
+ NUMERIC = R"\d+"
+ map.connect(..., requirements={"id": NUMERIC})
+
+ ARTICLE_REQS = {"year": R"\d\d\d\d", "month": R"\d\d", "day": R"\d\d"}
+ map.connect(..., requirements=ARTICLE_REQS)
+
+Because the argument ``requirements`` is reserved, you can't define a routing
+variable by that name.
+
+Magic path_info
+---------------
+
+If the "path_info" variable is used at the end of the URL, Routes moves
+everything preceding it into the "SCRIPT_NAME" environment variable. This is
+useful when delegating to another WSGI application that does its own routing:
+the subapplication will route on the remainder of the URL rather than the
+entire URL. You still
+need the ":.*" requirement to capture the following URL components into the
+variable. ::
+
+ map.connect(None, "/cards/{path_info:.*}",
+ controller="main", action="cards")
+ # Incoming URL "/cards/diamonds/4.png"
+ => {"controller": "main", action: "cards", "path_info": "/diamonds/4.png"}
+ # Second WSGI application sees:
+ # SCRIPT_NAME="/cards" PATH_INFO="/diamonds/4.png"
+
+This route does not match "/cards" because it requires a following slash.
+Add another route to get around this::
+
+ map.connect("cards", "/cards", controller="main", action="cards",
+ path_info="/")
+
+.. tip::
+
+ You may think you can combine the two with the following route::
+
+ map.connect("cards", "/cards{path_info:.*}",
+ controller="main", action="cards")
+
+ There are two problems with this, however. One, it would also match
+ "/cardshark". Two, Routes 1.10 has a bug: it forgets to take
+ the suffix off the SCRIPT_NAME.
+
+A future version of Routes may delegate directly to WSGI applications, but for
+now this must be done in the framework. In Pylons, you can do this in a
+controller action as follows::
+
+ from paste.fileapp import DirectoryApp
+ def cards(self, environ, start_response):
+ app = DirectoryApp("/cards-directory")
+ return app(environ, start_response)
+
+Or create a fake controller module with a ``__controller__`` variable set to
+the WSGI application::
+
+ from paste.fileapp import DirectoryApp
+ __controller__ = DirectoryApp("/cards-directory")
+
+Conditions
+----------
+
+Conditions impose additional constraints on what kinds of requests can match.
+The ``conditions`` argument is a dict with up to three keys:
+
+ method
+
+ A list of uppercase HTTP methods. The request must be one of the
+ listed methods.
+
+ sub_domain
+
+ Can be a list of subdomains, ``True``, ``False``, or ``None``. If a
+ list, the request must be for one of the specified subdomains. If
+ ``True``, the request must contain a subdomain but it can be anything.
+ If ``False`` or ``None``, do not match if there's a subdomain.
+
+ *New in Routes 1.10: ``False`` and ``None`` values.*
+
+ function
+
+ A function that evaluates the request. Its signature must be
+ ``func(environ, match_dict) => bool``. It should return true if the
+ match is successful or false otherwise. The first arg is the WSGI
+ environment; the second is the routing variables that would be
+ returned if the match succeeds. The function can modify ``match_dict``
+ in place to affect which variables are returned. This allows a wide
+ range of transformations.
+
+Examples::
+
+ # Match only if the HTTP method is "GET" or "HEAD".
+ m.connect("/user/list", controller="user", action="list",
+ conditions=dict(method=["GET", "HEAD"]))
+
+ # A sub-domain should be present.
+ m.connect("/", controller="user", action="home",
+ conditions=dict(sub_domain=True))
+
+ # Sub-domain should be either "fred" or "george".
+ m.connect("/", controller="user", action="home",
+ conditions=dict(sub_domain=["fred", "george"]))
+
+ # Put the referrer into the resulting match dictionary.
+ # This function always returns true, so it never prevents the match
+ # from succeeding.
+ def referals(environ, result):
+ result["referer"] = environ.get("HTTP_REFERER")
+ return True
+ m.connect("/{controller}/{action}/{id}",
+ conditions=dict(function=referals))
+
+Wildcard routes
+---------------
+
+By default, path variables do not match a slash. This ensures that each
+variable will match exactly one component. You can use requirements to
+override this::
+
+ map.connect("/static/{filename:.*?}")
+
+This matches "/static/foo.jpg", "/static/bar/foo.jpg", etc.
+
+Beware that careless regexes may eat the entire rest of the URL and cause
+components to the right of it not to match::
+
+ # OK because the following component is static and the regex has a "?".
+ map.connect("/static/{filename:.*?}/download")
+
+The lesson is to always test wildcard patterns.
+
+Format extensions
+-----------------
+
+A path component of ``{.format}`` will match an optional format extension (e.g.
+".html" or ".json"), setting the format variable to the part after the "."
+(e.g. "html" or "json") if there is one, or to ``None`` otherwise. For example::
+
+ map.connect('/entries/{id}{.format}')
+
+will match "/entries/1" and "/entries/1.mp3". You can use requirements to
+limit which extensions will match, for example::
+
+ map.connect('/entries/{id:\d+}{.format:json}')
+
+will match "/entries/1" and "/entries/1.json" but not "/entries/1.mp3".
+
+As with wildcard routes, it's important to understand and test this. Without
+the ``\d+`` requirement on the ``id`` variable above, "/entries/1.mp3" would match
+successfully, with the ``id`` variable capturing "1.mp3".
+
+*New in Routes 1.12.*
+
+Submappers
+----------
+
+A submapper lets you add several similar routes
+without having to repeat identical keyword arguments. There are two syntaxes,
+one using a Python ``with`` block, and the other avoiding it. ::
+
+ # Using 'with'
+ with map.submapper(controller="home") as m:
+ m.connect("home", "/", action="splash")
+ m.connect("index", "/index", action="index")
+
+ # Not using 'with'
+ m = map.submapper(controller="home")
+ m.connect("home", "/", action="splash")
+ m.connect("index", "/index", action="index")
+
+ # Both of these syntaxes create the following routes::
+ # "/" => {"controller": "home", action="splash"}
+ # "/index" => {"controller": "home", action="index"}
+
+You can also specify a common path prefix for your routes::
+
+ with map.submapper(path_prefix="/admin", controller="admin") as m:
+ m.connect("admin_users", "/users", action="users")
+ m.connect("admin_databases", "/databases", action="databases")
+
+ # /admin/users => {"controller": "admin", "action": "users"}
+ # /admin/databases => {"controller": "admin", "action": "databases"}
+
+All arguments to ``.submapper`` must be keyword arguments.
+
+The submapper is *not* a complete mapper. It's just a temporary object
+with a ``.connect`` method that adds routes to the mapper it was spawned
+from.
+
+*New in Routes 1.11.*
+
+Submapper helpers
+-----------------
+
+Submappers contain a number of helpers that further simplify routing
+configuration. This::
+
+ with map.submapper(controller="home") as m:
+ m.connect("home", "/", action="splash")
+ m.connect("index", "/index", action="index")
+
+can be written::
+
+ with map.submapper(controller="home", path_prefix="/") as m:
+ m.action("home", action="splash")
+ m.link("index")
+
+The ``action`` helper generates a route for one or more HTTP methods ('GET' is
+assumed) at the submapper's path ('/' in the example above). The ``link``
+helper generates a route at a relative path.
+
+There are specific helpers corresponding to the standard ``index``, ``new``,
+``create``, ``show``, ``edit``, ``update`` and ``delete`` actions.
+You can use these directly::
+
+ with map.submapper(controller="entries", path_prefix="/entries") as entries:
+ entries.index()
+ with entries.submapper(path_prefix="/{id}") as entry:
+ entry.show()
+
+or indirectly::
+
+ with map.submapper(controller="entries", path_prefix="/entries",
+ actions=["index"]) as entries:
+ entries.submapper(path_prefix="/{id}", actions=["show"])
+
+Collection/member submappers nested in this way are common enough that there is
+helper for this too::
+
+ map.collection(collection_name="entries", member_name="entry",
+ controller="entries",
+ collection_actions=["index"], member_actions["show"])
+
+This returns a submapper instance to which further routes may be added; it has
+a ``member`` property (a nested submapper) to which which member-specific routes
+can be added. When ``collection_actions`` or ``member_actions`` are omitted,
+the full set of actions is generated (see the example under "Printing" below).
+
+See "RESTful services" below for ``map.resource``, a precursor to
+``map.collection`` that does not use submappers.
+
+*New in Routes 1.12.*
+
+Adding routes from a nested application
+---------------------------------------
+
+*New in Routes 1.11.* Sometimes in nested applications, the child application
+gives the parent a list of routes to add to its mapper. These can be added
+with the ``.extend`` method, optionally providing a path prefix::
+
+ routes = [
+ Route("index", "/index.html", controller="home", action="index"),
+ ]
+
+ map.extend(routes)
+ # /index.html => {"controller": "home", "action": "index"}
+
+ map.extend(routes, "/subapp")
+ # /subapp/index.html => {"controller": "home", "action": "index"}
+
+This does not exactly add the route objects to the mapper. It creates
+identical new route objects and adds those to the mapper.
+
+*New in Routes 1.11.*
diff --git a/docs/uni_redirect_rest.rst b/docs/uni_redirect_rest.rst
new file mode 100644
index 0000000..322e20f
--- /dev/null
+++ b/docs/uni_redirect_rest.rst
@@ -0,0 +1,212 @@
+============================
+Unicode, Redirects, and More
+============================
+
+Unicode
+=======
+
+Routes assumes UTF-8 encoding on incoming URLs, and ``url`` and ``url_for``
+also generate UTF-8. You can change the encoding with the ``map.charset``
+attribute::
+
+ map.charset = "latin-1"
+
+New in Routes 1.10: several bugfixes.
+
+Redirect Routes
+===============
+
+Redirect routes allow you to specify redirects in the route map, similar to
+RewriteRule in an Apache configuration. This avoids the need to define dummy
+controller actions just to handle redirects. It's especially useful when the
+URL structure changes and you want to redirect legacy URLs to their new
+equivalents. The redirection is done by the Routes middleware, and the WSGI
+application is not called.
+
+``map.redirect`` takes two positional arguments: the route path and the
+destination URL. Redirect routes do not have a name. Both paths can contain
+variables, and the route path can take inline requirements. Keyword arguments
+are the same as ``map.connect``, both in regards to extra variables and to route
+options. ::
+
+ map.redirect("/legacyapp/archives/{url:.*}", "/archives/{url}")
+
+ map.redirect("/legacyapp/archives/{url:.*}", "/archives/{url}")
+
+By default a "302 Found" HTTP status is issued. You can override this with the
+``_redirect_code`` keyword argument. The value must be an entire status
+string. ::
+
+ map.redirect("/home/index", "/", _redirect_code="301 Moved Permanently")
+
+*New in Routes 1.10.*
+
+Printing
+========
+
+Mappers now have a formatted string representation. In your python shell,
+simply print your application's mapper::
+
+ >>> map.collection("entries", "entry")
+ >>> print map
+ Route name Methods Path
+ entries GET /entries{.format}
+ create_entry POST /entries{.format}
+ new_entry GET /entries/new{.format}
+ entry GET /entries/{id}{.format}
+ update_entry PUT /entries/{id}{.format}
+ delete_entry DELETE /entries/{id}{.format}
+ edit_entry GET /entries/{id}/edit{.format}
+
+*New in Routes 1.12.*
+
+
+Introspection
+=============
+
+The mapper attribute ``.matchlist`` contains the list of routes to be matched
+against incoming URLs. You can iterate this list to see what routes are
+defined. This can be useful when debugging route configurations.
+
+
+Other
+=====
+
+If your application is behind an HTTP proxy such a load balancer on another
+host, the WSGI environment will refer to the internal server rather than to the
+proxy, which will mess up generated URLs. Use the ProxyMiddleware in
+PasteDeploy to fix the WSGI environment to what it would have been without the
+proxy.
+
+To debug routes, turn on debug logging for the "routes.middleware" logger.
+(See Python's ``logging`` module to set up your logging configuration.)
+
+Backward compatibility
+======================
+
+The following syntaxes are allowed for compatibility with previous versions
+of Routes. They may be removed in the future.
+
+Omitting the name arg
+---------------------
+
+In the tutorial we said that nameless routes can be defined by passing ``None``
+as the first argument. You can also omit the first argument entirely::
+
+ map.connect(None, "/{controller}/{action}")
+ map.connect("/{controller}/{action}")
+
+The syntax with ``None`` is preferred to be forward-compatible with future
+versions of Routes. It avoids the path argument changing position between
+the first and second arguments, which is unpythonic.
+
+:varname
+--------
+
+Path variables were defined in the format ``:varname`` and ``:(varname)``
+prior to Routes 1.9. The form with parentheses was called "grouping", used
+to delimit the variable name from a following letter or number. Thus the old
+syntax "/:controller/:(id)abc" corresponds to the new syntax
+"/{controller}/{id}abc".
+
+The older wildcard syntax is ``*varname`` or ``*(varname)``::
+
+ # OK because the following component is static.
+ map.connect("/static/*filename/download")
+
+ # Deprecated syntax. WRONG because the wildcard will eat the rest of the
+ # URL, leaving nothing for the following variable, which will cause the
+ # match to fail.
+ map.connect("/static/*filename/:action")
+
+
+Minimization
+------------
+
+Minimization was a misfeature which was intended to save typing, but which
+often resulted in the wrong route being chosen. Old applications that still
+depend on it must now enable it by putting ``map.minimization = True`` in
+their route definitions.
+
+Without minimization, the URL must contain values for all path variables in
+the route::
+
+ map.connect("basic", "/{controller}/{action}",
+ controller="mycontroller", action="myaction", weather="sunny")
+
+This route matches any two-component URL, for instance "/help/about". The
+resulting routing variables would be::
+
+ {"controller": "help", "action": "about", "weather": "sunny"}
+
+The path variables are taken from the URL, and any extra variables are added as
+constants. The extra variables for "controller" and "action" are *never used*
+in matching, but are available as default values for generation::
+
+ url("basic", controller="help") => "/help/about?weather=sunny"
+
+With minimization, the same route path would also match shorter URLs such as
+"/help", "/foo", and "/". Missing values on the right of the URL would be
+taken from the extra variables. This was intended to lessen the number of
+routes you had to write. In practice it led to obscure application bugs
+because sometimes an unexpected route would be matched. Thus Routes 1.9
+introduced non-minimization and recommended "map.minimization = False" for
+all new applications.
+
+A corollary problem was generating the wrong route. Routes 1.9 tightened up
+the rule for generating named routes. If a route name is specified in
+``url()`` or ``url_for()``, *only* that named route will be chosen. In
+previous versions, it might choose another route based on the keyword args.
+
+Implicit defaults and route memory
+----------------------------------
+
+Implicit defaults worked with minimization to provide automatic default values
+for the "action" and "id" variables. If a route was defined as
+``map.connect("/{controller}/{action}/{id}") and the URL "/archives"`` was
+requested, Routes would implicitly add ``action="index", id=None`` to the
+routing variables.
+
+To enable implicit defaults, set ``map.minimization = True; map.explicit =
+False``. You can also enable implicit defaults on a per-route basis by setting
+``map.explicit = True`` and defining each route with a keyword argument ``explicit=False``.
+
+Previous versions also had implicit default values for "controller",
+"action", and "id". These are now disabled by default, but can be enabled via
+``map.explicit = True``. This also enables route memory
+
+url_for()
+---------
+
+``url_for`` was a route generation function which was replaced by the ``url``
+object. Usage is the same except that ``url_for`` uses route memory in some
+cases and ``url`` never does. Route memory is where variables from the current
+URL (the current request) are injected into the generated URL. To use route
+memory with ``url``, call ``url.current()`` passing the variables you want to
+override. Any other variables needed by the route will be taken from the
+current routing variables.
+
+In other words, ``url_for`` combines ``url`` and ``url.current()`` into one
+function. The location of ``url_for`` is also different. ``url_for`` is
+properly imported from ``routes``::
+
+ from routes import url_for
+
+``url_for`` was traditionally imported into WebHelpers, and it's still used in
+some tests and in ``webhelpers.paginate``. Many old Pylons applications
+contain ``h.url_for()`` based on its traditional importation to helpers.py.
+However, its use in new applications is discouraged both because of its
+ambiguous syntax and because its implementation depends on an ugly singleton.
+
+The ``url`` object is created by the RoutesMiddleware and inserted into the
+WSGI environment. Pylons makes it available as ``pylons.url``, and in
+templates as ``url``.
+
+redirect_to()
+-------------
+
+This combined ``url_for`` with a redirect. Instead, please use your
+framework's redirect mechanism with a ``url`` call. For instance in Pylons::
+
+ from pylons.controllers.util import redirect
+ redirect(url("login"))
diff --git a/routes/__init__.py b/routes/__init__.py
index 21bbc09..d252c70 100644
--- a/routes/__init__.py
+++ b/routes/__init__.py
@@ -1,5 +1,5 @@
"""Provides common classes and functions most users will want access to."""
-import threadinglocal, sys
+import threading, sys
class _RequestConfig(object):
"""
@@ -8,7 +8,7 @@ class _RequestConfig(object):
The Routes RequestConfig object is a thread-local singleton that should
be initialized by the web framework that is utilizing Routes.
"""
- __shared_state = threadinglocal.local()
+ __shared_state = threading.local()
def __getattr__(self, name):
return getattr(self.__shared_state, name)
diff --git a/routes/mapper.py b/routes/mapper.py
index 65ed7a0..79a2007 100644
--- a/routes/mapper.py
+++ b/routes/mapper.py
@@ -1,10 +1,7 @@
+"""Mapper and Sub-Mapper"""
import re
import sys
import threading
-import threadinglocal
-
-if sys.version < '2.4':
- from sets import ImmutableSet as frozenset
from routes import request_config
from routes.lru import LRUCache
@@ -367,7 +364,7 @@ class Mapper(SubMapperParent):
self._created_gens = False
self._master_regexp = None
self.prefix = None
- self.req_data = threadinglocal.local()
+ self.req_data = threading.local()
self.directory = directory
self.always_scan = always_scan
self.controller_scan = controller_scan
diff --git a/routes/threadinglocal.py b/routes/threadinglocal.py
deleted file mode 100644
index 33c4dfd..0000000
--- a/routes/threadinglocal.py
+++ /dev/null
@@ -1,36 +0,0 @@
-try:
- import threading
-except ImportError:
- # No threads, so "thread local" means process-global
- class local(object):
- pass
-else:
- try:
- local = threading.local
- except AttributeError:
- # Added in 2.4, but now we'll have to define it ourselves
- import thread
- class local(object):
-
- def __init__(self):
- self.__dict__['__objs'] = {}
-
- def __getattr__(self, attr, g=thread.get_ident):
- try:
- return self.__dict__['__objs'][g()][attr]
- except KeyError:
- raise AttributeError(
- "No variable %s defined for the thread %s"
- % (attr, g()))
-
- def __setattr__(self, attr, value, g=thread.get_ident):
- self.__dict__['__objs'].setdefault(g(), {})[attr] = value
-
- def __delattr__(self, attr, g=thread.get_ident):
- try:
- del self.__dict__['__objs'][g()][attr]
- except KeyError:
- raise AttributeError(
- "No variable %s defined for thread %s"
- % (attr, g()))
-