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 # path. If its not in kargs, or doesn't match, or is None, this # route won't work for k in self.maxkeys - self.minkeys: if k not in kargs: return False elif self.make_unicode(kargs[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: if arg in self.dotkeys: kargs[arg] = '' else: return False # Encode all the argument that the regpath can use for k in kargs: 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) else: kargs[k] = url_quote(as_unicode(kargs[k], self.encoding), self.encoding) return self.regpath % kargs
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(self.make_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 = 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']: 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 if extras: if _append_slash and not url.endswith('/'): url += '/' fragments = [] # don't assume the 'extras' set preserves order: iterate # through the ordered kargs instead for key in kargs: if key not in extras: continue if key == 'action' or key == 'controller': continue val = kargs[key] if isinstance(val, (tuple, list)): for value in val: value = as_unicode(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))) if fragments: url += '?' url += urlparse.urlencode(fragments) elif _append_slash and not url.endswith('/'): url += '/' return url
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 # path. If its not in kargs, or doesn't match, or is None, this # route won't work for k in self.maxkeys - self.minkeys: if k not in kargs: return False elif self.make_unicode(kargs[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: if arg in self.dotkeys: kargs[arg] = '' else: return False # Encode all the argument that the regpath can use for k in kargs: 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) else: kargs[k] = url_quote(as_unicode(kargs[k], self.encoding), self.encoding) return self.regpath % kargs
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) or {} if 'SCRIPT_NAME' in environ: script_name = environ['SCRIPT_NAME'] elif self.environ and 'SCRIPT_NAME' in self.environ: script_name = self.environ['SCRIPT_NAME'] else: script_name = "" 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 = six.text_type(args).encode('utf8') + \ six.text_type(kargs).encode('utf8') if self.urlcache is not None: if six.PY3: cache_key_script_name = b':'.join((script_name.encode('utf-8'), cache_key)) else: cache_key_script_name = '%s:%s' % (script_name, cache_key) # Check the url cache to see if it exists, use it if it does val = self.urlcache.get(cache_key_script_name, self) if val != self: return val controller = as_unicode(controller, self.encoding) action = as_unicode(action, self.encoding) actionlist = self._gendict.get(controller) or self._gendict.get('*', {}) if not actionlist and not args: return None (keylist, sortcache) = actionlist.get(action) or \ actionlist.get('*', (None, {})) if not keylist and not args: return None keys = frozenset(kargs.keys()) cacheset = False cachekey = six.text_type(keys) cachelist = sortcache.get(cachekey) if args: keylist = args elif cachelist: keylist = cachelist else: cacheset = True newlist = [] for route in keylist: if len(route.minkeys - route.dotkeys - keys) == 0: newlist.append(route) keylist = newlist class KeySorter: def __init__(self, obj, *args): self.obj = obj 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.""" 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 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): # 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)) def _compare(self, obj1, obj2): if obj1 < obj2: return -1 elif obj1 < obj2: 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: fail = False for key in route.hardcoded: kval = kargs.get(key) if not kval: continue kval = as_unicode(kval, self.encoding) if kval != route.defaults[key] and \ not callable(route.defaults[key]): fail = True break if fail: continue path = route.generate(**kargs) if path: if self.prefix: path = self.prefix + path external_static = route.static and route.external if not route.absolute and not external_static: path = script_name + path key = cache_key_script_name else: key = cache_key if self.urlcache is not None: self.urlcache.put(key, str(path)) return str(path) else: continue 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) or {} if 'SCRIPT_NAME' in environ: script_name = environ['SCRIPT_NAME'] elif self.environ and 'SCRIPT_NAME' in self.environ: script_name = self.environ['SCRIPT_NAME'] else: script_name = "" 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 = six.text_type(args).encode('utf8') + \ six.text_type(kargs).encode('utf8') if self.urlcache is not None: if six.PY3: cache_key_script_name = b':'.join( (script_name.encode('utf-8'), cache_key)) else: cache_key_script_name = '%s:%s' % (script_name, cache_key) # Check the url cache to see if it exists, use it if it does val = self.urlcache.get(cache_key_script_name, self) if val != self: return val controller = as_unicode(controller, self.encoding) action = as_unicode(action, self.encoding) actionlist = self._gendict.get(controller) or self._gendict.get( '*', {}) if not actionlist and not args: return None (keylist, sortcache) = actionlist.get(action) or \ actionlist.get('*', (None, {})) if not keylist and not args: return None keys = frozenset(kargs.keys()) cacheset = False cachekey = six.text_type(keys) cachelist = sortcache.get(cachekey) if args: keylist = args elif cachelist: keylist = cachelist else: cacheset = True newlist = [] for route in keylist: if len(route.minkeys - route.dotkeys - keys) == 0: newlist.append(route) keylist = newlist class KeySorter: def __init__(self, obj, *args): self.obj = obj 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.""" 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 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): # 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)) def _compare(self, obj1, obj2): if obj1 < obj2: return -1 elif obj1 < obj2: 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: fail = False for key in route.hardcoded: kval = kargs.get(key) if not kval: continue kval = as_unicode(kval, self.encoding) if kval != route.defaults[key] and \ not callable(route.defaults[key]): fail = True break if fail: continue path = route.generate(**kargs) if path: if self.prefix: path = self.prefix + path external_static = route.static and route.external if not route.absolute and not external_static: path = script_name + path key = cache_key_script_name else: key = cache_key if self.urlcache is not None: self.urlcache.put(key, str(path)) return str(path) else: continue return None
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( self.make_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 = 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']: 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 if extras: if _append_slash and not url.endswith('/'): url += '/' fragments = [] # don't assume the 'extras' set preserves order: iterate # through the ordered kargs instead for key in kargs: if key not in extras: continue if key == 'action' or key == 'controller': continue val = kargs[key] if isinstance(val, (tuple, list)): for value in val: value = as_unicode(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))) if fragments: url += '?' url += urllib.urlencode(fragments) elif _append_slash and not url.endswith('/'): url += '/' return url
def generate_minimized(self, kargs): """Generate a minimized version of the URL""" routelist = self.routebackwards urllist = [] gaps = False 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) # 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 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 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? elif part['type'] == '.': continue # 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'] == '.': urllist.append('.') 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) return url
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 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']: return False # Check sub-domains? use_sd = self.conditions.get('sub_domain') if use_sd and not sub_domain: return False elif not use_sd and 'sub_domain' in self.conditions and sub_domain: 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 # 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): return False return result
def generate_minimized(self, kargs): """Generate a minimized version of the URL""" routelist = self.routebackwards urllist = [] gaps = False for part in routelist: if isinstance(part, dict) and part['type'] in (':', '.'): arg = part['name'] # For efficiency, check these just once 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 # 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 \ 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 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? elif part['type'] == '.': continue # 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'] == '.': urllist.append('.') 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) return url
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 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']: return False # Check sub-domains? use_sd = self.conditions.get('sub_domain') if use_sd and not sub_domain: return False elif not use_sd and 'sub_domain' in self.conditions and sub_domain: 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 six.iteritems(matchdict): if key != 'path_info' and self.encoding: # 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): return False return result