PKD468vØŒ‘))EGG-INFO/SOURCES.txtCHANGELOG INSTALL LICENSE README setup.cfg setup.py Routes.egg-info/PKG-INFO Routes.egg-info/SOURCES.txt Routes.egg-info/dependency_links.txt Routes.egg-info/not-zip-safe Routes.egg-info/top_level.txt docs/index.txt docs/integration.txt docs/manual.txt docs/recipes.txt docs/community/index.txt docs/docs/index.txt docs/download/index.txt docs/pudge_template/SyntaxHighlighter.css docs/pudge_template/layout.css docs/pudge_template/layout.html docs/pudge_template/pudge.css docs/pudge_template/rst.css docs/pudge_template/site.css ez_setup/README.txt ez_setup/__init__.py routes/__init__.py routes/base.py routes/middleware.py routes/threadinglocal.py routes/util.py tests/test_files/controller_files/content.py tests/test_files/controller_files/users.py tests/test_files/controller_files/admin/users.py tests/test_functional/__init__.py tests/test_functional/test_generation.py tests/test_functional/test_middleware.py tests/test_functional/test_recognition.py tests/test_functional/test_utils.py tests/test_units/test_base.py tests/test_units/test_environment.py PKB468“×2EGG-INFO/dependency_links.txt PKB468¼?ÔØØEGG-INFO/PKG-INFOMetadata-Version: 1.0 Name: Routes Version: 1.7 Summary: Routing Recognition and Generation Tools Home-page: http://routes.groovie.org/ Author: Ben Bangert Author-email: ben@groovie.org License: UNKNOWN Description: A Routing package for Python that matches URL's to dicts and vice versa `Dev version available `_ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Software Development :: Libraries :: Python Modules PKB468g1`EGG-INFO/top_level.txtroutes PK%6“×2EGG-INFO/not-zip-safe PKãuŠ6olnnroutes/__init__.py"""Provides common classes and functions most users will want access to.""" import threadinglocal, sys class _RequestConfig(object): """ RequestConfig thread-local singleton The Routes RequestConfig object is a thread-local singleton that should be initialized by the web framework that is utilizing Routes. """ __shared_state = threadinglocal.local() def __getattr__(self, name): return getattr(self.__shared_state, name) def __setattr__(self, name, value): """ If the name is environ, load the wsgi envion with load_wsgi_environ and set the environ """ if name == 'environ': 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. Also, match the incoming URL if there's already a mapper, and store the resulting match dict in mapper_dict. """ if environ.get('HTTPS') or environ.get('wsgi.url_scheme') == 'https': self.__shared_state.protocol = 'https' else: self.__shared_state.protocol = 'http' if hasattr(self, 'mapper'): self.mapper.environ = environ if 'PATH_INFO' in environ and hasattr(self, 'mapper'): mapper = self.mapper path = environ['PATH_INFO'] result = mapper.routematch(path) if result is not None: self.__shared_state.mapper_dict = result[0] self.__shared_state.route = result[1] else: self.__shared_state.mapper_dict = None self.__shared_state.route = None if environ.get('HTTP_HOST'): self.__shared_state.host = environ['HTTP_HOST'] else: self.__shared_state.host = environ['SERVER_NAME'] if environ['wsgi.url_scheme'] == 'https': if environ['SERVER_PORT'] != '443': self.__shared_state.host += ':' + environ['SERVER_PORT'] else: 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 host is the hostname of the webapp protocol protocol is the protocol of the current request mapper_dict mapper_dict should be the dict returned by mapper.match() 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 will be stripped before matching, and prepended on generation 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 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 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 avoid repeat assignments of the request specific callable. Should you want the original object, perhaps to change the callable its using or stop this behavior, call request_config(original=True). """ obj = _RequestConfig() if hasattr(obj, 'request_local') and original is False: return getattr(obj, 'request_local')() else: obj.using_request_local = False return _RequestConfig() from base import Mapper from util import url_for, redirect_to __all__=['Mapper', 'url_for', 'redirect_to', 'request_config'] PKÊh²4 Ð 6¯¯routes/threadinglocal.pytry: import threading except ImportError: # No threads, so "thread local" means process-global class local(object): pass else: try: local = threading.local except AttributeError: # Added in 2.4, but now we'll have to define it ourselves import thread class local(object): def __init__(self): self.__dict__['__objs'] = {} def __getattr__(self, attr, g=thread.get_ident): try: return self.__dict__['__objs'][g()][attr] except KeyError: raise AttributeError( "No variable %s defined for the thread %s" % (attr, g())) def __setattr__(self, attr, value, g=thread.get_ident): self.__dict__['__objs'].setdefault(g(), {})[attr] = value def __delattr__(self, attr, g=thread.get_ident): try: del self.__dict__['__objs'][g()][attr] except KeyError: raise AttributeError( "No variable %s defined for thread %s" % (attr, g())) PKˆa·6h‚â|³$³$routes/util.py"""Utility functions for use in templates / controllers *PLEASE NOTE*: Many of these functions expect an initialized RequestConfig object. This is expected to have been initialized for EACH REQUEST by the web framework. """ import os import re import urllib from routes import request_config def _screenargs(kargs): """ 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. """ config = request_config() if config.mapper.explicit and config.mapper.sub_domains: return _subdomain_check(config, kargs) elif config.mapper.explicit: return kargs controller_name = kargs.get('controller') if controller_name and controller_name.startswith('/'): # If the controller name starts with '/', ignore route memory kargs['controller'] = kargs['controller'][1:] return kargs 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' memory_kargs = getattr(config, 'mapper_dict', {}).copy() # 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 config.mapper.sub_domains: memory_kargs = _subdomain_check(config, memory_kargs) return memory_kargs def _subdomain_check(config, kargs): """Screen the kargs for a subdomain and alter it appropriately depending on the current subdomain or lack therof.""" if config.mapper.sub_domains: subdomain = kargs.pop('sub_domain', None) fullhost = config.environ.get('HTTP_HOST') or \ config.environ.get('SERVER_NAME') hostmatch = fullhost.split(':') host = hostmatch[0] port = '' if len(hostmatch) > 1: port += ':' + hostmatch[1] sub_match = re.compile('^.+?\.(%s)$' % config.mapper.domain_match) domain = re.sub(sub_match, r'\1', host) if subdomain and not host.startswith(subdomain) and \ subdomain not in config.mapper.sub_domains_ignore: kargs['_host'] = subdomain + '.' + domain + port elif (subdomain in config.mapper.sub_domains_ignore or \ subdomain is None) and domain != host: kargs['_host'] = domain + port return kargs else: return kargs def _url_quote(string, encoding): """A Unicode handling version of urllib.quote_plus.""" if encoding: return urllib.quote_plus(unicode(string).encode(encoding), '/') else: return urllib.quote_plus(str(string), '/') def url_for(*args, **kargs): """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 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 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 specified For example, if the current request yielded a dict of {'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 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 arg's if a route matches. 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 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): del kargs[key] if kargs.has_key(key+'_'): kargs[key] = kargs.pop(key+'_') config = request_config() route = None static = False encoding = config.mapper.encoding url = '' if len(args) > 0: route = config.mapper._routenames.get(args[0]) if route and route.defaults.has_key('_static'): static = True url = route.routepath # 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 += '?' query_args = [] for key, val in kargs.iteritems(): query_args.append("%s=%s" % ( urllib.quote_plus(unicode(key).encode(encoding)), urllib.quote_plus(unicode(val).encode(encoding)))) url += '&'.join(query_args) if not static: if route: newargs = route.defaults.copy() newargs.update(kargs) # If this route has a filter, apply it if route.filter: newargs = route.filter(newargs) # Handle sub-domains newargs = _subdomain_check(config, newargs) else: newargs = _screenargs(kargs) anchor = newargs.pop('_anchor', None) or anchor host = newargs.pop('_host', None) or host protocol = newargs.pop('_protocol', None) or protocol url = config.mapper.generate(**newargs) if anchor: url += '#' + _url_quote(anchor, encoding) if host or protocol or qualified: 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 host = config.host.split(':')[0] elif not host: host = config.host if not protocol: protocol = config.protocol if url is not None: url = protocol + '://' + host + url if not isinstance(url, str) and url is not None: raise Exception("url_for can only return a string or None, got " " unicode instead: %s" % url) return url def redirect_to(*args, **kargs): """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. """ target = url_for(*args, **kargs) config = request_config() return config.redirect(target) 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): controllers.append(prefix + fname[:-3]) elif os.path.isdir(filename): controllers.extend(find_controllers(filename, prefix=prefix+fname+'/')) return controllers def longest_first(fst, lst): """Compare the length of one string to another, shortest goes first""" return cmp(len(lst), len(fst)) controllers = find_controllers(directory) controllers.sort(longest_first) return controllers class RouteException(Exception): """Tossed during Route exceptions""" pass PKH468:‰Nzû˜û˜routes/base.pyc;ò ÿŸiFc@s±dZdkZdkZdkZdklZdklZlZdk l Z ei djodk l ZndkZd„Zdefd „ƒYZd efd „ƒYZdS( sRoute and Mapper core classesN(s _url_quote(scontroller_scansRouteException(srequest_configs2.4(s ImmutableSetcCsD|idƒo|d}n|idƒo|d }n|SdS(s8Remove slashes from the beginning and end of a part/URL.s/iiÿÿÿÿN(snames startswithsendswith(sname((s/build/bdist.darwin-8.0.1-x86/egg/routes/base.pys strip_slashess sRoutecBshtZdZd„Zd„Zd„Zd„Zd„Zd„Ze e e dd„Z e e d „Z RS( ssThe Route object holds a route recognition and generation routine. See Route.__init__ docs for usage. c Ksz||_t|_t|_|iddƒ|_d|_ |i dtƒ|_ |idtƒ|_ |idtƒ|_ |idtƒ|_|idtƒ|_|id tƒ|_|id tƒ|_|id tƒ|_d g}d ddddf|_|id ƒo|d}n|i|ƒ|_} tgi}| D](}t|tƒo||dƒqGqG~ƒ}|i d hƒ|_h|_ x;|ii!ƒD]*\}}t#i$d|dƒ|i |>> from routes.base import Route >>> newroute = Route(':controller/:action/:id') >>> newroute.defaults {'action': 'index', 'id': None} >>> newroute = Route('date/:year/:month/:day', controller="blog", ... action="view") >>> newroute = Route('archives/:page', controller="blog", ... action="by_page", requirements = { 'page':'\d{1,2}' }) >>> newroute.reqs {'page': '\\d{1,2}'} .. Note:: Route is generally not called directly, a Mapper instance connect method should be used to add routes. s _encodingsutf-8sreplaces_statics_filters _absolutes _member_names_collection_names_parent_resources conditionss _explicits requirementss/s,s;s.s#isnames^s$N(-s routepathsselfsFalses sub_domainssNonespriorskargsspopsencodings decode_errorssgetsstaticsfiltersabsolutes member_namescollection_namesparent_resources conditionssexplicits reserved_keyss done_charss startswiths _pathkeyss routelists frozensetsappends_[1]skeys isinstancesdicts routekeyssreqssreq_regss iteritemssvalsrescompiles _defaultssdefaultss defaultkeyssmaxkeyss_minkeyssminkeyssroutebackwardss hardcoded( sselfs routepathskargsskeysvals defaultkeyss routekeyss_[1]s reserved_keyss routelist((s/build/bdist.darwin-8.0.1-x86/egg/routes/base.pys__init__s<     E  "  c Cs¤t}d}d}d}t}g}x6|D].}|ddgjo| o@t }t }|}t |ƒdjo|i |ƒd}qYq+|o|o7t}|djo d}qY|}|i df}q+|o ||jo||7}q+|oUt}|i td|d |ƒƒ||i jo|i |ƒnd}}}q+||7}q+W|o |i td|d |ƒƒn|o|i |ƒn|Sd S( sZUtility function to walk the route, and pull out the valid dynamic/wildcard keys.ss:s*is(s)s-stypesnameN(sFalses collectingscurrentsdone_onsvar_types just_starteds routelists routepathscharsTrueslensappendsselfs done_charssdict( sselfs routepaths collectingscharsvar_typesdone_onscurrents routelists just_started((s/build/bdist.darwin-8.0.1-x86/egg/routes/base.pys _pathkeysosH    cCsÇg}|}t}|iƒx“|D]‹}t|tƒ o ||i jo t }q$nt|tƒ oq$n|d}|i i|ƒo| oq$n|i|ƒt }q$Wt|ƒ|fSdS(s&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 snameN(sminkeyss routelists backchecksFalsesgapssreversesparts isinstancesdictsselfs done_charssTrueskeysdefaultsshas_keysappends frozenset(sselfs routelists backchecksminkeyssgapsskeyspart((s/build/bdist.darwin-8.0.1-x86/egg/routes/base.pys_minkeys—s$ !   c Cs´h}d|jod|jo|i od|ds)s controllers|s/s#s>[^s]+?)s >[^%s]+?)s(s)?s,s;s.s?s*s>.*)iÿÿÿÿN(spathspartsregsTruesrestsnoreqssallblankslensselfspriors buildnextregsclists isinstancesdictsvarspartregsreqsshas_keysjoinsmapsresescapes done_charssFalsesdefaults( sselfspathsclistsnoreqsspartregsrestsallblankspartsvarsreg((s/build/bdist.darwin-8.0.1-x86/egg/routes/base.pys buildnextregës† #! ! '  &,!  ,scCs|iotSn|idƒot|ƒdjo|d }n|ii|ƒ} | otSn| o h}nt }|i dƒo|og|di dƒd} tid|ƒ} ti| d| ƒ} | |jo | | jo | }qûn|ioŠ|iid ƒo|i d ƒ|id jotSn|ii d ƒ} | o| otSnt| tƒo || jotSqn| iƒ}h}t|ii ƒƒt|i ƒƒ}xÃ|i"ƒD]µ\}}|d jo|i%oJy,|ot&i'|ƒi(|i%|i)ƒ}Wq@t*j o tSq@Xn| o|ii|ƒo |i|o|i|||/:_id". If ``parent_resource`` is supplied and ``name_prefix`` isn't, ``name_prefix`` will be generated from ``parent_resource`` as "_". 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) '/regions/13/locations' >>> url_for('region_new_location', region_id=13) '/regions/13/locations/new' >>> url_for('region_location', region_id=13, id=60) '/regions/13/locations/60' >>> url_for('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_for('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_for('locations', region_id=51) '/regions/51/locations' s collectionsmembersnews path_prefixs name_prefixsparent_resources %s/:%s_idscollection_names member_names%s_ssGETseditcCsDx9|iƒD]+\}}|i|iƒgƒi|ƒq W|SdS(siSwap the keys and values in the dict, and uppercase the values from the dict during the swap.N(sdcts iteritemsskeysvalsnewdcts setdefaultsuppersappend(sdctsnewdctsvalskey((s/build/bdist.darwin-8.0.1-x86/egg/routes/base.pysswapPs  #sPOSTiscreatesPUTsupdatesDELETEsdeletes/s/news/:(id)s controllers _member_names_collection_names_parent_resourcecsAˆiƒ}ˆdjo hd|iƒg<|d>> from routes.base import Route >>> newroute = Route(':controller/:action/:id') >>> newroute.defaults {'action': 'index', 'id': None} >>> newroute = Route('date/:year/:month/:day', controller="blog", ... action="view") >>> newroute = Route('archives/:page', controller="blog", ... action="by_page", requirements = { 'page':'\d{1,2}' }) >>> newroute.reqs {'page': '\\\d{1,2}'} .. 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 self.prior = None self.encoding = kargs.pop('_encoding', 'utf-8') self.decode_errors = 'replace' # Don't bother forming stuff we don't need if its a static route self.static = kargs.get('_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) # reserved keys that don't count reserved_keys = ['requirements'] # special chars to indicate a natural split in the URL self.done_chars = ('/', ',', ';', '.', '#') # Strip preceding '/' if present if routepath.startswith('/'): routepath = routepath[1:] # Build our routelist, and the keys used in the route self.routelist = routelist = self._pathkeys(routepath) routekeys = frozenset([key['name'] for key in routelist \ if isinstance(key, dict)]) # Build a req list with all the regexp requirements for our args self.reqs = kargs.get('requirements', {}) 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, reserved_keys, kargs) # 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 # 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]) def _pathkeys(self, routepath): """Utility function to walk the route, and pull out the valid dynamic/wildcard keys.""" collecting = False current = '' done_on = '' var_type = '' just_started = False routelist = [] for char in routepath: if char in [':', '*'] and not collecting: just_started = True collecting = True var_type = char if len(current) > 0: routelist.append(current) current = '' elif collecting and just_started: just_started = False if char == '(': done_on = ')' else: current = char done_on = self.done_chars + ('-',) elif collecting and char not in done_on: current += char elif collecting: collecting = False routelist.append(dict(type=var_type, name=current)) if char in self.done_chars: routelist.append(char) done_on = var_type = current = '' else: current += char if collecting: routelist.append(dict(type=var_type, name=current)) elif current: routelist.append(current) return routelist 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[:] gaps = False backcheck.reverse() for part in backcheck: if not isinstance(part, dict) and part not in self.done_chars: gaps = True continue elif not isinstance(part, dict): continue key = part['name'] if self.defaults.has_key(key) and not gaps: continue minkeys.append(key) gaps = True 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 if 'controller' not in routekeys and 'controller' not in kargs \ and not self.explicit: kargs['controller'] = 'content' 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() \ if key not in reserved_keys]) for key in defaultkeys: if kargs[key] != None: defaults[key] = unicode(kargs[key]) else: defaults[key] = None if 'action' in routekeys and not defaults.has_key('action') \ and not self.explicit: defaults['action'] = 'index' if 'id' in routekeys and not defaults.has_key('id') \ and not self.explicit: defaults['id'] = None newdefaultkeys = frozenset([key for key in defaults.keys() \ if key not in reserved_keys]) return (defaults, newdefaultkeys) def makeregexp(self, clist): """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 matched, for this reason makeregexp should be called by the web framework after it knows all available controllers that can be utilized. """ (reg, noreqs, allblank) = self.buildnextreg(self.routelist, clist) if not reg: reg = '/' reg = reg + '(/)?' + '$' if not reg.startswith('/'): reg = '/' + reg reg = '^' + reg self.regexp = reg self.regmatch = re.compile(reg) def buildnextreg(self, path, clist): """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 # 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) if isinstance(part, dict) and part['type'] == ':': var = part['name'] partreg = '' # First we plug in the proper part matcher if self.reqs.has_key(var): partreg = '(?P<' + var + '>' + self.reqs[var] + ')' elif var == 'controller': partreg = '(?P<' + var + '>' + '|'.join(map(re.escape, clist)) partreg += ')' elif self.prior in ['/', '#']: partreg = '(?P<' + var + '>[^' + self.prior + ']+?)' else: if not rest: partreg = '(?P<' + var + '>[^%s]+?)' % '/' else: partreg = '(?P<' + var + '>[^%s]+?)' % ''.join(self.done_chars) if self.reqs.has_key(var): noreqs = False if not self.defaults.has_key(var): 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 # 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 # 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): reg = '(' + partreg + rest + ')?' # 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): 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 (',', ';', '.'): reg = partreg + rest # Or we have a default with no regexp, don't touch the allblank elif self.defaults.has_key(var): reg = partreg + '?' + rest # Or we have a key with no default, and no reqs. Not possible # to be all blank from here else: allblank = False reg = partreg + rest # In this case, we have something dangling that might need to be # 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 # 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): reg = '(' + partreg + rest + ')?' # 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 elif isinstance(part, dict) and part['type'] == '*': var = part['name'] if noreqs: if self.defaults.has_key(var): reg = '(?P<' + var + '>.*)' + rest else: reg = '(?P<' + var + '>.*)' + rest allblank = False noreqs = False else: if allblank and self.defaults.has_key(var): reg = '(?P<' + var + '>.*)' + rest elif self.defaults.has_key(var): reg = '(?P<' + var + '>.*)' + rest else: allblank = False noreqs = False reg = '(?P<' + var + '>.*)' + rest elif part and part[-1] in self.done_chars: if allblank: reg = re.escape(part[:-1]) + '(' + re.escape(part[-1]) + rest reg += ')?' else: allblank = False reg = re.escape(part) + rest # 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, sub_domains_ignore=None, domain_match=''): """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 if url.endswith('/') and len(url) > 1: url = url[:-1] match = self.regmatch.match(url) if not match: return False if not environ: environ = {} sub_domain = None if environ.get('HTTP_HOST') and sub_domains: 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 self.conditions.has_key('method') and \ environ.get('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: return False if isinstance(use_sd, list) and sub_domain not in use_sd: return False matchdict = match.groupdict() result = {} extras = frozenset(self.defaults.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 # representation try: val = val and urllib.unquote_plus(val).decode(self.encoding, self.decode_errors) except UnicodeDecodeError: return False if not val and self.defaults.has_key(key) 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 self.conditions.has_key('function') and \ not self.conditions['function'](environ, result): return False return result 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: for key in self.reqs.keys(): val = kargs.get(key) if val and not self.req_regs[key].match(unicode(val)): return False # 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 = kargs.get('method') if meth: if self.conditions and 'method' in self.conditions \ and meth.upper() not in self.conditions['method']: return False kargs.pop('method') routelist = self.routebackwards urllist = [] gaps = False for part in routelist: if isinstance(part, dict) and part['type'] == ':': arg = part['name'] # For efficiency, check these just once has_arg = kargs.has_key(arg) has_default = self.defaults.has_key(arg) # Determine if we can leave this part off # 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 # incoming call arg if (has_default and has_arg) and unicode(kargs[arg]) == \ unicode(self.defaults[arg]) and not gaps: continue # 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] # No arg at all? This won't work else: return False urllist.append(url_quote(val, self.encoding)) if has_arg: del kargs[arg] gaps = True elif isinstance(part, dict) and part['type'] == '*': arg = part['name'] kar = kargs.get(arg) if kar is not None: urllist.append(url_quote(kar, self.encoding)) gaps = True elif part and part[-1] in self.done_chars: if not gaps and part in self.done_chars: continue elif not gaps: urllist.append(part[:-1]) gaps = True else: gaps = True urllist.append(part) else: gaps = True urllist.append(part) urllist.reverse() url = ''.join(urllist) if not url.startswith('/'): url = '/' + url extras = frozenset(kargs.keys()) - self.maxkeys if extras: if _append_slash and not url.endswith('/'): url += '/' url += '?' url += urllib.urlencode([(key, kargs[key]) for key in kargs \ if key in extras and \ (key != 'action' or key != 'controller')]) elif _append_slash and not url.endswith('/'): url += '/' return url class Mapper(object): """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, always_scan=False, register=True, explicit=False): """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. ``directory`` Passed into controller_scan for the directory to scan. It 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 ``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'. """ self.matchlist = [] self.maxkeys = {} self.minkeys = {} self.urlcache = {} self._created_regs = False self._created_gens = False self.prefix = None self.req_data = threadinglocal.local() self.directory = directory self.always_scan = always_scan self.controller_scan = controller_scan self._regprefix = None self._routenames = {} self.debug = False self.append_slash = False self.sub_domains = False self.sub_domains_ignore = [] self.domain_match = '[^\.\/]+?\.[^\.\/]+' self.explicit = explicit self.encoding = 'utf-8' self.decode_errors = 'ignore' if register: config = request_config() config.mapper = self def _envget(self): return getattr(self.req_data, 'environ', None) def _envset(self, env): self.req_data.environ = env def _envdel(self): del self.req_data.environ environ = property(_envget, _envset, _envdel) 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('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') """ routename = None if len(args) > 1: routename = args[0] args = args[1:] if '_explicit' not in kargs: kargs['_explicit'] = self.explicit route = Route(*args, **kargs) # 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 self.matchlist.append(route) if routename: self._routenames[routename] = route if route.static: return exists = False for key in self.maxkeys: if key == route.maxkeys: self.maxkeys[key].append(route) exists = True break 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'): controllerlist[route.defaults['controller']] = True if route.defaults.has_key('action'): 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 for route in self.matchlist: if route.static: continue clist = controllerlist alist = actionlist if 'controller' in route.hardcoded: clist = [route.defaults['controller']] if 'action' in route.hardcoded: alist = [unicode(route.defaults['action'])] for controller in clist: for action in alist: actiondict = gendict.setdefault(controller, {}) actiondict.setdefault(action, ([], {}))[0].append(route) self._gendict = gendict self._created_gens = True def create_regs(self, clist=None): """Creates regular expressions for all connected routes""" if clist is None: if self.directory: clist = self.controller_scan(self.directory) else: clist = self.controller_scan() for key, val in self.maxkeys.iteritems(): for route in val: route.makeregexp(clist) # Create our regexp to strip the prefix if self.prefix: self._regprefix = re.compile(self.prefix + '(.*)') self._created_regs = True def _match(self, url): """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 RouteException("You must generate the regular expressions before matching.") if self.always_scan: self.create_regs() matchlog = [] if self.prefix: if re.match(self._regprefix, url): url = re.sub(self._regprefix, r'\1', url) if not url: url = '/' else: return (None, None, matchlog) for route in self.matchlist: if route.static: if self.debug: matchlog.append(dict(route=route, static=True)) continue match = route.match(url, self.environ, self.sub_domains, self.sub_domains_ignore, self.domain_match) if self.debug: matchlog.append(dict(route=route, regexp=bool(match))) if match: return (match, route, matchlog) return (None, None, matchlog) def match(self, url): """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: raise RouteException('No URL provided, the minimum URL necessary' ' to match is "/".') result = self._match(url) if self.debug: return result[0], result[1], result[2] if result[0]: return result[0] return None def routematch(self, url): """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') """ result = self._match(url) if self.debug: return result[0], result[1], result[2] if result[0]: return result[0], result[1] return None def generate(self, **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' controller = kargs.get('controller', None) action = kargs.get('action', None) # If the URL didn't depend on the SCRIPT_NAME, we'll cache it # keyed by just by kargs; otherwise we need to cache it with # both SCRIPT_NAME and kargs: cache_key = unicode(kargs).encode('utf8') if self.environ: cache_key_script_name = '%s:%s' % ( self.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]: if key in self.urlcache: return self.urlcache[key] actionlist = self._gendict.get(controller) or self._gendict.get('*') if not actionlist: return None (keylist, sortcache) = actionlist.get(action) or \ actionlist.get('*', (None, None)) if not keylist: return None keys = frozenset(kargs.keys()) cacheset = False cachekey = unicode(keys) cachelist = sortcache.get(cachekey) if cachelist: keylist = cachelist else: cacheset = True newlist = [] for route in keylist: if len(route.minkeys-keys) == 0: newlist.append(route) keylist = newlist def keysort(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) # If they both match, don't switch them if lendiffa == 0 and lendiffb == 0: return 0 # First, if a matches exactly, use it if lendiffa == 0: return -1 # Or b matches exactly, use it if lendiffb == 0: return 1 # Neither matches exactly, return the one with the most in # common if cmp(lendiffa, lendiffb) != 0: return cmp(lendiffa, lendiffb) # 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 cmp(len(a), len(b)) # Otherwise, we return the one that has the most in common else: return cmp(len(keys&b), len(keys&a)) keylist.sort(keysort) if cacheset: sortcache[cachekey] = keylist for route in keylist: fail = False for key in route.hardcoded: kval = kargs.get(key) if not kval: continue if kval != route.defaults[key]: fail = True break if fail: continue path = route.generate(**kargs) if path: if self.prefix: path = self.prefix + path if self.environ and self.environ.get('SCRIPT_NAME', '') != '' \ and not route.absolute: path = self.environ['SCRIPT_NAME'] + path key = cache_key_script_name else: key = cache_key if self.urlcache is not None: self.urlcache[key] = str(path) return str(path) 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' 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', name_prefix="category_") # GET /category/7/message/1 # has 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:: >>> 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) '/regions/13/locations' >>> url_for('region_new_location', region_id=13) '/regions/13/locations/new' >>> url_for('region_location', region_id=13, id=60) '/regions/13/locations/60' >>> url_for('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_for('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_for('locations', region_id=51) '/regions/51/locations' """ collection = kwargs.pop('collection', {}) member = kwargs.pop('member', {}) new = kwargs.pop('new', {}) 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 # ``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 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 = '' # 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): """Swap the keys and values in the dict, and uppercase the values from the dict during the swap.""" for key, val in dct.iteritems(): newdct.setdefault(val.upper(), []).append(key) return newdct 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) if path_prefix: path = path_prefix + '/' + controller else: path = controller collection_path = path new_path = path + "/new" member_path = path + "/:(id)" options = {'controller':kwargs.get('controller', controller)} options = { 'controller': kwargs.get('controller', controller), '_member_name': member_name, '_collection_name': collection_name, '_parent_resource': parent_resource, } 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()]} 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 route_options = requirements_for(method) for action in lst: route_options['action'] = action route_name = "%s%s_%s" % (name_prefix, action, collection_name) self.connect(route_name, "%s;%s" % (collection_path, action), **route_options) self.connect("formatted_" + route_name, "%s.:(format);%s" % \ (collection_path, action), **route_options) if primary: route_options['action'] = primary self.connect(collection_path, **route_options) self.connect("%s.:(format)" % collection_path, **route_options) # Specifically add in the built-in 'index' collection method and its # formatted version 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) # 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 self.connect(name_prefix + name, path, **route_options) path = (action == 'new' and new_path + '.:(format)') or \ "%s.:(format);%s" % (new_path, action) self.connect("formatted_" + name_prefix + name, path, **route_options) requirements_regexp = '[\w\-_]+' # 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} if method not in ['POST', 'GET', 'any']: primary = lst.pop(0) else: primary = None for action in lst: route_options['action'] = action self.connect("%s%s_%s" % (name_prefix, action, member_name), "%s;%s" % (member_path, action), **route_options) self.connect("formatted_%s%s_%s" % (name_prefix, action, member_name), "%s.:(format);%s" % (member_path, action), **route_options) if primary: route_options['action'] = primary 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(name_prefix + member_name, member_path, **route_options) self.connect("formatted_" + name_prefix + member_name, member_path + ".:(format)", **route_options) PKH468€¹·ÙÙroutes/middleware.pyc;ò €àMFc@srdZdkZdkZdkZydklZWnnXdklZeidƒZ de fd„ƒYZ dS(sRoutes WSGI MiddlewareN(s WSGIRequest(srequest_configsroutes.middlewaresRoutesMiddlewarecBs&tZdZeed„Zd„ZRS(stRouting middleware that handles resolving the PATH_INFO in addition to optionally recognizing method overriding.cCs;||_||_||_||_tid||ƒdS(sgCreate 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='') sEInitialized with method overriding = %s, and path info altering = %sN(swsgi_appsselfsappsmappersuse_method_overrides path_infoslogsdebug(sselfswsgi_appsmappersuse_method_overrides path_info((s5build/bdist.darwin-8.0.1-x86/egg/routes/middleware.pys__init__s     cCstƒ}|i|_t}|ioît|ƒ}d|_ d|i ddƒjo d|i jo9|d}|i di ƒ|d '/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 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 arg's if a route matches. 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 keyword args becoming GET query args. sanchorshostsprotocols qualifieds_sis_statics/senvirons SCRIPT_NAMEs?s%s=%ss&s_anchors_hosts _protocols#s:s://sBurl_for can only return a string or None, got unicode instead: %sN(0skargssgetsanchorshostsprotocolspopsNones qualifiedskeyshas_keysrequest_configsconfigsroutesFalsesstaticsmappersencodingsurlslensargss _routenamessdefaultssTrues routepaths startswithshasattrsenvirons query_argss iteritemssvalsappendsurllibs quote_plussunicodesencodesjoinscopysnewargssupdatesfilters_subdomain_checks _screenargssgenerates _url_quotessplits isinstancesstrs Exception(sargsskargssprotocolsencodingsstaticsvals query_argsshosts qualifiedskeysnewargssurlsconfigsroutesanchor((s/build/bdist.darwin-8.0.1-x86/egg/routes/util.pysurl_forTsx*    3  K      cOs)t||Ž}tƒ}|i|ƒSdS(s@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. N(surl_forsargsskargsstargetsrequest_configsconfigsredirect(sargsskargssconfigstarget((s/build/bdist.darwin-8.0.1-x86/egg/routes/util.pys redirect_toÍs csN|tjogSnd‡d†‰d„}ˆ|ƒ}|i|ƒ|SdS(s=Scan a directory for python files and use them as controllersscs´g}x£ti|ƒD]’}tii||ƒ}tii|ƒot i d|ƒo|i ||d ƒqtii |ƒo%|iˆ|d||dƒƒqqW|SdS(s!Locate controllers in a directorys^[^_]{1,1}.*\.py$iýÿÿÿsprefixs/N(s controllerssosslistdirsdirnamesfnamespathsjoinsfilenamesisfilesresmatchsappendsprefixsisdirsextendsfind_controllers(sdirnamesprefixsfilenames controllerssfname(sfind_controllers(s/build/bdist.darwin-8.0.1-x86/egg/routes/util.pysfind_controllersßs& cCstt|ƒt|ƒƒSdS(s@Compare the length of one string to another, shortest goes firstN(scmpslenslstsfst(sfstslst((s/build/bdist.darwin-8.0.1-x86/egg/routes/util.pys longest_firstësN(s directorysNonesfind_controllerss longest_firsts controllersssort(s directorys longest_firsts controllerssfind_controllers((sfind_controllerss/build/bdist.darwin-8.0.1-x86/egg/routes/util.pyscontroller_scanÚs    sRouteExceptioncBstZdZRS(sTossed during Route exceptions(s__name__s __module__s__doc__(((s/build/bdist.darwin-8.0.1-x86/egg/routes/util.pysRouteExceptionòs (s__doc__sossresurllibsroutessrequest_configs _screenargss_subdomain_checks _url_quotesurl_fors redirect_tosNonescontroller_scans ExceptionsRouteException( s _screenargssurl_fors redirect_toscontroller_scansurllibsresrequest_configsRouteExceptions _url_quotes_subdomain_checksos((s/build/bdist.darwin-8.0.1-x86/egg/routes/util.pys?s     '   y PK¢j²6i¶1routes/middleware.py"""Routes WSGI Middleware""" import re import urllib import logging try: from paste.wsgiwrappers import WSGIRequest except: pass from routes.base import request_config 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, path_info=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.use_method_override = use_method_override self.path_info = path_info log.debug("""Initialized with method overriding = %s, and path 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.""" config = request_config() config.mapper = self.mapper old_method = None if self.use_method_override: req = WSGIRequest(environ) req.errors = 'ignore' if '_method' in environ.get('QUERY_STRING', '') and \ '_method' in req.GET: old_method = environ['REQUEST_METHOD'] environ['REQUEST_METHOD'] = req.GET['_method'].upper() log.debug("_method found in QUERY_STRING, altering request" " method to %s", environ['REQUEST_METHOD']) elif environ['REQUEST_METHOD'] == 'POST' and \ 'application/x-www-form-urlencoded' in environ.get('CONTENT_TYPE', '') \ and'_method' in req.POST: old_method = environ['REQUEST_METHOD'] environ['REQUEST_METHOD'] = req.POST['_method'].upper() log.debug("_method found in POST data, altering request " "method to %s", environ['REQUEST_METHOD']) config.environ = environ match = config.mapper_dict route = config.route if old_method: environ['REQUEST_METHOD'] = old_method urlinfo = "%s %s" % (environ['REQUEST_METHOD'], environ['PATH_INFO']) if not match: match = {} log.debug("No route matched for %s", urlinfo) else: log.debug("Matched %s", urlinfo) log.debug("Route path: '%s', defaults: %s", route.routepath, route.defaults) log.debug("Match dict: %s", match) for key, val in match.iteritems(): if val and isinstance(val, basestring): match[key] = urllib.unquote_plus(val) environ['wsgiorg.routing_args'] = ((), match) environ['routes.route'] = route # If the route included a path_info attribute and it should be used to # alter the environ, we'll pull it out if self.path_info and match.get('path_info'): oldpath = environ['PATH_INFO'] newpath = match.get('path_info') or '' environ['PATH_INFO'] = newpath if not environ['PATH_INFO'].startswith('/'): environ['PATH_INFO'] = '/' + environ['PATH_INFO'] environ['SCRIPT_NAME'] += re.sub(r'^(.*?)/' + newpath + '$', r'\1', oldpath) if environ['SCRIPT_NAME'].endswith('/'): environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][:-1] response = self.app(environ, start_response) del config.environ del self.mapper.environ return response PKH468•7å€@@routes/__init__.pyc;ò ªÛFc@spdZdkZdkZdefd„ƒYZed„ZdklZdk l Z l Z ddd d gZ dS( sEProvides common classes and functions most users will want access to.Ns_RequestConfigcBs>tZdZeiƒZd„Zd„Zd„Zd„Z RS(sÁ RequestConfig thread-local singleton The Routes RequestConfig object is a thread-local singleton that should be initialized by the web framework that is utilizing Routes. cCst|i|ƒSdS(N(sgetattrsselfs_RequestConfig__shared_statesname(sselfsname((s3build/bdist.darwin-8.0.1-x86/egg/routes/__init__.pys __getattr__ scCsH|djo$|i|ƒ|ii||ƒSn|ii||ƒSdS(sq If the name is environ, load the wsgi envion with load_wsgi_environ and set the environ senvironN(snamesselfsload_wsgi_environsvalues_RequestConfig__shared_states __setattr__(sselfsnamesvalue((s3build/bdist.darwin-8.0.1-x86/egg/routes/__init__.pys __setattr__s   cCst|i|ƒdS(N(sdelattrsselfs_RequestConfig__shared_statesname(sselfsname((s3build/bdist.darwin-8.0.1-x86/egg/routes/__init__.pys __delattr__scCs™|idƒp|idƒdjod|i_n d|i_t|dƒo||i_nd|jo t|dƒoo|i}|d}|i|ƒ}|t j o$|d|i_ |d|i_ qît |i_ t |i_ n|id ƒo|d |i_ n„|d |i_ |ddjo3|d d jo|ii d |d 7_ q•n0|d djo|ii d |d 7_ ndS(sË Load the protocol/server info from the environ and store it. Also, match the incoming URL if there's already a mapper, and store the resulting match dict in mapper_dict. sHTTPSswsgi.url_schemeshttpsshttpsmappers PATH_INFOiis HTTP_HOSTs SERVER_NAMEs SERVER_PORTs443s:s80N(senvironsgetsselfs_RequestConfig__shared_statesprotocolshasattrsmapperspaths routematchsresultsNones mapper_dictsrouteshost(sselfsenvironsmappersresultspath((s3build/bdist.darwin-8.0.1-x86/egg/routes/__init__.pysload_wsgi_environs.&     "( s__name__s __module__s__doc__sthreadinglocalslocals_RequestConfig__shared_states __getattr__s __setattr__s __delattr__sload_wsgi_environ(((s3build/bdist.darwin-8.0.1-x86/egg/routes/__init__.pys_RequestConfigs    cCsNtƒ}t|dƒo |tjot|dƒƒSn t|_tƒSdS(sy 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 host is the hostname of the webapp protocol protocol is the protocol of the current request mapper_dict mapper_dict should be the dict returned by mapper.match() 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 will be stripped before matching, and prepended on generation 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 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 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 avoid repeat assignments of the request specific callable. Should you want the original object, perhaps to change the callable its using or stop this behavior, call request_config(original=True). s request_localN(s_RequestConfigsobjshasattrsoriginalsFalsesgetattrsusing_request_local(soriginalsobj((s3build/bdist.darwin-8.0.1-x86/egg/routes/__init__.pysrequest_config?s 6  (sMapper(surl_fors redirect_tosMappersurl_fors redirect_tosrequest_config( s__doc__sthreadinglocalssyssobjects_RequestConfigsFalsesrequest_configsbasesMappersutilsurl_fors redirect_tos__all__(sMappers_RequestConfigs__all__s redirect_tossyssrequest_configsurl_forsthreadinglocal((s3build/bdist.darwin-8.0.1-x86/egg/routes/__init__.pys?s ; > PKH468àñD((routes/threadinglocal.pyc;ò ©lDc@sy dkZWn)ej odefd„ƒYZnDXy eiZWn2ej o&dkZdefd„ƒYZnXdS(NslocalcBstZRS(N(s__name__s __module__(((s9build/bdist.darwin-8.0.1-x86/egg/routes/threadinglocal.pyslocalscBs>tZd„Zeid„Zeid„Zeid„ZRS(NcCsh|id