summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bangert <ben@groovie.org>2015-01-17 10:31:28 -0800
committerBen Bangert <ben@groovie.org>2015-01-17 10:31:28 -0800
commit93470a86317342d2298ad393c5993b2ec07348ad (patch)
treeb193aca05bffff74fa5ad4940f02693f4e5ea5d5
parenta7af7fd6dc68f952e659bd3fcffc3d6c470160e3 (diff)
downloadroutes-93470a86317342d2298ad393c5993b2ec07348ad.tar.gz
PEP8 cleanups.
-rw-r--r--routes/__init__.py61
-rw-r--r--routes/mapper.py493
-rw-r--r--routes/middleware.py60
-rw-r--r--routes/route.py300
-rw-r--r--routes/util.py154
5 files changed, 551 insertions, 517 deletions
diff --git a/routes/__init__.py b/routes/__init__.py
index d252c70..ae9a21b 100644
--- a/routes/__init__.py
+++ b/routes/__init__.py
@@ -1,15 +1,16 @@
"""Provides common classes and functions most users will want access to."""
-import threading, sys
+import threading
+
class _RequestConfig(object):
"""
RequestConfig thread-local singleton
-
- The Routes RequestConfig object is a thread-local singleton that should
+
+ The Routes RequestConfig object is a thread-local singleton that should
be initialized by the web framework that is utilizing Routes.
"""
__shared_state = threading.local()
-
+
def __getattr__(self, name):
return getattr(self.__shared_state, name)
@@ -22,10 +23,10 @@ class _RequestConfig(object):
self.load_wsgi_environ(value)
return self.__shared_state.__setattr__(name, value)
return self.__shared_state.__setattr__(name, value)
-
+
def __delattr__(self, name):
delattr(self.__shared_state, name)
-
+
def load_wsgi_environ(self, environ):
"""
Load the protocol/server info from the environ and store it.
@@ -41,7 +42,7 @@ class _RequestConfig(object):
self.mapper.environ = environ
except AttributeError:
pass
-
+
# Wrap in try/except as common case is that there is a mapper
# attached to self
try:
@@ -57,7 +58,7 @@ class _RequestConfig(object):
self.__shared_state.route = None
except AttributeError:
pass
-
+
if 'HTTP_X_FORWARDED_HOST' in environ:
self.__shared_state.host = environ['HTTP_X_FORWARDED_HOST']
elif 'HTTP_HOST' in environ:
@@ -71,17 +72,18 @@ class _RequestConfig(object):
if environ['SERVER_PORT'] != '80':
self.__shared_state.host += ':' + environ['SERVER_PORT']
+
def request_config(original=False):
"""
Returns the Routes RequestConfig object.
-
+
To get the Routes RequestConfig:
-
+
>>> from routes import *
>>> config = request_config()
-
+
The following attributes must be set on the config object every request:
-
+
mapper
mapper should be a Mapper instance thats ready for use
host
@@ -91,7 +93,7 @@ def request_config(original=False):
mapper_dict
mapper_dict should be the dict returned by mapper.match()
redirect
- redirect should be a function that issues a redirect,
+ redirect should be a function that issues a redirect,
and takes a url as the sole argument
prefix (optional)
Set if the application is moved under a URL prefix. Prefix
@@ -99,33 +101,33 @@ def request_config(original=False):
environ (optional)
Set to the WSGI environ for automatic prefix support if the
webapp is underneath a 'SCRIPT_NAME'
-
+
Setting the environ will use information in environ to try and
populate the host/protocol/mapper_dict options if you've already
set a mapper.
-
+
**Using your own requst local**
-
- If you have your own request local object that you'd like to use instead
- of the default thread local provided by Routes, you can configure Routes
+
+ If you have your own request local object that you'd like to use instead
+ of the default thread local provided by Routes, you can configure Routes
to use it::
-
+
from routes import request_config()
config = request_config()
if hasattr(config, 'using_request_local'):
config.request_local = YourLocalCallable
config = request_config()
-
- Once you have configured request_config, its advisable you retrieve it
- again to get the object you wanted. The variable you assign to
- request_local is assumed to be a callable that will get the local config
+
+ Once you have configured request_config, its advisable you retrieve it
+ again to get the object you wanted. The variable you assign to
+ request_local is assumed to be a callable that will get the local config
object you wish.
-
+
This example tests for the presence of the 'using_request_local' attribute
- which will be present if you haven't assigned it yet. This way you can
+ which will be present if you haven't assigned it yet. This way you can
avoid repeat assignments of the request specific callable.
-
- Should you want the original object, perhaps to change the callable its
+
+ Should you want the original object, perhaps to change the callable its
using or stop this behavior, call request_config(original=True).
"""
obj = _RequestConfig()
@@ -136,7 +138,8 @@ def request_config(original=False):
obj.request_local = False
obj.using_request_local = False
return _RequestConfig()
-
+
from routes.mapper import Mapper
from routes.util import redirect_to, url_for, URLGenerator
-__all__=['Mapper', 'url_for', 'URLGenerator', 'redirect_to', 'request_config']
+
+__all__ = ['Mapper', 'url_for', 'URLGenerator', 'redirect_to', 'request_config']
diff --git a/routes/mapper.py b/routes/mapper.py
index 018fa7c..cc3f7ac 100644
--- a/routes/mapper.py
+++ b/routes/mapper.py
@@ -1,13 +1,15 @@
"""Mapper and Sub-Mapper"""
import re
-import sys
import threading
-import pkg_resources
from repoze.lru import LRUCache
from routes import request_config
-from routes.util import controller_scan, MatchException, RoutesException, as_unicode
+from routes.util import (
+ controller_scan,
+ RoutesException,
+ as_unicode
+)
from routes.route import Route
@@ -28,23 +30,23 @@ class SubMapperParent(object):
"""Base class for Mapper and SubMapper, both of which may be the parent
of SubMapper objects
"""
-
+
def submapper(self, **kargs):
"""Create a partial version of the Mapper with the designated
options set
-
+
This results in a :class:`routes.mapper.SubMapper` object.
-
+
If keyword arguments provided to this method also exist in the
keyword arguments provided to the submapper, their values will
be merged with the saved options going first.
-
+
In addition to :class:`routes.route.Route` arguments, submapper
can also take a ``path_prefix`` argument which will be
prepended to the path of all routes that are connected.
-
+
Example::
-
+
>>> map = Mapper(controller_scan=None)
>>> map.connect('home', '/', controller='home', action='splash')
>>> map.matchlist[0].name == 'home'
@@ -55,7 +57,7 @@ class SubMapperParent(object):
True
>>> map.matchlist[1].defaults['controller'] == 'home'
True
-
+
Optional ``collection_name`` and ``resource_name`` arguments are
used in the generation of route names by the ``action`` and
``link`` methods. These in turn are used by the ``index``,
@@ -65,12 +67,15 @@ class SubMapperParent(object):
is set to ``True`` (the default), generated paths are given the
suffix '{.format}' which matches or generates an optional format
extension.
-
+
Example::
-
+
>>> from routes.util import url_for
>>> map = Mapper(controller_scan=None)
- >>> m = map.submapper(path_prefix='/entries', collection_name='entries', resource_name='entry', actions=['index', 'new'])
+ >>> m = map.submapper(path_prefix='/entries',
+ collection_name='entries',
+ resource_name='entry',
+ actions=['index', 'new'])
>>> url_for('entries') == '/entries'
True
>>> url_for('new_entry', format='xml') == '/entries/new.xml'
@@ -82,21 +87,21 @@ class SubMapperParent(object):
def collection(self, collection_name, resource_name, path_prefix=None,
member_prefix='/{id}', controller=None,
collection_actions=COLLECTION_ACTIONS,
- member_actions = MEMBER_ACTIONS, member_options=None,
+ member_actions=MEMBER_ACTIONS, member_options=None,
**kwargs):
"""Create a submapper that represents a collection.
This results in a :class:`routes.mapper.SubMapper` object, with a
``member`` property of the same type that represents the collection's
member resources.
-
+
Its interface is the same as the ``submapper`` together with
``member_prefix``, ``member_actions`` and ``member_options``
which are passed to the ``member`` submapper as ``path_prefix``,
``actions`` and keyword arguments respectively.
-
+
Example::
-
+
>>> from routes.util import url_for
>>> map = Mapper(controller_scan=None)
>>> c = map.collection('entries', 'entry')
@@ -111,7 +116,7 @@ class SubMapperParent(object):
"""
if controller is None:
controller = resource_name or collection_name
-
+
if path_prefix is None:
path_prefix = '/' + collection_name
@@ -119,9 +124,9 @@ class SubMapperParent(object):
resource_name=resource_name,
path_prefix=path_prefix, controller=controller,
actions=collection_actions, **kwargs)
-
+
collection.member = SubMapper(collection, path_prefix=member_prefix,
- actions=member_actions,
+ actions=member_actions,
**(member_options or {}))
return collection
@@ -136,9 +141,9 @@ class SubMapper(SubMapperParent):
self.collection_name = collection_name
self.member = None
self.resource_name = resource_name \
- or getattr(obj, 'resource_name', None) \
- or kwargs.get('controller', None) \
- or getattr(obj, 'controller', None)
+ or getattr(obj, 'resource_name', None) \
+ or kwargs.get('controller', None) \
+ or getattr(obj, 'controller', None)
if formatted is not None:
self.formatted = formatted
else:
@@ -147,7 +152,7 @@ class SubMapper(SubMapperParent):
self.formatted = True
self.add_actions(actions or [])
-
+
def connect(self, *args, **kwargs):
newkargs = {}
newargs = args
@@ -159,7 +164,7 @@ class SubMapper(SubMapperParent):
newargs = (self.kwargs[key] + args[0],)
elif key in kwargs:
if isinstance(value, dict):
- newkargs[key] = dict(value, **kwargs[key]) # merge dicts
+ newkargs[key] = dict(value, **kwargs[key]) # merge dicts
elif key == 'controller':
newkargs[key] = kwargs[key]
else:
@@ -176,7 +181,7 @@ class SubMapper(SubMapperParent):
"""Generates a named route for a subresource.
Example::
-
+
>>> from routes.util import url_for
>>> map = Mapper(controller_scan=None)
>>> c = map.collection('entries', 'entry')
@@ -188,7 +193,8 @@ class SubMapper(SubMapperParent):
True
>>> url_for('ping_entry', id=1) == '/entries/1/ping'
True
- >>> url_for('ping_entry', id=1, format='xml') == '/entries/1/ping.xml'
+ >>> url_for('ping_entry', id=1, format='xml') \
+ == '/entries/1/ping.xml'
True
"""
@@ -215,17 +221,20 @@ class SubMapper(SubMapperParent):
"""Generates a named route at the base path of a submapper.
Example::
-
+
>>> from routes import url_for
>>> map = Mapper(controller_scan=None)
>>> c = map.submapper(path_prefix='/entries', controller='entry')
>>> c.action(action='index', name='entries', formatted=True)
>>> c.action(action='create', method='POST')
- >>> url_for(controller='entry', action='index', method='GET') == '/entries'
+ >>> url_for(controller='entry', action='index', method='GET') \
+ == '/entries'
True
- >>> url_for(controller='entry', action='index', method='GET', format='xml') == '/entries.xml'
+ >>> url_for(controller='entry', action='index', method='GET',
+ format='xml') == '/entries.xml'
True
- >>> url_for(controller='entry', action='create', method='POST') == '/entries'
+ >>> url_for(controller='entry', action='create', method='POST') \
+ == '/entries'
True
"""
@@ -237,13 +246,13 @@ class SubMapper(SubMapperParent):
suffix,
action=action or name,
**_kwargs_with_conditions(kwargs, method))
-
+
def index(self, name=None, **kwargs):
"""Generates the "index" action for a collection submapper."""
return self.action(name=name or self.collection_name,
action='index', method='GET', **kwargs)
- def show(self, name = None, **kwargs):
+ def show(self, name=None, **kwargs):
"""Generates the "show" action for a collection member submapper."""
return self.action(name=name or self.resource_name,
action='show', method='GET', **kwargs)
@@ -251,7 +260,7 @@ class SubMapper(SubMapperParent):
def create(self, **kwargs):
"""Generates the "create" action for a collection submapper."""
return self.action(action='create', method='POST', **kwargs)
-
+
def update(self, **kwargs):
"""Generates the "update" action for a collection member submapper."""
return self.action(action='update', method='PUT', **kwargs)
@@ -266,99 +275,99 @@ class SubMapper(SubMapperParent):
# Provided for those who prefer using the 'with' syntax in Python 2.5+
def __enter__(self):
return self
-
+
def __exit__(self, type, value, tb):
pass
+
# Create kwargs with a 'conditions' member generated for the given method
def _kwargs_with_conditions(kwargs, method):
if method and 'conditions' not in kwargs:
newkwargs = kwargs.copy()
- newkwargs['conditions'] = {'method': method}
- return newkwargs
+ newkwargs['conditions'] = {'method': method}
+ return newkwargs
else:
return kwargs
-
class Mapper(SubMapperParent):
"""Mapper handles URL generation and URL recognition in a web
application.
-
+
Mapper is built handling dictionary's. It is assumed that the web
application will handle the dictionary returned by URL recognition
to dispatch appropriately.
-
+
URL generation is done by passing keyword parameters into the
generate function, a URL is then returned.
-
+
"""
- def __init__(self, controller_scan=controller_scan, directory=None,
+ def __init__(self, controller_scan=controller_scan, directory=None,
always_scan=False, register=True, explicit=True):
"""Create a new Mapper instance
-
+
All keyword arguments are optional.
-
+
``controller_scan``
Function reference that will be used to return a list of
valid controllers used during URL matching. If
``directory`` keyword arg is present, it will be passed
into the function during its call. This option defaults to
a function that will scan a directory for controllers.
-
+
Alternatively, a list of controllers or None can be passed
in which are assumed to be the definitive list of
controller names valid when matching 'controller'.
-
+
``directory``
Passed into controller_scan for the directory to scan. It
- should be an absolute path if using the default
+ should be an absolute path if using the default
``controller_scan`` function.
-
+
``always_scan``
Whether or not the ``controller_scan`` function should be
run during every URL match. This is typically a good idea
during development so the server won't need to be restarted
anytime a controller is added.
-
+
``register``
- Boolean used to determine if the Mapper should use
+ Boolean used to determine if the Mapper should use
``request_config`` to register itself as the mapper. Since
it's done on a thread-local basis, this is typically best
used during testing though it won't hurt in other cases.
-
+
``explicit``
Boolean used to determine if routes should be connected
with implicit defaults of::
-
+
{'controller':'content','action':'index','id':None}
-
+
When set to True, these defaults will not be added to route
connections and ``url_for`` will not use Route memory.
-
+
Additional attributes that may be set after mapper
initialization (ie, map.ATTRIBUTE = 'something'):
-
+
``encoding``
Used to indicate alternative encoding/decoding systems to
use with both incoming URL's, and during Route generation
when passed a Unicode string. Defaults to 'utf-8'.
-
+
``decode_errors``
How to handle errors in the encoding, generally ignoring
any chars that don't convert should be sufficient. Defaults
to 'ignore'.
-
+
``minimization``
Boolean used to indicate whether or not Routes should
minimize URL's and the generated URL's, or require every
part where it appears in the path. Defaults to True.
-
+
``hardcode_names``
Whether or not Named Routes result in the default options
for the route being used *or* if they actually force url
generation to use the route. Defaults to False.
-
+
"""
self.matchlist = []
self.maxkeys = {}
@@ -388,7 +397,7 @@ class Mapper(SubMapperParent):
if register:
config = request_config()
config.mapper = self
-
+
def __str__(self):
"""Generates a tabular string representation."""
def format_methods(r):
@@ -401,10 +410,10 @@ class Mapper(SubMapperParent):
table = [('Route name', 'Methods', 'Path')] + \
[(r.name or '', format_methods(r), r.routepath or '')
for r in self.matchlist]
-
+
widths = [max(len(row[col]) for row in table)
for col in range(len(table[0]))]
-
+
return '\n'.join(
' '.join(row[col].ljust(widths[col])
for col in range(len(widths)))
@@ -415,20 +424,22 @@ class Mapper(SubMapperParent):
return self.req_data.environ
except AttributeError:
return None
+
def _envset(self, env):
self.req_data.environ = env
+
def _envdel(self):
del self.req_data.environ
environ = property(_envget, _envset, _envdel)
-
+
def extend(self, routes, path_prefix=''):
"""Extends the mapper routes with a list of Route objects
-
+
If a path_prefix is provided, all the routes will have their
path prepended with the path_prefix.
-
+
Example::
-
+
>>> map = Mapper(controller_scan=None)
>>> map.connect('home', '/', controller='home', action='splash')
>>> map.matchlist[0].name == 'home'
@@ -443,13 +454,13 @@ class Mapper(SubMapperParent):
True
>>> map.matchlist[2].routepath == '/subapp/index.htm'
True
-
+
.. note::
-
+
This function does not merely extend the mapper with the
given list of routes, it actually creates new routes with
identical calling arguments.
-
+
"""
for route in routes:
if path_prefix and route.minimization:
@@ -459,7 +470,7 @@ class Mapper(SubMapperParent):
else:
routepath = route.routepath
self.connect(route.name, routepath, **route._kargs)
-
+
def make_route(self, *args, **kargs):
"""Make a new Route object
@@ -469,20 +480,23 @@ class Mapper(SubMapperParent):
def connect(self, *args, **kargs):
"""Create and connect a new Route to the Mapper.
-
+
Usage:
-
+
.. code-block:: python
-
+
m = Mapper()
m.connect(':controller/:action/:id')
- m.connect('date/:year/:month/:day', controller="blog", action="view")
+ m.connect('date/:year/:month/:day', controller="blog",
+ action="view")
m.connect('archives/:page', controller="blog", action="by_page",
requirements = { 'page':'\d{1,2}' })
- m.connect('category_list', 'archives/category/:section', controller='blog', action='category',
- section='home', type='list')
- m.connect('home', '', controller='blog', action='view', section='home')
-
+ m.connect('category_list', 'archives/category/:section',
+ controller='blog', action='category',
+ section='home', type='list')
+ m.connect('home', '', controller='blog', action='view',
+ section='home')
+
"""
routename = None
if len(args) > 1:
@@ -494,17 +508,17 @@ class Mapper(SubMapperParent):
if '_minimize' not in kargs:
kargs['_minimize'] = self.minimization
route = self.make_route(*args, **kargs)
-
- # Apply encoding and errors if its not the defaults and the route
+
+ # Apply encoding and errors if its not the defaults and the route
# didn't have one passed in.
if (self.encoding != 'utf-8' or self.decode_errors != 'ignore') and \
'_encoding' not in kargs:
route.encoding = self.encoding
route.decode_errors = self.decode_errors
-
+
if not route.static:
self.matchlist.append(route)
-
+
if routename:
self._routenames[routename] = route
route.name = routename
@@ -519,33 +533,33 @@ class Mapper(SubMapperParent):
if not exists:
self.maxkeys[route.maxkeys] = [route]
self._created_gens = False
-
+
def _create_gens(self):
"""Create the generation hashes for route lookups"""
# Use keys temporailly to assemble the list to avoid excessive
# list iteration testing with "in"
controllerlist = {}
actionlist = {}
-
+
# Assemble all the hardcoded/defaulted actions/controllers used
for route in self.matchlist:
if route.static:
continue
- if route.defaults.has_key('controller'):
+ if 'controller' in route.defaults:
controllerlist[route.defaults['controller']] = True
- if route.defaults.has_key('action'):
+ if 'action' in route.defaults:
actionlist[route.defaults['action']] = True
-
+
# Setup the lists of all controllers/actions we'll add each route
# to. We include the '*' in the case that a generate contains a
# controller/action that has no hardcodes
controllerlist = controllerlist.keys() + ['*']
actionlist = actionlist.keys() + ['*']
-
+
# Go through our list again, assemble the controllers/actions we'll
# add each route to. If its hardcoded, we only add it to that dict key.
# Otherwise we add it to every hardcode since it can be changed.
- gendict = {} # Our generated two-deep hash
+ gendict = {} # Our generated two-deep hash
for route in self.matchlist:
if route.static:
continue
@@ -571,7 +585,7 @@ class Mapper(SubMapperParent):
self._create_regs(*args, **kwargs)
finally:
self.create_regs_lock.release()
-
+
def _create_regs(self, clist=None):
"""Creates regular expressions for all connected routes"""
if clist is None:
@@ -583,11 +597,11 @@ class Mapper(SubMapperParent):
clist = []
else:
clist = self.controller_scan
-
+
for key, val in self.maxkeys.iteritems():
for route in val:
route.makeregexp(clist)
-
+
regexps = []
routematches = []
for route in self.matchlist:
@@ -595,11 +609,11 @@ class Mapper(SubMapperParent):
routematches.append(route)
regexps.append(route.makeregexp(clist, include_names=False))
self._routematches = routematches
-
+
# Create our regexp to strip the prefix
if self.prefix:
self._regprefix = re.compile(self.prefix + '(.*)')
-
+
# Save the master regexp
regexp = '|'.join(['(?:%s)' % x for x in regexps])
self._master_reg = regexp
@@ -608,26 +622,26 @@ class Mapper(SubMapperParent):
except OverflowError:
self._master_regexp = None
self._created_regs = True
-
+
def _match(self, url, environ):
"""Internal Route matcher
-
+
Matches a URL against a route, and returns a tuple of the match
dict and the route object if a match is successfull, otherwise
it returns empty.
-
+
For internal use only.
-
+
"""
if not self._created_regs and self.controller_scan:
self.create_regs()
elif not self._created_regs:
raise RoutesException("You must generate the regular expressions"
- " before matching.")
-
+ " before matching.")
+
if self.always_scan:
self.create_regs()
-
+
matchlog = []
if self.prefix:
if re.match(self._regprefix, url):
@@ -636,13 +650,13 @@ class Mapper(SubMapperParent):
url = '/'
else:
return (None, None, matchlog)
-
+
environ = environ or self.environ
sub_domains = self.sub_domains
sub_domains_ignore = self.sub_domains_ignore
domain_match = self.domain_match
debug = self.debug
-
+
if self._master_regexp is not None:
# Check to see if its a valid url against the main regexp
# Done for faster invalid URL elimination
@@ -654,7 +668,7 @@ class Mapper(SubMapperParent):
valid_url = True
if not valid_url:
return (None, None, matchlog)
-
+
for route in self.matchlist:
if route.static:
if debug:
@@ -667,44 +681,44 @@ class Mapper(SubMapperParent):
if isinstance(match, dict) or match:
return (match, route, matchlog)
return (None, None, matchlog)
-
+
def match(self, url=None, environ=None):
"""Match a URL against against one of the routes contained.
-
+
Will return None if no valid match is found.
-
+
.. code-block:: python
-
+
resultdict = m.match('/joe/sixpack')
-
+
"""
if not url and not environ:
raise RoutesException('URL or environ must be provided')
-
+
if not url:
url = environ['PATH_INFO']
-
+
result = self._match(url, environ)
if self.debug:
return result[0], result[1], result[2]
if isinstance(result[0], dict) or result[0]:
return result[0]
return None
-
+
def routematch(self, url=None, environ=None):
"""Match a URL against against one of the routes contained.
-
+
Will return None if no valid match is found, otherwise a
result dict and a route object is returned.
-
+
.. code-block:: python
-
+
resultdict, route_obj = m.match('/joe/sixpack')
-
+
"""
if not url and not environ:
raise RoutesException('URL or environ must be provided')
-
+
if not url:
url = environ['PATH_INFO']
result = self._match(url, environ)
@@ -713,30 +727,30 @@ class Mapper(SubMapperParent):
if isinstance(result[0], dict) or result[0]:
return result[0], result[1]
return None
-
+
def generate(self, *args, **kargs):
"""Generate a route from a set of keywords
-
+
Returns the url text, or None if no URL could be generated.
-
+
.. code-block:: python
-
+
m.generate(controller='content',action='view',id=10)
-
+
"""
# Generate ourself if we haven't already
if not self._created_gens:
self._create_gens()
-
+
if self.append_slash:
kargs['_append_slash'] = True
-
+
if not self.explicit:
if 'controller' not in kargs:
kargs['controller'] = 'content'
if 'action' not in kargs:
kargs['action'] = 'index'
-
+
environ = kargs.pop('_environ', self.environ)
controller = kargs.get('controller', None)
action = kargs.get('action', None)
@@ -746,20 +760,20 @@ class Mapper(SubMapperParent):
# both SCRIPT_NAME and kargs:
cache_key = unicode(args).encode('utf8') + \
unicode(kargs).encode('utf8')
-
+
if self.urlcache is not None:
if self.environ:
cache_key_script_name = '%s:%s' % (
environ.get('SCRIPT_NAME', ''), cache_key)
else:
cache_key_script_name = cache_key
-
+
# Check the url cache to see if it exists, use it if it does
for key in [cache_key, cache_key_script_name]:
val = self.urlcache.get(key, self)
if val != self:
return val
-
+
controller = as_unicode(controller, self.encoding)
action = as_unicode(action, self.encoding)
@@ -767,7 +781,7 @@ class Mapper(SubMapperParent):
if not actionlist and not args:
return None
(keylist, sortcache) = actionlist.get(action) or \
- actionlist.get('*', (None, {}))
+ actionlist.get('*', (None, {}))
if not keylist and not args:
return None
@@ -794,16 +808,15 @@ class Mapper(SubMapperParent):
def __lt__(self, other):
return self._keysort(self.obj, other.obj) < 0
-
+
def _keysort(self, a, b):
"""Sorts two sets of sets, to order them ideally for
matching."""
- am = a.minkeys
a = a.maxkeys
b = b.maxkeys
- lendiffa = len(keys^a)
- lendiffb = len(keys^b)
+ lendiffa = len(keys ^ a)
+ lendiffb = len(keys ^ b)
# If they both match, don't switch them
if lendiffa == 0 and lendiffb == 0:
return 0
@@ -821,15 +834,15 @@ class Mapper(SubMapperParent):
if self._compare(lendiffa, lendiffb) != 0:
return self._compare(lendiffa, lendiffb)
- # Neither matches exactly, but if they both have just as much
- # in common
- if len(keys&b) == len(keys&a):
+ # Neither matches exactly, but if they both have just as
+ # much in common
+ if len(keys & b) == len(keys & a):
# Then we return the shortest of the two
return self._compare(len(a), len(b))
# Otherwise, we return the one that has the most in common
else:
- return self._compare(len(keys&b), len(keys&a))
+ return self._compare(len(keys & b), len(keys & a))
def _compare(self, obj1, obj2):
if obj1 < obj2:
@@ -838,11 +851,11 @@ class Mapper(SubMapperParent):
return 1
else:
return 0
-
+
keylist.sort(key=KeySorter)
if cacheset:
sortcache[cachekey] = keylist
-
+
# Iterate through the keylist of sorted routes (or a single route if
# it was passed in explicitly for hardcoded named routes)
for route in keylist:
@@ -852,7 +865,8 @@ class Mapper(SubMapperParent):
if not kval:
continue
kval = as_unicode(kval, self.encoding)
- if kval != route.defaults[key] and not callable(route.defaults[key]):
+ if kval != route.defaults[key] and \
+ not callable(route.defaults[key]):
fail = True
break
if fail:
@@ -863,7 +877,7 @@ class Mapper(SubMapperParent):
path = self.prefix + path
external_static = route.static and route.external
if environ and environ.get('SCRIPT_NAME', '') != ''\
- and not route.absolute and not external_static:
+ and not route.absolute and not external_static:
path = environ['SCRIPT_NAME'] + path
key = cache_key_script_name
else:
@@ -874,113 +888,113 @@ class Mapper(SubMapperParent):
else:
continue
return None
-
+
def resource(self, member_name, collection_name, **kwargs):
"""Generate routes for a controller resource
-
+
The member_name name should be the appropriate singular version
of the resource given your locale and used with members of the
collection. The collection_name name will be used to refer to
the resource collection methods and should be a plural version
of the member_name argument. By default, the member_name name
will also be assumed to map to a controller you create.
-
- The concept of a web resource maps somewhat directly to 'CRUD'
+
+ The concept of a web resource maps somewhat directly to 'CRUD'
operations. The overlying things to keep in mind is that
mapping a resource is about handling creating, viewing, and
editing that resource.
-
+
All keyword arguments are optional.
-
+
``controller``
If specified in the keyword args, the controller will be
the actual controller used, but the rest of the naming
conventions used for the route names and URL paths are
unchanged.
-
+
``collection``
Additional action mappings used to manipulate/view the
entire set of resources provided by the controller.
-
+
Example::
-
+
map.resource('message', 'messages', collection={'rss':'GET'})
# GET /message/rss (maps to the rss action)
# also adds named route "rss_message"
-
+
``member``
Additional action mappings used to access an individual
'member' of this controllers resources.
-
+
Example::
-
+
map.resource('message', 'messages', member={'mark':'POST'})
# POST /message/1/mark (maps to the mark action)
# also adds named route "mark_message"
-
+
``new``
Action mappings that involve dealing with a new member in
the controller resources.
-
+
Example::
-
+
map.resource('message', 'messages', new={'preview':'POST'})
# POST /message/new/preview (maps to the preview action)
# also adds a url named "preview_new_message"
-
+
``path_prefix``
Prepends the URL path for the Route with the path_prefix
given. This is most useful for cases where you want to mix
resources or relations between resources.
-
+
``name_prefix``
Perpends the route names that are generated with the
name_prefix given. Combined with the path_prefix option,
it's easy to generate route names and paths that represent
resources that are in relations.
-
+
Example::
-
- map.resource('message', 'messages', controller='categories',
- path_prefix='/category/:category_id',
+
+ map.resource('message', 'messages', controller='categories',
+ path_prefix='/category/:category_id',
name_prefix="category_")
# GET /category/7/message/1
# has named route "category_message"
-
- ``parent_resource``
+
+ ``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
+ 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".
+ "<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::
-
- >>> from routes.util import url_for
- >>> m = Mapper()
- >>> m.resource('location', 'locations',
- ... parent_resource=dict(member_name='region',
+ ``parent_resource`` as "<parent member name>_".
+
+ Example::
+
+ >>> from routes.util import url_for
+ >>> 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_for('region_locations', region_id=13)
+ >>> # path_prefix is "regions/:region_id"
+ >>> # name prefix is "region_"
+ >>> url_for('region_locations', region_id=13)
'/regions/13/locations'
- >>> url_for('region_new_location', region_id=13)
+ >>> url_for('region_new_location', region_id=13)
'/regions/13/locations/new'
- >>> url_for('region_location', region_id=13, id=60)
+ >>> url_for('region_location', region_id=13, id=60)
'/regions/13/locations/60'
- >>> url_for('region_edit_location', region_id=13, id=60)
+ >>> url_for('region_edit_location', region_id=13, id=60)
'/regions/13/locations/60/edit'
Overriding generated ``path_prefix``::
@@ -1001,7 +1015,7 @@ class Mapper(SubMapperParent):
... parent_resource=dict(member_name='region',
... collection_name='regions'),
... name_prefix='')
- >>> # path_prefix is "regions/:region_id"
+ >>> # path_prefix is "regions/:region_id"
>>> url_for('locations', region_id=51)
'/regions/51/locations'
@@ -1012,26 +1026,28 @@ class Mapper(SubMapperParent):
path_prefix = kwargs.pop('path_prefix', None)
name_prefix = kwargs.pop('name_prefix', None)
parent_resource = kwargs.pop('parent_resource', None)
-
- # Generate ``path_prefix`` if ``path_prefix`` wasn't specified and
+
+ # Generate ``path_prefix`` if ``path_prefix`` wasn't specified and
# ``parent_resource`` was. Likewise for ``name_prefix``. Make sure
# that ``path_prefix`` and ``name_prefix`` *always* take precedence if
# they are specified--in particular, we need to be careful when they
# are explicitly set to "".
- if parent_resource is not None:
- if path_prefix is None:
- path_prefix = '%s/:%s_id' % (parent_resource['collection_name'],
- parent_resource['member_name'])
+ if parent_resource is not None:
+ if path_prefix is None:
+ path_prefix = '%s/:%s_id' % (parent_resource['collection_name'],
+ parent_resource['member_name'])
if name_prefix is None:
name_prefix = '%s_' % parent_resource['member_name']
else:
- if path_prefix is None: path_prefix = ''
- if name_prefix is None: name_prefix = ''
-
+ if path_prefix is None:
+ path_prefix = ''
+ if name_prefix is None:
+ name_prefix = ''
+
# Ensure the edit and new actions are in and GET
member['edit'] = 'GET'
new.update({'new': 'GET'})
-
+
# Make new dict's based off the old, except the old values become keys,
# and the old keys become items in a list as the value
def swap(dct, newdct):
@@ -1043,12 +1059,12 @@ class Mapper(SubMapperParent):
collection_methods = swap(collection, {})
member_methods = swap(member, {})
new_methods = swap(new, {})
-
+
# Insert create, update, and destroy methods
collection_methods.setdefault('POST', []).insert(0, 'create')
member_methods.setdefault('PUT', []).insert(0, 'update')
member_methods.setdefault('DELETE', []).insert(0, 'delete')
-
+
# If there's a path prefix option, use it with the controller
controller = strip_slashes(collection_name)
path_prefix = strip_slashes(path_prefix)
@@ -1060,23 +1076,23 @@ class Mapper(SubMapperParent):
collection_path = path
new_path = path + "/new"
member_path = path + "/:(id)"
-
- options = {
+
+ options = {
'controller': kwargs.get('controller', controller),
'_member_name': member_name,
'_collection_name': collection_name,
'_parent_resource': parent_resource,
'_filter': kwargs.get('_filter')
}
-
+
def requirements_for(meth):
"""Returns a new dict to be used for all route creation as the
route options"""
opts = options.copy()
- if method != 'any':
- opts['conditions'] = {'method':[meth.upper()]}
+ if method != 'any':
+ opts['conditions'] = {'method': [meth.upper()]}
return opts
-
+
# Add the routes for handling collection methods
for method, lst in collection_methods.iteritems():
primary = (method != 'GET' and lst.pop(0)) or None
@@ -1084,36 +1100,37 @@ class Mapper(SubMapperParent):
for action in lst:
route_options['action'] = action
route_name = "%s%s_%s" % (name_prefix, action, collection_name)
- self.connect("formatted_" + route_name, "%s/%s.:(format)" % \
+ self.connect("formatted_" + route_name, "%s/%s.:(format)" %
(collection_path, action), **route_options)
self.connect(route_name, "%s/%s" % (collection_path, action),
- **route_options)
+ **route_options)
if primary:
route_options['action'] = primary
self.connect("%s.:(format)" % collection_path, **route_options)
self.connect(collection_path, **route_options)
-
- # Specifically add in the built-in 'index' collection method and its
+
+ # Specifically add in the built-in 'index' collection method and its
# formatted version
- self.connect("formatted_" + name_prefix + collection_name,
- collection_path + ".:(format)", action='index',
- conditions={'method':['GET']}, **options)
- self.connect(name_prefix + collection_name, collection_path,
- action='index', conditions={'method':['GET']}, **options)
-
+ self.connect("formatted_" + name_prefix + collection_name,
+ collection_path + ".:(format)", action='index',
+ conditions={'method': ['GET']}, **options)
+ self.connect(name_prefix + collection_name, collection_path,
+ action='index', conditions={'method': ['GET']}, **options)
+
# Add the routes that deal with new resource methods
for method, lst in new_methods.iteritems():
route_options = requirements_for(method)
for action in lst:
- path = (action == 'new' and new_path) or "%s/%s" % (new_path,
- action)
name = "new_" + member_name
- if action != 'new':
- name = action + "_" + name
route_options['action'] = action
- formatted_path = (action == 'new' and new_path + '.:(format)') or \
- "%s/%s.:(format)" % (new_path, action)
- self.connect("formatted_" + name_prefix + name, formatted_path,
+ if action == 'new':
+ path = new_path
+ formatted_path = new_path + '.:(format)'
+ else:
+ path = "%s/%s" % (new_path, action)
+ name = action + "_" + name
+ formatted_path = "%s/%s.:(format)" % (new_path, action)
+ self.connect("formatted_" + name_prefix + name, formatted_path,
**route_options)
self.connect(name_prefix + name, path, **route_options)
@@ -1122,77 +1139,79 @@ class Mapper(SubMapperParent):
# Add the routes that deal with member methods of a resource
for method, lst in member_methods.iteritems():
route_options = requirements_for(method)
- route_options['requirements'] = {'id':requirements_regexp}
+ route_options['requirements'] = {'id': requirements_regexp}
if method not in ['POST', 'GET', 'any']:
primary = lst.pop(0)
else:
primary = None
for action in lst:
route_options['action'] = action
- self.connect("formatted_%s%s_%s" % (name_prefix, action,
+ self.connect("formatted_%s%s_%s" % (name_prefix, action,
member_name),
- "%s/%s.:(format)" % (member_path, action), **route_options)
+ "%s/%s.:(format)" % (member_path, action),
+ **route_options)
self.connect("%s%s_%s" % (name_prefix, action, member_name),
- "%s/%s" % (member_path, action), **route_options)
+ "%s/%s" % (member_path, action), **route_options)
if primary:
route_options['action'] = primary
self.connect("%s.:(format)" % member_path, **route_options)
self.connect(member_path, **route_options)
-
+
# Specifically add the member 'show' method
route_options = requirements_for('GET')
route_options['action'] = 'show'
- route_options['requirements'] = {'id':requirements_regexp}
- self.connect("formatted_" + name_prefix + member_name,
+ route_options['requirements'] = {'id': requirements_regexp}
+ self.connect("formatted_" + name_prefix + member_name,
member_path + ".:(format)", **route_options)
self.connect(name_prefix + member_name, member_path, **route_options)
-
+
def redirect(self, match_path, destination_path, *args, **kwargs):
"""Add a redirect route to the mapper
-
+
Redirect routes bypass the wrapped WSGI application and instead
result in a redirect being issued by the RoutesMiddleware. As
such, this method is only meaningful when using
RoutesMiddleware.
-
+
By default, a 302 Found status code is used, this can be
changed by providing a ``_redirect_code`` keyword argument
which will then be used instead. Note that the entire status
code string needs to be present.
-
+
When using keyword arguments, all arguments that apply to
matching will be used for the match, while generation specific
options will be used during generation. Thus all options
normally available to connected Routes may be used with
redirect routes as well.
-
+
Example::
-
+
map = Mapper()
map.redirect('/legacyapp/archives/{url:.*}, '/archives/{url})
- map.redirect('/home/index', '/', _redirect_code='301 Moved Permanently')
-
+ map.redirect('/home/index', '/',
+ _redirect_code='301 Moved Permanently')
+
"""
both_args = ['_encoding', '_explicit', '_minimize']
gen_args = ['_filter']
-
+
status_code = kwargs.pop('_redirect_code', '302 Found')
gen_dict, match_dict = {}, {}
-
+
# Create the dict of args for the generation route
for key in both_args + gen_args:
if key in kwargs:
gen_dict[key] = kwargs[key]
gen_dict['_static'] = True
-
+
# Create the dict of args for the matching route
for key in kwargs:
if key not in gen_args:
match_dict[key] = kwargs[key]
-
+
self.connect(match_path, **match_dict)
match_route = self.matchlist[-1]
-
+
self.connect('_redirect_%s' % id(match_route), destination_path,
**gen_dict)
match_route.redirect = True
diff --git a/routes/middleware.py b/routes/middleware.py
index d4c005e..850e06e 100644
--- a/routes/middleware.py
+++ b/routes/middleware.py
@@ -9,44 +9,45 @@ from routes.util import URLGenerator, url_for
log = logging.getLogger('routes.middleware')
+
class RoutesMiddleware(object):
"""Routing middleware that handles resolving the PATH_INFO in
addition to optionally recognizing method overriding."""
- def __init__(self, wsgi_app, mapper, use_method_override=True,
+ def __init__(self, wsgi_app, mapper, use_method_override=True,
path_info=True, singleton=True):
"""Create a Route middleware object
-
+
Using the use_method_override keyword will require Paste to be
installed, and your application should use Paste's WSGIRequest
object as it will properly handle POST issues with wsgi.input
should Routes check it.
-
+
If path_info is True, then should a route var contain
path_info, the SCRIPT_NAME and PATH_INFO will be altered
accordingly. This should be used with routes like:
-
+
.. code-block:: python
-
+
map.connect('blog/*path_info', controller='blog', path_info='')
-
+
"""
self.app = wsgi_app
self.mapper = mapper
self.singleton = singleton
self.use_method_override = use_method_override
self.path_info = path_info
- log_debug = self.log_debug = logging.DEBUG >= log.getEffectiveLevel()
+ self.log_debug = logging.DEBUG >= log.getEffectiveLevel()
if self.log_debug:
log.debug("Initialized with method overriding = %s, and path "
- "info altering = %s", use_method_override, path_info)
-
+ "info altering = %s", use_method_override, path_info)
+
def __call__(self, environ, start_response):
"""Resolves the URL in PATH_INFO, and uses wsgi.routing_args
to pass on URL resolver results."""
old_method = None
if self.use_method_override:
req = None
-
+
# In some odd cases, there's no query string
try:
qs = environ['QUERY_STRING']
@@ -59,8 +60,9 @@ class RoutesMiddleware(object):
old_method = environ['REQUEST_METHOD']
environ['REQUEST_METHOD'] = req.GET['_method'].upper()
if self.log_debug:
- log.debug("_method found in QUERY_STRING, altering request"
- " method to %s", environ['REQUEST_METHOD'])
+ log.debug("_method found in QUERY_STRING, altering "
+ "request method to %s",
+ environ['REQUEST_METHOD'])
elif environ['REQUEST_METHOD'] == 'POST' and is_form_post(environ):
if req is None:
req = Request(environ)
@@ -69,9 +71,10 @@ class RoutesMiddleware(object):
old_method = environ['REQUEST_METHOD']
environ['REQUEST_METHOD'] = req.POST['_method'].upper()
if self.log_debug:
- log.debug("_method found in POST data, altering request "
- "method to %s", environ['REQUEST_METHOD'])
-
+ log.debug("_method found in POST data, altering "
+ "request method to %s",
+ environ['REQUEST_METHOD'])
+
# Run the actual route matching
# -- Assignment of environ to config triggers route matching
if self.singleton:
@@ -86,22 +89,24 @@ class RoutesMiddleware(object):
match, route = results[0], results[1]
else:
match = route = None
-
+
if old_method:
environ['REQUEST_METHOD'] = old_method
-
+
if not match:
match = {}
if self.log_debug:
- urlinfo = "%s %s" % (environ['REQUEST_METHOD'], environ['PATH_INFO'])
+ urlinfo = "%s %s" % (environ['REQUEST_METHOD'],
+ environ['PATH_INFO'])
log.debug("No route matched for %s", urlinfo)
elif self.log_debug:
- urlinfo = "%s %s" % (environ['REQUEST_METHOD'], environ['PATH_INFO'])
+ urlinfo = "%s %s" % (environ['REQUEST_METHOD'],
+ environ['PATH_INFO'])
log.debug("Matched %s", urlinfo)
- log.debug("Route path: '%s', defaults: %s", route.routepath,
+ log.debug("Route path: '%s', defaults: %s", route.routepath,
route.defaults)
log.debug("Match dict: %s", match)
-
+
url = URLGenerator(self.mapper, environ)
environ['wsgiorg.routing_args'] = ((url), match)
environ['routes.route'] = route
@@ -112,8 +117,8 @@ class RoutesMiddleware(object):
location = url(route_name, **match)
log.debug("Using redirect route, redirect to '%s' with status"
"code: %s", location, route.redirect_status)
- start_response(route.redirect_status,
- [('Content-Type', 'text/plain; charset=utf8'),
+ start_response(route.redirect_status,
+ [('Content-Type', 'text/plain; charset=utf8'),
('Location', location)])
return []
@@ -125,11 +130,11 @@ class RoutesMiddleware(object):
environ['PATH_INFO'] = newpath
if not environ['PATH_INFO'].startswith('/'):
environ['PATH_INFO'] = '/' + environ['PATH_INFO']
- environ['SCRIPT_NAME'] += re.sub(r'^(.*?)/' + re.escape(newpath) + '$',
- r'\1', oldpath)
-
+ environ['SCRIPT_NAME'] += re.sub(
+ r'^(.*?)/' + re.escape(newpath) + '$', r'\1', oldpath)
+
response = self.app(environ, start_response)
-
+
# Wrapped in try as in rare cases the attribute will be gone already
try:
del self.mapper.environ
@@ -137,6 +142,7 @@ class RoutesMiddleware(object):
pass
return response
+
def is_form_post(environ):
"""Determine whether the request is a POSTed html form"""
content_type = environ.get('CONTENT_TYPE', '').lower()
diff --git a/routes/route.py b/routes/route.py
index 7b49c57..ea14b1f 100644
--- a/routes/route.py
+++ b/routes/route.py
@@ -11,39 +11,39 @@ from routes.util import _url_quote as url_quote, _str_encode, as_unicode
class Route(object):
"""The Route object holds a route recognition and generation
routine.
-
+
See Route.__init__ docs for usage.
-
+
"""
# reserved keys that don't count
reserved_keys = ['requirements']
-
+
# special chars to indicate a natural split in the URL
done_chars = ('/', ',', ';', '.', '#')
-
+
def __init__(self, name, routepath, **kargs):
"""Initialize a route, with a given routepath for
matching/generation
-
+
The set of keyword args will be used as defaults.
-
+
Usage::
-
+
>>> from routes.base import Route
>>> newroute = Route(None, ':controller/:action/:id')
>>> sorted(newroute.defaults.items())
[('action', 'index'), ('id', None)]
- >>> newroute = Route(None, 'date/:year/:month/:day',
+ >>> newroute = Route(None, 'date/:year/:month/:day',
... controller="blog", action="view")
- >>> newroute = Route(None, 'archives/:page', controller="blog",
+ >>> newroute = Route(None, 'archives/:page', controller="blog",
... action="by_page", requirements = { 'page':'\d{1,2}' })
>>> newroute.reqs
{'page': '\\\d{1,2}'}
-
- .. Note::
+
+ .. Note::
Route is generally not called directly, a Mapper instance
connect method should be used to add routes.
-
+
"""
self.routepath = routepath
self.sub_domains = False
@@ -55,71 +55,72 @@ class Route(object):
self.encoding = kargs.pop('_encoding', 'utf-8')
self.reqs = kargs.get('requirements', {})
self.decode_errors = 'replace'
-
+
# Don't bother forming stuff we don't need if its a static route
self.static = kargs.pop('_static', False)
self.filter = kargs.pop('_filter', None)
self.absolute = kargs.pop('_absolute', False)
-
+
# Pull out the member/collection name if present, this applies only to
# map.resource
self.member_name = kargs.pop('_member_name', None)
self.collection_name = kargs.pop('_collection_name', None)
self.parent_resource = kargs.pop('_parent_resource', None)
-
+
# Pull out route conditions
self.conditions = kargs.pop('conditions', None)
-
+
# Determine if explicit behavior should be used
self.explicit = kargs.pop('_explicit', False)
-
+
# Since static need to be generated exactly, treat them as
# non-minimized
if self.static:
self.external = '://' in self.routepath
self.minimization = False
-
+
# Strip preceding '/' if present, and not minimizing
if routepath.startswith('/') and self.minimization:
self.routepath = routepath[1:]
self._setup_route()
-
+
def _setup_route(self):
# Build our routelist, and the keys used in the route
self.routelist = routelist = self._pathkeys(self.routepath)
routekeys = frozenset([key['name'] for key in routelist
if isinstance(key, dict)])
self.dotkeys = frozenset([key['name'] for key in routelist
- if isinstance(key, dict) and
- key['type'] == '.'])
+ if isinstance(key, dict) and
+ key['type'] == '.'])
if not self.minimization:
self.make_full_route()
-
+
# Build a req list with all the regexp requirements for our args
self.req_regs = {}
for key, val in self.reqs.iteritems():
self.req_regs[key] = re.compile('^' + val + '$')
# Update our defaults and set new default keys if needed. defaults
# needs to be saved
- (self.defaults, defaultkeys) = self._defaults(routekeys,
- self.reserved_keys,
+ (self.defaults, defaultkeys) = self._defaults(routekeys,
+ self.reserved_keys,
self._kargs.copy())
# Save the maximum keys we could utilize
self.maxkeys = defaultkeys | routekeys
-
+
# Populate our minimum keys, and save a copy of our backward keys for
# quicker generation later
(self.minkeys, self.routebackwards) = self._minkeys(routelist[:])
-
- # Populate our hardcoded keys, these are ones that are set and don't
+
+ # Populate our hardcoded keys, these are ones that are set and don't
# exist in the route
- self.hardcoded = frozenset([key for key in self.maxkeys \
- if key not in routekeys and self.defaults[key] is not None])
-
+ self.hardcoded = frozenset(
+ [key for key in self.maxkeys if key not in routekeys and
+ self.defaults[key] is not None])
+
# Cache our default keys
self._default_keys = frozenset(self.defaults.keys())
-
+
def make_full_route(self):
"""Make a full routelist string for use with non-minimized
generation"""
@@ -130,7 +131,7 @@ class Route(object):
else:
regpath += part
self.regpath = regpath
-
+
def make_unicode(self, s):
"""Transform the given argument into a unicode string."""
if isinstance(s, unicode):
@@ -141,7 +142,7 @@ class Route(object):
return s
else:
return unicode(s)
-
+
def _pathkeys(self, routepath):
"""Utility function to walk the route, and pull out the valid
dynamic/wildcard keys."""
@@ -198,17 +199,17 @@ class Route(object):
def _minkeys(self, routelist):
"""Utility function to walk the route backwards
-
+
Will also determine the minimum keys we can handle to generate
a working route.
-
+
routelist is a list of the '/' split route path
defaults is a dict of all the defaults provided for the route
-
+
"""
minkeys = []
backcheck = routelist[:]
-
+
# If we don't honor minimization, we need all the keys in the
# route path
if not self.minimization:
@@ -216,7 +217,7 @@ class Route(object):
if isinstance(part, dict):
minkeys.append(part['name'])
return (frozenset(minkeys), backcheck)
-
+
gaps = False
backcheck.reverse()
for part in backcheck:
@@ -226,23 +227,23 @@ class Route(object):
elif not isinstance(part, dict):
continue
key = part['name']
- if self.defaults.has_key(key) and not gaps:
+ if key in self.defaults and not gaps:
continue
minkeys.append(key)
gaps = True
- return (frozenset(minkeys), backcheck)
-
+ return (frozenset(minkeys), backcheck)
+
def _defaults(self, routekeys, reserved_keys, kargs):
"""Creates default set with values stringified
-
+
Put together our list of defaults, stringify non-None values
and add in our action/id default if they use it and didn't
specify it.
-
+
defaultkeys is a list of the currently assumed default keys
routekeys is a list of the keys found in the route path
reserved_keys is a list of keys that are not
-
+
"""
defaults = {}
# Add in a controller/action default if they don't exist
@@ -252,59 +253,59 @@ class Route(object):
if 'action' not in routekeys and 'action' not in kargs \
and not self.explicit:
kargs['action'] = 'index'
- defaultkeys = frozenset([key for key in kargs.keys() \
+ defaultkeys = frozenset([key for key in kargs.keys()
if key not in reserved_keys])
for key in defaultkeys:
if kargs[key] is not None:
defaults[key] = self.make_unicode(kargs[key])
else:
defaults[key] = None
- if 'action' in routekeys and not defaults.has_key('action') \
+ if 'action' in routekeys and 'action' not in defaults \
and not self.explicit:
defaults['action'] = 'index'
- if 'id' in routekeys and not defaults.has_key('id') \
+ if 'id' in routekeys and 'id' not in defaults \
and not self.explicit:
defaults['id'] = None
- newdefaultkeys = frozenset([key for key in defaults.keys() \
+ newdefaultkeys = frozenset([key for key in defaults.keys()
if key not in reserved_keys])
-
+
return (defaults, newdefaultkeys)
-
+
def makeregexp(self, clist, include_names=True):
"""Create a regular expression for matching purposes
-
+
Note: This MUST be called before match can function properly.
-
- clist should be a list of valid controller strings that can be
+
+ clist should be a list of valid controller strings that can be
matched, for this reason makeregexp should be called by the web
framework after it knows all available controllers that can be
utilized.
-
+
include_names indicates whether this should be a match regexp
assigned to itself using regexp grouping names, or if names
should be excluded for use in a single larger regexp to
determine if any routes match
-
+
"""
if self.minimization:
reg = self.buildnextreg(self.routelist, clist, include_names)[0]
if not reg:
reg = '/'
reg = reg + '/?' + '$'
-
+
if not reg.startswith('/'):
reg = '/' + reg
else:
reg = self.buildfullreg(clist, include_names)
-
+
reg = '^' + reg
-
+
if not include_names:
return reg
-
+
self.regexp = reg
self.regmatch = re.compile(reg)
-
+
def buildfullreg(self, clist, include_names=True):
"""Build the regexp by iterating through the routelist and
replacing dicts with the appropriate regexp match"""
@@ -332,36 +333,37 @@ class Route(object):
regparts.append(re.escape(part))
regexp = ''.join(regparts) + '$'
return regexp
-
+
def buildnextreg(self, path, clist, include_names=True):
"""Recursively build our regexp given a path, and a controller
list.
-
+
Returns the regular expression string, and two booleans that
can be ignored as they're only used internally by buildnextreg.
-
+
"""
if path:
part = path[0]
else:
part = ''
reg = ''
-
- # noreqs will remember whether the remainder has either a string
+
+ # noreqs will remember whether the remainder has either a string
# match, or a non-defaulted regexp match on a key, allblank remembers
# if the rest could possible be completely empty
(rest, noreqs, allblank) = ('', True, True)
if len(path[1:]) > 0:
self.prior = part
- (rest, noreqs, allblank) = self.buildnextreg(path[1:], clist, include_names)
-
+ (rest, noreqs, allblank) = self.buildnextreg(path[1:], clist,
+ include_names)
+
if isinstance(part, dict) and part['type'] in (':', '.'):
var = part['name']
typ = part['type']
partreg = ''
-
+
# First we plug in the proper part matcher
- if self.reqs.has_key(var):
+ if var in self.reqs:
if include_names:
partreg = '(?P<%s>%s)' % (var, self.reqs[var])
else:
@@ -370,7 +372,8 @@ class Route(object):
partreg = '(?:\.%s)??' % partreg
elif var == 'controller':
if include_names:
- partreg = '(?P<%s>%s)' % (var, '|'.join(map(re.escape, clist)))
+ partreg = '(?P<%s>%s)' % (var, '|'.join(map(re.escape,
+ clist)))
else:
partreg = '(?:%s)' % '|'.join(map(re.escape, clist))
elif self.prior in ['/', '#']:
@@ -404,41 +407,40 @@ class Route(object):
partreg = '(?P<%s>[^%s]+?)' % (var, ''.join(rem))
else:
partreg = '(?:[^%s]+?)' % ''.join(rem)
-
- if self.reqs.has_key(var):
+
+ if var in self.reqs:
noreqs = False
- if not self.defaults.has_key(var):
+ if var not in self.defaults:
allblank = False
noreqs = False
-
- # Now we determine if its optional, or required. This changes
- # depending on what is in the rest of the match. If noreqs is
+
+ # Now we determine if its optional, or required. This changes
+ # depending on what is in the rest of the match. If noreqs is
# true, then its possible the entire thing is optional as there's
# no reqs or string matches.
if noreqs:
- # The rest is optional, but now we have an optional with a
+ # The rest is optional, but now we have an optional with a
# regexp. Wrap to ensure that if we match anything, we match
# our regexp first. It's still possible we could be completely
# blank as we have a default
- if self.reqs.has_key(var) and self.defaults.has_key(var):
+ if var in self.reqs and var in self.defaults:
reg = '(' + partreg + rest + ')?'
-
- # Or we have a regexp match with no default, so now being
+
+ # Or we have a regexp match with no default, so now being
# completely blank form here on out isn't possible
- elif self.reqs.has_key(var):
+ elif var in self.reqs:
allblank = False
reg = partreg + rest
-
+
# If the character before this is a special char, it has to be
# followed by this
- elif self.defaults.has_key(var) and \
- self.prior in (',', ';', '.'):
+ elif var in self.defaults and self.prior in (',', ';', '.'):
reg = partreg + rest
-
+
# Or we have a default with no regexp, don't touch the allblank
- elif self.defaults.has_key(var):
+ elif var in self.defaults:
reg = partreg + '?' + rest
-
+
# Or we have a key with no default, and no reqs. Not possible
# to be all blank from here
else:
@@ -448,13 +450,13 @@ class Route(object):
# matched
else:
# If they can all be blank, and we have a default here, we know
- # its safe to make everything from here optional. Since
+ # its safe to make everything from here optional. Since
# something else in the chain does have req's though, we have
# to make the partreg here required to continue matching
- if allblank and self.defaults.has_key(var):
+ if allblank and var in self.defaults:
reg = '(' + partreg + rest + ')?'
-
- # Same as before, but they can't all be blank, so we have to
+
+ # Same as before, but they can't all be blank, so we have to
# require it all to ensure our matches line up right
else:
reg = partreg + rest
@@ -465,16 +467,16 @@ class Route(object):
reg = '(?P<%s>.*)' % var + rest
else:
reg = '(?:.*)' + rest
- if not self.defaults.has_key(var):
+ if var not in self.defaults:
allblank = False
noreqs = False
else:
- if allblank and self.defaults.has_key(var):
+ if allblank and var in self.defaults:
if include_names:
reg = '(?P<%s>.*)' % var + rest
else:
reg = '(?:.*)' + rest
- elif self.defaults.has_key(var):
+ elif var in self.defaults:
if include_names:
reg = '(?P<%s>.*)' % var + rest
else:
@@ -493,53 +495,53 @@ class Route(object):
else:
allblank = False
reg = re.escape(part) + rest
-
- # We have a normal string here, this is a req, and it prevents us from
+
+ # We have a normal string here, this is a req, and it prevents us from
# being all blank
else:
noreqs = False
allblank = False
reg = re.escape(part) + rest
-
+
return (reg, noreqs, allblank)
-
- def match(self, url, environ=None, sub_domains=False,
+
+ def match(self, url, environ=None, sub_domains=False,
sub_domains_ignore=None, domain_match=''):
- """Match a url to our regexp.
-
+ """Match a url to our regexp.
+
While the regexp might match, this operation isn't
guaranteed as there's other factors that can cause a match to
fail even though the regexp succeeds (Default that was relied
on wasn't given, requirement regexp doesn't pass, etc.).
-
+
Therefore the calling function shouldn't assume this will
return a valid dict, the other possible return is False if a
match doesn't work out.
-
+
"""
# Static routes don't match, they generate only
if self.static:
return False
-
+
match = self.regmatch.match(url)
-
+
if not match:
return False
-
+
sub_domain = None
-
+
if sub_domains and environ and 'HTTP_HOST' in environ:
host = environ['HTTP_HOST'].split(':')[0]
sub_match = re.compile('^(.+?)\.%s$' % domain_match)
subdomain = re.sub(sub_match, r'\1', host)
if subdomain not in sub_domains_ignore and host != subdomain:
sub_domain = subdomain
-
+
if self.conditions:
if 'method' in self.conditions and environ and \
- environ['REQUEST_METHOD'] not in self.conditions['method']:
+ environ['REQUEST_METHOD'] not in self.conditions['method']:
return False
-
+
# Check sub-domains?
use_sd = self.conditions.get('sub_domain')
if use_sd and not sub_domain:
@@ -548,38 +550,38 @@ class Route(object):
return False
if isinstance(use_sd, list) and sub_domain not in use_sd:
return False
-
+
matchdict = match.groupdict()
result = {}
extras = self._default_keys - frozenset(matchdict.keys())
for key, val in matchdict.iteritems():
if key != 'path_info' and self.encoding:
- # change back into python unicode objects from the URL
+ # change back into python unicode objects from the URL
# representation
try:
val = as_unicode(val, self.encoding, self.decode_errors)
except UnicodeDecodeError:
return False
-
+
if not val and key in self.defaults and self.defaults[key]:
result[key] = self.defaults[key]
else:
result[key] = val
for key in extras:
result[key] = self.defaults[key]
-
+
# Add the sub-domain if there is one
if sub_domains:
result['sub_domain'] = sub_domain
-
+
# If there's a function, call it with environ and expire if it
# returns False
if self.conditions and 'function' in self.conditions and \
- not self.conditions['function'](environ, result):
+ not self.conditions['function'](environ, result):
return False
-
+
return result
-
+
def generate_non_minimized(self, kargs):
"""Generate a non-minimal version of the URL"""
# Iterate through the keys that are defaults, and NOT in the route
@@ -589,9 +591,9 @@ class Route(object):
if k not in kargs:
return False
elif self.make_unicode(kargs[k]) != \
- self.make_unicode(self.defaults[k]):
+ self.make_unicode(self.defaults[k]):
return False
-
+
# Ensure that all the args in the route path are present and not None
for arg in self.minkeys:
if arg not in kargs or kargs[arg] is None:
@@ -605,12 +607,14 @@ class Route(object):
if k in self.maxkeys:
if k in self.dotkeys:
if kargs[k]:
- kargs[k] = url_quote('.' + as_unicode(kargs[k], self.encoding), self.encoding)
+ kargs[k] = url_quote('.' + as_unicode(kargs[k],
+ self.encoding), self.encoding)
else:
- kargs[k] = url_quote(as_unicode(kargs[k], self.encoding), self.encoding)
+ kargs[k] = url_quote(as_unicode(kargs[k], self.encoding),
+ self.encoding)
return self.regpath % kargs
-
+
def generate_minimized(self, kargs):
"""Generate a minimized version of the URL"""
routelist = self.routebackwards
@@ -619,32 +623,33 @@ class Route(object):
for part in routelist:
if isinstance(part, dict) and part['type'] in (':', '.'):
arg = part['name']
-
+
# For efficiency, check these just once
- has_arg = kargs.has_key(arg)
- has_default = self.defaults.has_key(arg)
-
+ has_arg = arg in kargs
+ has_default = arg in self.defaults
+
# Determine if we can leave this part off
- # First check if the default exists and wasn't provided in the
+ # First check if the default exists and wasn't provided in the
# call (also no gaps)
if has_default and not has_arg and not gaps:
continue
-
- # Now check to see if there's a default and it matches the
+
+ # Now check to see if there's a default and it matches the
# incoming call arg
- if (has_default and has_arg) and self.make_unicode(kargs[arg]) == \
- self.make_unicode(self.defaults[arg]) and not gaps:
+ if (has_default and has_arg) and \
+ self.make_unicode(kargs[arg]) == \
+ self.make_unicode(self.defaults[arg]) and not gaps:
continue
-
- # We need to pull the value to append, if the arg is None and
+
+ # We need to pull the value to append, if the arg is None and
# we have a default, use that
if has_arg and kargs[arg] is None and has_default and not gaps:
continue
-
+
# Otherwise if we do have an arg, use that
elif has_arg:
val = kargs[arg]
-
+
elif has_default and self.defaults[arg] is not None:
val = self.defaults[arg]
# Optional format parameter?
@@ -653,7 +658,7 @@ class Route(object):
# No arg at all? This won't work
else:
return False
-
+
val = as_unicode(val, self.encoding)
urllist.append(url_quote(val, self.encoding))
if part['type'] == '.':
@@ -683,13 +688,13 @@ class Route(object):
urllist.reverse()
url = ''.join(urllist)
return url
-
+
def generate(self, _ignore_req_list=False, _append_slash=False, **kargs):
"""Generate a URL from ourself given a set of keyword arguments
-
+
Toss an exception if this
set of keywords would cause a gap in the url.
-
+
"""
# Verify that our args pass any regexp requirements
if not _ignore_req_list:
@@ -697,24 +702,24 @@ class Route(object):
val = kargs.get(key)
if val and not self.req_regs[key].match(self.make_unicode(val)):
return False
-
- # Verify that if we have a method arg, its in the method accept list.
+
+ # Verify that if we have a method arg, its in the method accept list.
# Also, method will be changed to _method for route generation
meth = as_unicode(kargs.get('method'), self.encoding)
if meth:
if self.conditions and 'method' in self.conditions \
- and meth.upper() not in self.conditions['method']:
+ and meth.upper() not in self.conditions['method']:
return False
kargs.pop('method')
-
+
if self.minimization:
url = self.generate_minimized(kargs)
else:
url = self.generate_non_minimized(kargs)
-
+
if url is False:
return url
-
+
if not url.startswith('/') and not self.static:
url = '/' + url
extras = frozenset(kargs.keys()) - self.maxkeys
@@ -733,7 +738,8 @@ class Route(object):
if isinstance(val, (tuple, list)):
for value in val:
value = as_unicode(value, self.encoding)
- fragments.append((key, _str_encode(value, self.encoding)))
+ fragments.append((key, _str_encode(value,
+ self.encoding)))
else:
val = as_unicode(val, self.encoding)
fragments.append((key, _str_encode(val, self.encoding)))
diff --git a/routes/util.py b/routes/util.py
index f7b98df..738d002 100644
--- a/routes/util.py
+++ b/routes/util.py
@@ -25,8 +25,8 @@ class GenerationException(RoutesException):
def _screenargs(kargs, mapper, environ, force_explicit=False):
"""
- Private function that takes a dict, and screens it against the current
- request dict to determine what the dict should look like that is used.
+ Private function that takes a dict, and screens it against the current
+ request dict to determine what the dict should look like that is used.
This is responsible for the requests "memory" of the current.
"""
# Coerce any unicode args with the encoding
@@ -34,14 +34,14 @@ def _screenargs(kargs, mapper, environ, force_explicit=False):
for key, val in kargs.iteritems():
if isinstance(val, unicode):
kargs[key] = val.encode(encoding)
-
+
if mapper.explicit and mapper.sub_domains and not force_explicit:
return _subdomain_check(kargs, mapper, environ)
elif mapper.explicit and not force_explicit:
return kargs
-
+
controller_name = as_unicode(kargs.get('controller'), encoding)
-
+
if controller_name and controller_name.startswith('/'):
# If the controller name starts with '/', ignore route memory
kargs['controller'] = kargs['controller'][1:]
@@ -49,22 +49,22 @@ def _screenargs(kargs, mapper, environ, force_explicit=False):
elif controller_name and not kargs.has_key('action'):
# Fill in an action if we don't have one, but have a controller
kargs['action'] = 'index'
-
+
route_args = environ.get('wsgiorg.routing_args')
if route_args:
memory_kargs = route_args[1].copy()
else:
memory_kargs = {}
-
+
# Remove keys from memory and kargs if kargs has them as None
for key in [key for key in kargs.keys() if kargs[key] is None]:
del kargs[key]
if memory_kargs.has_key(key):
del memory_kargs[key]
-
+
# Merge the new args on top of the memory args
memory_kargs.update(kargs)
-
+
# Setup a sub-domain if applicable
if mapper.sub_domains:
memory_kargs = _subdomain_check(memory_kargs, mapper, environ)
@@ -78,13 +78,13 @@ def _subdomain_check(kargs, mapper, environ):
subdomain = kargs.pop('sub_domain', None)
if isinstance(subdomain, unicode):
subdomain = str(subdomain)
-
+
fullhost = environ.get('HTTP_HOST') or environ.get('SERVER_NAME')
-
+
# In case environ defaulted to {}
if not fullhost:
return kargs
-
+
hostmatch = fullhost.split(':')
host = hostmatch[0]
port = ''
@@ -132,54 +132,54 @@ def _str_encode(string, encoding):
def url_for(*args, **kargs):
- """Generates a URL
-
- All keys given to url_for are sent to the Routes Mapper instance for
+ """Generates a URL
+
+ All keys given to url_for are sent to the Routes Mapper instance for
generation except for::
-
+
anchor specified the anchor name to be appened to the path
host overrides the default (current) host if provided
protocol overrides the default (current) protocol if provided
- qualified creates the URL with the host/port information as
+ qualified creates the URL with the host/port information as
needed
-
- The URL is generated based on the rest of the keys. When generating a new
- URL, values will be used from the current request's parameters (if
- present). The following rules are used to determine when and how to keep
+
+ The URL is generated based on the rest of the keys. When generating a new
+ URL, values will be used from the current request's parameters (if
+ present). The following rules are used to determine when and how to keep
the current requests parameters:
-
+
* If the controller is present and begins with '/', no defaults are used
- * If the controller is changed, action is set to 'index' unless otherwise
+ * If the controller is changed, action is set to 'index' unless otherwise
specified
-
+
For example, if the current request yielded a dict of
- {'controller': 'blog', 'action': 'view', 'id': 2}, with the standard
+ {'controller': 'blog', 'action': 'view', 'id': 2}, with the standard
':controller/:action/:id' route, you'd get the following results::
-
+
url_for(id=4) => '/blog/view/4',
url_for(controller='/admin') => '/admin',
url_for(controller='admin') => '/admin/view/2'
url_for(action='edit') => '/blog/edit/2',
url_for(action='list', id=None) => '/blog/list'
-
+
**Static and Named Routes**
-
- If there is a string present as the first argument, a lookup is done
+
+ If there is a string present as the first argument, a lookup is done
against the named routes table to see if there's any matching routes. The
- keyword defaults used with static routes will be sent in as GET query
+ keyword defaults used with static routes will be sent in as GET query
arg's if a route matches.
-
- If no route by that name is found, the string is assumed to be a raw URL.
+
+ If no route by that name is found, the string is assumed to be a raw URL.
Should the raw URL begin with ``/`` then appropriate SCRIPT_NAME data will
- be added if present, otherwise the string will be used as the url with
+ be added if present, otherwise the string will be used as the url with
keyword args becoming GET query args.
-
+
"""
anchor = kargs.get('anchor')
host = kargs.get('host')
protocol = kargs.get('protocol')
qualified = kargs.pop('qualified', None)
-
+
# Remove special words from kargs, convert placeholders
for key in ['anchor', 'host', 'protocol']:
if kargs.get(key):
@@ -191,16 +191,16 @@ def url_for(*args, **kargs):
url = ''
if len(args) > 0:
route = config.mapper._routenames.get(args[0])
-
+
# No named route found, assume the argument is a relative path
if not route:
static = True
url = args[0]
-
+
if url.startswith('/') and hasattr(config, 'environ') \
and config.environ.get('SCRIPT_NAME'):
url = config.environ.get('SCRIPT_NAME') + url
-
+
if static:
if kargs:
url += '?'
@@ -225,7 +225,7 @@ def url_for(*args, **kargs):
else:
match_dict = {}
environ['wsgiorg.routing_args'] = ((), match_dict)
-
+
if not static:
route_args = []
if route:
@@ -233,11 +233,11 @@ def url_for(*args, **kargs):
route_args.append(route)
newargs = route.defaults.copy()
newargs.update(kargs)
-
+
# If this route has a filter, apply it
if route.filter:
newargs = route.filter(newargs)
-
+
if not route.static:
# Handle sub-domains
newargs = _subdomain_check(newargs, config.mapper, environ)
@@ -260,7 +260,7 @@ def url_for(*args, **kargs):
protocol = config.protocol
if url is not None:
url = protocol + '://' + host + url
-
+
if not ascii_characters(url) and url is not None:
raise GenerationException("url_for can only return a string, got "
"unicode instead: %s" % url)
@@ -273,47 +273,47 @@ def url_for(*args, **kargs):
class URLGenerator(object):
"""The URL Generator generates URL's
-
+
It is automatically instantiated by the RoutesMiddleware and put
into the ``wsgiorg.routing_args`` tuple accessible as::
-
+
url = environ['wsgiorg.routing_args'][0][0]
-
+
Or via the ``routes.url`` key::
-
+
url = environ['routes.url']
-
+
The url object may be instantiated outside of a web context for use
in testing, however sub_domain support and fully qualified URL's
cannot be generated without supplying a dict that must contain the
key ``HTTP_HOST``.
-
+
"""
def __init__(self, mapper, environ):
"""Instantiate the URLGenerator
-
+
``mapper``
The mapper object to use when generating routes.
``environ``
The environment dict used in WSGI, alternately, any dict
that contains at least an ``HTTP_HOST`` value.
-
+
"""
self.mapper = mapper
if 'SCRIPT_NAME' not in environ:
environ['SCRIPT_NAME'] = ''
self.environ = environ
-
+
def __call__(self, *args, **kargs):
- """Generates a URL
+ """Generates a URL
- All keys given to url_for are sent to the Routes Mapper instance for
+ All keys given to url_for are sent to the Routes Mapper instance for
generation except for::
anchor specified the anchor name to be appened to the path
host overrides the default (current) host if provided
protocol overrides the default (current) protocol if provided
- qualified creates the URL with the host/port information as
+ qualified creates the URL with the host/port information as
needed
"""
@@ -326,18 +326,18 @@ class URLGenerator(object):
for key in ['anchor', 'host', 'protocol']:
if kargs.get(key):
del kargs[key]
-
+
route = None
use_current = '_use_current' in kargs and kargs.pop('_use_current')
-
+
static = False
encoding = self.mapper.encoding
url = ''
-
+
more_args = len(args) > 0
if more_args:
route = self.mapper._routenames.get(args[0])
-
+
if not route and more_args:
static = True
url = args[0]
@@ -366,7 +366,7 @@ class URLGenerator(object):
route_args.append(route)
newargs = route.defaults.copy()
newargs.update(kargs)
-
+
# If this route has a filter, apply it
if route.filter:
newargs = route.filter(newargs)
@@ -379,14 +379,14 @@ class URLGenerator(object):
# it
if 'sub_domain' in route.defaults:
newargs['sub_domain'] = sub
-
+
elif use_current:
newargs = _screenargs(kargs, self.mapper, self.environ, force_explicit=True)
elif 'sub_domain' in kargs:
newargs = _subdomain_check(kargs, self.mapper, self.environ)
else:
newargs = kargs
-
+
anchor = anchor or newargs.pop('_anchor', None)
host = host or newargs.pop('_host', None)
protocol = protocol or newargs.pop('_protocol', None)
@@ -398,7 +398,7 @@ class URLGenerator(object):
if 'routes.cached_hostinfo' not in self.environ:
cache_hostinfo(self.environ)
hostinfo = self.environ['routes.cached_hostinfo']
-
+
if not host and not qualified:
# Ensure we don't use a specific port, as changing the protocol
# means that we most likely need a new port
@@ -420,11 +420,11 @@ class URLGenerator(object):
"Could not generate URL. Called with args: %s %s" % \
(args, kargs))
return url
-
+
def current(self, *args, **kwargs):
"""Generate a route that includes params used on the current
request
-
+
The arguments for this method are identical to ``__call__``
except that arguments set to None will remove existing route
matches of the same name from the set of arguments used to
@@ -434,11 +434,11 @@ class URLGenerator(object):
def redirect_to(*args, **kargs):
- """Issues a redirect based on the arguments.
-
- Redirect's *should* occur as a "302 Moved" header, however the web
+ """Issues a redirect based on the arguments.
+
+ Redirect's *should* occur as a "302 Moved" header, however the web
framework may utilize a different method.
-
+
All arguments are passed to url_for to retrieve the appropriate URL, then
the resulting URL it sent to the redirect function as the URL.
"""
@@ -449,14 +449,14 @@ def redirect_to(*args, **kargs):
def cache_hostinfo(environ):
"""Processes the host information and stores a copy
-
+
This work was previously done but wasn't stored in environ, nor is
it guaranteed to be setup in the future (Routes 2 and beyond).
-
+
cache_hostinfo processes environ keys that may be present to
determine the proper host, protocol, and port information to use
when generating routes.
-
+
"""
hostinfo = {}
if environ.get('HTTPS') or environ.get('wsgi.url_scheme') == 'https' \
@@ -484,17 +484,17 @@ def controller_scan(directory=None):
"""Scan a directory for python files and use them as controllers"""
if directory is None:
return []
-
+
def find_controllers(dirname, prefix=''):
"""Locate controllers in a directory"""
controllers = []
for fname in os.listdir(dirname):
filename = os.path.join(dirname, fname)
if os.path.isfile(filename) and \
- re.match('^[^_]{1,1}.*\.py$', fname):
+ re.match('^[^_]{1,1}.*\.py$', fname):
controllers.append(prefix + fname[:-3])
elif os.path.isdir(filename):
- controllers.extend(find_controllers(filename,
+ controllers.extend(find_controllers(filename,
prefix=prefix+fname+'/'))
return controllers
controllers = find_controllers(directory)
@@ -502,16 +502,16 @@ def controller_scan(directory=None):
controllers.sort(key=len, reverse=True)
return controllers
-def as_unicode(value, encoding, errors='strict'):
+def as_unicode(value, encoding, errors='strict'):
if value is not None and isinstance(value, bytes):
return value.decode(encoding, errors)
return value
-def ascii_characters(string):
+def ascii_characters(string):
if string is None:
return True
- return all(ord(c) < 128 for c in string) \ No newline at end of file
+ return all(ord(c) < 128 for c in string)