Routes-2.4.1/ 0000775 0001750 0001750 00000000000 13032244005 013634 5 ustar travis travis 0000000 0000000 Routes-2.4.1/README.rst 0000664 0001750 0001750 00000001221 13032243746 015332 0 ustar travis travis 0000000 0000000 Routes is a Python re-implementation of the Rails routes system for mapping
URL's to Controllers/Actions and generating URL's. Routes makes it easy to
create pretty and concise URL's that are RESTful with little effort.
Speedy and dynamic URL generation means you get a URL with minimal cruft
(no big dangling query args). Shortcut features like Named Routes cut down
on repetitive typing.
See `the documentation for installation and usage of Routes `_.
.. image:: https://secure.travis-ci.org/bbangert/routes.png?branch=master
:alt: Build Status
:target: https://secure.travis-ci.org/bbangert/routes
Routes-2.4.1/MANIFEST.in 0000664 0001750 0001750 00000000161 13032243746 015403 0 ustar travis travis 0000000 0000000 recursive-include docs *
include CHANGELOG.rst
include LICENSE.txt
global-exclude .DS_Store *.hgignore *.hgtags
Routes-2.4.1/docs/ 0000775 0001750 0001750 00000000000 13032244005 014564 5 ustar travis travis 0000000 0000000 Routes-2.4.1/docs/porting.rst 0000664 0001750 0001750 00000010116 13032243746 017012 0 ustar travis travis 0000000 0000000 Porting Routes to a WSGI Web Framework
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
RoutesMiddleware
----------------
An application can create a raw mapper object and call its ``.match`` and
``.generate`` methods. However, WSGI applications probably want to use
the ``RoutesMiddleware`` as Pylons does::
# In myapp/config/middleware.py
from routes.middleware import RoutesMiddleware
app = RoutesMiddleware(app, map) # ``map`` is a routes.Mapper.
The middleware matches the requested URL and sets the following WSGI
variables::
environ['wsgiorg.routing_args'] = ((url, match))
environ['routes.route'] = route
environ['routes.url'] = url
where ``match`` is the routing variables dict, ``route`` is the matched route,
and ``url`` is a ``URLGenerator`` object. In Pylons, ``match`` is used by the
dispatcher, and ``url`` is accessible as ``pylons.url``.
The middleware handles redirect routes itself, issuing the appropriate
redirect. The application is not called in this case.
To debug routes, turn on debug logging for the "routes.middleware" logger.
See the Routes source code for other features which may have been added.
URL Resolution
--------------
When the URL is looked up, it should be matched against the Mapper. When
matching an incoming URL, it is assumed that the URL path is the only string
being matched. All query args should be stripped before matching::
m.connect('/articles/{year}/{month}', controller='blog', action='view', year=None)
m.match('/articles/2003/10')
# {'controller':'blog', 'action':'view', 'year':'2003', 'month':'10'}
Matching a URL will return a dict of the match results, if you'd like to
differentiate between where the argument came from you can use routematch which
will return the Route object that has all these details::
m.connect('/articles/{year}/{month}', controller='blog', action='view', year=None)
result = m.routematch('/articles/2003/10')
# result is a tuple of the match dict and the Route object
# result[0] - {'controller':'blog', 'action':'view', 'year':'2003', 'month':'10'}
# result[1] - Route object
# result[1].defaults - {'controller':'blog', 'action':'view', 'year':None}
# result[1].hardcoded - ['controller', 'action']
Your integration code is then expected to dispatch to a controller and action
in the dict. How it does this is entirely up to the framework integrator. Your
integration should also typically provide the web developer a mechanism to
access the additional dict values.
Request Configuration
---------------------
If you intend to support ``url_for()`` and ``redirect_to()``, they depend on a
singleton object which requires additional configuration. You're better off
not supporting them at all because they will be deprecated soon.
``URLGenerator`` is the forward-compatible successor to ``url_for()``.
``redirect_to()`` is better done in the web framework`as in
``pylons.controllers.util.redirect_to()``.
``url_for()`` and ``redirect_to()`` need information on the current request,
and since they can be called from anywhere they don't have direct access to the
WSGI environment. To remedy this, Routes provides a thread-safe singleton class
called "request_config", which holds the request information for the current
thread. You should update this after matching the incoming URL but before
executing any code that might call the two functions. Here is an example::
from routes import request_config
config = request_config()
config.mapper = m # Your mapper object
config.mapper_dict = result # The dict from m.match for this URL request
config.host = hostname # The server hostname
config.protocol = port # Protocol used, http, https, etc.
config.redirect = redir_func # A redirect function used by your framework, that is
# expected to take as the first non-keyword arg a single
# full or relative URL
See the docstring for ``request_config`` in routes/__init__.py to make sure
you've initialized everything necessary.
Routes-2.4.1/docs/restful.rst 0000664 0001750 0001750 00000022435 13032243746 017023 0 ustar travis travis 0000000 0000000 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)
.. note::
Due to how Routes matches a list of URL's, it has no inherent knowledge of
a route being a **resource**. As such, if a route fails to match due to
the method requirements not being met, a 404 will return just like any
other failure to match a route.
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 "/:_id".
If parent_resource is supplied and name_prefix isn't, name_prefix will
be generated from parent_resource as "_".
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'
Routes-2.4.1/docs/uni_redirect_rest.rst 0000664 0001750 0001750 00000020540 13032243746 021043 0 ustar travis travis 0000000 0000000 ============================
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 Controller action
entries GET /entries{.format} entry index
create_entry POST /entries{.format} entry create
new_entry GET /entries/new{.format} entry new
entry GET /entries/{id}{.format} entry show
update_entry PUT /entries/{id}{.format} entry update
delete_entry DELETE /entries/{id}{.format} entry delete
edit_entry GET /entries/{id}/edit{.format} entry edit
*New in Routes 1.12.*
*Controller/action fields new in Routes 2.1*
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"))
Routes-2.4.1/docs/changes.rst 0000664 0001750 0001750 00000000072 13032243746 016740 0 ustar travis travis 0000000 0000000 :tocdepth: 2
.. _changes:
.. include:: ../CHANGELOG.rst
Routes-2.4.1/docs/glossary.rst 0000664 0001750 0001750 00000004326 13032243746 017201 0 ustar travis travis 0000000 0000000 .. _glossary:
Glossary
========
.. glossary::
component
A part of a URL delimited by slashes. The URL "/help/about" contains
two components: "help" and "about".
generation
The act of creating a URL based on a route name and/or variable values.
This is the opposite of matching. Finding a route by name is called
*named generation*. Finding a route without specifying a name is
called *nameless generation*.
mapper
A container for routes. There is normally one mapper per application,
although nested subapplications might have their own mappers. A
mapper knows how to match routes and generate them.
matching
The act of matching a given URL against a list of routes, and
returning the routing variables. See the *route* entry for an example.
minimization
A deprecated feature which allowed short URLs to match long paths.
Details are in the ``Backward Compatibility`` section in the manual.
route
A rule mapping a URL pattern to a dict of routing variables. For
instance, if the pattern is "/{controller}/{action}" and the requested
URL is "/help/about", the resulting dict would be::
{"controller": "help", "action": "about"}
Routes does not know what these variables mean; it simply returns them
to the application. Pylons would look for a ``controllers/help.py``
module containing a ``HelpController`` class, and call its ``about``
method. Other frameworks may do something different.
A route may have a name, used to identify the route.
route path
The URL pattern in a route.
routing variables
A dict of key-value pairs returned by matching. Variables defined in
the route path are called *path variables*; their values will be taken
from the URL. Variables defined outside the route path are called
*default variables*; their values are not affected by the URL.
The WSGI.org environment key for routing variables is
"wsgiorg.routing_args". This manual does not use that term because it
can be confused with function arguments.
Routes-2.4.1/docs/routes-logo.png 0000664 0001750 0001750 00000042403 13032243746 017567 0 ustar travis travis 0000000 0000000 PNG
IHDR d \95 sRGB pHYs
B(x tIME
$'J IDATxgxյkeF]drjz3L͡%J!!^CHN'@ PBظ.mo{#HνIe?[^kov}]߮oѿ5'&\M5oƎcN{onv rom:K6&ƉY;s[W~x]s >Nɟu1%
օ=v! g35?B p:',37&
_UqC@&TnZ^G+(`j "xqsM=&F@O?Ȕ}[^ҁ!`~D*{a&r3ۭV@&>;Kb>,ݔtԘxS]:g1+oXvwԪcbF#2E+K233rw
c?|oU RSzi٦~r=ENv'lM0_cf&"bOL"1:hn/iW^hnNSO_>]F_L36Բۭ VTǀK ֵF zw#:j (@ |"6 H缅#V,vg.ya6Yo>`p`Nd~oڎ㧖pgwѡt@Ri r=jo(ޚ̡Wv=E<Λ/X@̺͉a41FM
Dy92ar 0!"a`nx[Z6]5X,=0Q}*cW7|ۙXg015MJ[z2ڰ_h~CX %?^QWpxGq~sԴ'XSSPzzY?$J< "0B82Ӧ"0s&D,Z,ba-^<{MGLKMkU適
oPL8SđcjԔ3(n5j|co(/(CE/ ֭H=+~ &b
~+_@$[4*J dzx1
>ԴQc~yR{]ͥaSuӭ7?68/ӅԸPUl=zhҤ+W^O?cN
y(1csl.pCZi-Ws\%d#Cا89\)Y*sA==ŧg2:~k:IWSw|[Z#~s_^Xz/Pr{ȃ{ʬ1h!DuФܼר+
yږrz.sO_JMW]wKs\_n<RLZSL?(+"G}tSa.D2D[RԖx\YHW2ꋒ#"y5aFݷc+27 .Wrܫ(pKjزrmo@y **u@"X::A7&uu /%w@ڞ:5'3՝ghyG%£KeQ3`;lWb `F2c!o{.bH04]MD&Q?JcA:>+jsZ33(3|`)#z2%%KB|Tbtv@id/Z'Ly5]{dnłe
QGNg>[yp?O۵&kr9Bںtܫ' e;fWoN{qgGw4W[7c0cQ!B @/f3b#(sdjģ/Ć 4m9QkeӤwu P9L:drK.?3qȌe_fkqq}楗)XHHqyĀm>#Wĺp0-_.mEO"C&