PHP Classes

File: src/python/Dromeo.py

Recommend this page to a friend!
  Classes of Nikos M.  >  Dromeo PHP Router Library  >  src/python/Dromeo.py  >  Download  
File: src/python/Dromeo.py
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Dromeo PHP Router Library
Route HTTP requests to functions with URL patterns
Author: By
Last change: v.1.2.0, contd

* namedroutes are passed on to top router
* .off() is handled recursively in subrouters
* various edits and changes
v.1.2.0 in progress

* onGroup method to group routes under common (literal) prefix (in progress)
* handle edge case in makePattern
* original matched extracts contain same defaults (if given)
* fix typo in PHP,JS in clearRoute
* update tests
Date: 3 months ago
Size: 36,728 bytes
 

Contents

Class file image Download
# -*- coding: UTF-8 -*- ## # Dromeo # Simple and Flexible Pattern Routing Framework for PHP, JavaScript, Python # @version: 1.2.0 # # https://github.com/foo123/Dromeo # ## # needed imports import re, copy #import pprint # http://www.php2python.com/wiki/function.urlencode/ # http://www.php2python.com/wiki/function.urldecode/ _urllib = 0 try: #3.x import urllib.parse _urllib = 1 def rawurlencode(s): return urllib.parse.quote(s) def rawurldecode(s): return urllib.parse.unquote(s) def urlencode(s): return urllib.parse.quote_plus(s) def urldecode(s): return urllib.parse.unquote_plus(s) except ImportError: _urllib = 0 if not _urllib: try: #2.x import urllib _urllib = 1 def rawurlencode(s): return urllib.quote(s) def rawurldecode(s): return urllib.unquote(s) def urlencode(s): return urllib.quote_plus(s) def urldecode(s): return urllib.unquote_plus(s) except ImportError: _urllib = 0 if not _urllib: def rawurlencode(s): return s def rawurldecode(s): return s def urlencode(s): return s def urldecode(s): return s def array_keys(o): if isinstance(o, (list,tuple)): return list(map(str, range(0,len(o)))) if isinstance(o, dict): return list(o.keys()) return [] def array_values(o): if isinstance(o, list): return o if isinstance(o, tuple): return list(o) if isinstance(o, dict): if is_numeric_array(o): # get values in list-order by ascending index v = [] l = len(o) i = 0 while i < l: v.append(o[str(i)]) i += 1 return v else: return list(o.values()) return [] def is_numeric_array(o): if isinstance(o,(list,tuple)): return True if isinstance(o,dict): k = array_keys(o) i = 0 l = len(k) while i < l: if str(i) not in k: return False i += 1 return True return False # (protected) global properties class _G: # http://en.wikipedia.org/wiki/List_of_HTTP_status_codes HTTP_STATUS = { # 1xx Informational 100: "Continue" ,101: "Switching Protocols" ,102: "Processing" ,103: "Early Hints" # 2xx Success ,200: "OK" ,201: "Created" ,202: "Accepted" ,203: "Non-Authoritative Information" ,204: "No Content" ,205: "Reset Content" ,206: "Partial Content" ,207: "Multi-Status" ,208: "Already Reported" ,226: "IM Used" # 3xx Redirection ,300: "Multiple Choices" ,301: "Moved Permanently" ,302: "Found" #Previously "Moved temporarily" ,303: "See Other" ,304: "Not Modified" ,305: "Use Proxy" ,306: "Switch Proxy" ,307: "Temporary Redirect" ,308: "Permanent Redirect" # 4xx Client Error ,400: "Bad Request" ,401: "Unauthorized" ,402: "Payment Required" ,403: "Forbidden" ,404: "Not Found" ,405: "Method Not Allowed" ,406: "Not Acceptable" ,407: "Proxy Authentication Required" ,408: "Request Timeout" ,409: "Conflict" ,410: "Gone" ,411: "Length Required" ,412: "Precondition Failed" ,413: "Request Entity Too Large" ,414: "Request-URI Too Long" ,415: "Unsupported Media Type" ,416: "Requested Range Not Satisfiable" ,417: "Expectation Failed" ,418: "I'm a teapot" ,419: "Authentication Timeout" ,422: "Unprocessable Entity" ,423: "Locked" ,424: "Failed Dependency" ,426: "Upgrade Required" ,428: "Precondition Required" ,429: "Too Many Requests" ,431: "Request Header Fields Too Large" ,440: "Login Timeout" ,444: "No Response" ,449: "Retry With" ,450: "Blocked by Windows Parental Controls" ,451: "Unavailable For Legal Reasons" ,494: "Request Header Too Large" ,495: "Cert Error" ,496: "No Cert" ,497: "HTTP to HTTPS" ,498: "Token expired/invalid" ,499: "Client Closed Request" # 5xx Server Error ,500: "Internal Server Error" ,501: "Not Implemented" ,502: "Bad Gateway" ,503: "Service Unavailable" ,504: "Gateway Timeout" ,505: "HTTP Version Not Supported" ,506: "Variant Also Negotiates" ,507: "Insufficient Storage" ,508: "Loop Detected" ,509: "Bandwidth Limit Exceeded" ,510: "Not Extended" ,511: "Network Authentication Required" ,520: "Origin Error" ,521: "Web server is down" ,522: "Connection timed out" ,523: "Proxy Declined Request" ,524: "A timeout occurred" ,598: "Network read timeout error" ,599: "Network connect timeout error" } uriParser = { 'php': re.compile(r'^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\?([^#]*))?(?:#(.*))?)'), 'strict': re.compile(r'^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)'), 'loose': re.compile(r'^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/\/?)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)') } uriComponent = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'] patternOr = re.compile(r'^([^|]+\|.+)$') nested = re.compile(r'\[([^\]]*?)\]$') group = re.compile(r'\((\d+)\)$') digit = re.compile(r'^\d+$') inited = False def parse_url(s, component = None, mode = 'php'): # http://www.php2python.com/wiki/function.parse-url/ global _G m = _G.uriParser[mode].match(s) uri = {} i = 14 while i > 0: i -= 1 if m.group(i): uri[_G.uriComponent[i]] = m.group(i) if 'port' in uri: uri['port'] = int(uri['port'], 10) if component: component = component.replace('PHP_URL_', '').lower() return uri[component] if component in uri else None if 'source' in uri: del uri['source'] return uri def parse_str(s): # http://www.php2python.com/wiki/function.parse-str/ global _G strArr = s.strip('&').split('&') array = {} possibleLists = [] for tmp in strArr: tmp = tmp.split('=') key = rawurldecode(tmp[0].strip()) if len(tmp) < 2: value = '' else: value = rawurldecode(tmp[1].strip()) j = key.find('\x00') if j > -1: key = key[0:j] if key and ('[' != key[0]): keys = [] postLeftBracketPos = 0 lk = len(key) for j in range(lk): if '[' == key[j] and 0 == postLeftBracketPos: postLeftBracketPos = j + 1 elif ']' == key[j]: if postLeftBracketPos: if 0 == len(keys): keys.append(key[0:postLeftBracketPos - 1]) keys.append(key[postLeftBracketPos:j]) postLeftBracketPos = 0 if j < lk-1 and '[' != key[j + 1]: break if 0 == len(keys): keys = [key] for j in range(len(key[0])): chr = keys[0][j] if ' ' == chr or '.' == chr or '[' == chr: keys[0] = keys[0][0:j] + '_' + keys[0][j + 1:] if '[' == chr: break obj = array key = None lastObj = obj lastkey = keys[len(keys)-1].strip("'\"").strip() if len(keys) else None for j in range(len(keys)): prevkey = key key = keys[j].strip("'\"") prevobj = lastObj lastObj = obj if '' != key.strip() or 0 == j: if key not in obj: obj[key] = [] if (j+1 == len(keys)-1) and (''==lastkey) else {} obj = obj[key] else: # To insert new dimension #ct = -1 #for p in obj: # if _G.digit.match(p) and int(p) > ct: ct = int(p) #key = str(ct + 1) key = True if key is True: lastObj.append(value) else: try: ikey = int(key, 10) except BaseException as exc: ikey = -1 if 0 <= ikey: possibleLists.append({'key':prevkey,'obj':prevobj}) lastObj[key] = value i = len(possibleLists)-1 while i >= 0: # safe to pass multiple times same obj, it is possible obj = possibleLists[i]['obj'][possibleLists[i]['key']] if possibleLists[i]['key'] else possibleLists[i]['obj'] if is_numeric_array(obj): obj = array_values(obj) if possibleLists[i]['key']: possibleLists[i]['obj'][possibleLists[i]['key']] = obj else: array = obj i -= 1 return array def http_build_query_helper(key, val, arg_separator, PHP_QUERY_RFC3986): encode = rawurlencode if PHP_QUERY_RFC3986 else urlencode if True == val: val = "1" elif False == val: val = "0" if val is not None: key = str(key) data = None if isinstance(val, dict): data = val.items() elif isinstance(val, (list, tuple)): data = enumerate(val) if data: tmp = [] for k,v in data: if v is not None: tmp.append(http_build_query_helper(key + "[" + str(k) + "]", v, arg_separator, PHP_QUERY_RFC3986)) return arg_separator.join(tmp) else: return encode(key) + "=" + encode(str(val)) else: return '' def http_build_query(data, arg_separator = '&', PHP_QUERY_RFC3986 = False): tmp = [ ] for key,value in data.items(): query = http_build_query_helper(key, value, arg_separator, PHP_QUERY_RFC3986) if '' != query: tmp.append(query) return arg_separator.join(tmp) def length(s): return len(s) > 0 def split(s, d1, d2 = None): if (d1==d2) or (not d2): return s.split(d1) else: parts = [] s = s.split(d1) for part in s: part = part.split(d2) parts.append(part[0]) if len(part) > 1: parts.append(part[1]) return parts def makePattern(delims, patterns, pattern): global _G numGroups = 0 types = {} pattern = split(pattern, delims[2], delims[3]) p = [] tpl = [] tplPattern = None isPattern = False for i,pt in enumerate(pattern): if isPattern: if len(pt): if pt in patterns: p.append('(' + patterns[pt][0] + ')') numGroups += 1 # typecaster if patterns[pt][1]: types[str(numGroups)] = patterns[pt][1] if tplPattern is None: tplPattern = p[len(p)-1] else: m = _G.patternOr.match(pt) if m: p.append('(' + '|'.join( map( re.escape, filter( length, m.group(1).split('|') ) ) ) + ')') numGroups += 1 if tplPattern is None: tplPattern = p[len(p)-1] elif len(pt): p.append('(' + re.escape( pt ) + ')') numGroups += 1 if tplPattern is None: tplPattern = p[len(p)-1] tpl.append(True) isPattern = False else: if len(pt): p.append(re.escape(pt)) tpl.append(pt) isPattern = True if 1 == len(p) and 1 == numGroups: types['0'] = types['1'] if '1' in types else None pat = ''.join(p) return [pat, numGroups, types, tpl, tplPattern if tplPattern else pat] else: types['0'] = None pat = '(' + ''.join(p) + ')' return [pat, numGroups+1, types, tpl, tplPattern if tplPattern else pat] def makeRoute(delims, patterns, route, method = None, prefix = None): global _G if delims[0] not in route: # literal route return [route, prefix+route if prefix and len(prefix) else route, {}, method, True, [route]] parts = split(route, delims[0], delims[1]) isPattern = False pattern = '' numGroups = 0 captures = {} tpl = [] if prefix and len(prefix): pattern += re.escape(prefix) for part in parts: if isPattern: isOptional = False isCaptured = False patternTypecaster = None # http://abc.org/{%ALFA%:user}{/%NUM%:?id(1)} p = part.split(delims[4]) if not len(p[0]): # http://abc.org/{:user}/{:?id} # assume pattern is %PART% p[0] = delims[2] + 'PART' + delims[3] capturePattern = makePattern(delims, patterns, p[0]) if len(p) > 1: captureName = p[1].strip() isOptional = (len(captureName)>0 and '?' == captureName[0]) if isOptional: captureName = captureName[1:] m = _G.group.search(captureName) if m: captureName = captureName[:-len(m.group(0))] captureIndex = int(m.group(1), 10) patternTypecaster = capturePattern[2][str(captureIndex)] if str(captureIndex) in capturePattern[2] else None if captureIndex > 0 and captureIndex < capturePattern[1]: captureIndex += numGroups + 1 else: captureIndex = numGroups + 1 else: patternTypecaster = capturePattern[2]['0'] if capturePattern[2]['0'] else None captureIndex = numGroups + 1 isCaptured = (len(captureName) > 0) pattern += capturePattern[0] numGroups += capturePattern[1] if isOptional: pattern += '?' if isCaptured: captures[captureName] = [captureIndex, patternTypecaster] if isCaptured: tpl.append({ 'name' : captureName, 'optional' : isOptional, 're' : re.compile('^' + capturePattern[4] + '$'), 'tpl' : capturePattern[3] }); isPattern = False else: pattern += re.escape(part) tpl.append(part) isPattern = True return [route, re.compile('^' + pattern + '$'), captures, method, False, tpl] def to_method(method): method = (list(map(lambda x: str(x).lower(), method)) if isinstance(method,(list,tuple)) else [str(method).lower()]) if method else ['*'] if '*' in method: method = ['*'] method = list(sorted(method)) return method def insertRoute(router, route, oneOff = False): if route and isinstance(route, dict) and ('route' in route) and isinstance(route['route'], str) and ('handler' in route) and callable(route['handler']): oneOff = (oneOff is True) handler = route['handler'] defaults = dict(route['defaults']) if 'defaults' in route else {} types = dict(route['types']) if ('types' in route) and route['types'] else None name = str(route['name']) if 'name' in route else None method = to_method(route['method'] if 'method' in route else None) route = router.key + str(route['route']) key = Route.to_key(route, method) r = None for rt in router._routes: if key == rt.key: r = rt break if not r: r = Route(router._delims, router._patterns, route, method, name, router._prefix) router._routes.append(r) router._addNamedRoute(r) r.handlers.append([ handler, defaults, types, oneOff, 0 ]) def clearRoute(router, key): l = len(router._routes)-1 while l >= 0: if key == router._routes[l].key: route = router._routes[l] del router._routes[l:l+1] router._delNamedRoute(route) route.dispose() l -= 1 def type_to_int(v): try: v = int(v, 10) except ValueError: v = 0 return 0 if not v else v # take account of nan def type_to_str(v): return v if isinstance(v, str) else str(v) def type_to_urldecode(v): return urldecode(v) def type_to_array(v): return v if isinstance(v, (list,tuple)) else [v] def type_to_params(v): return Dromeo.unglue_params(v) if isinstance(v, str) else v class Route: def to_key(route, method): return ','.join(method) + '->' + route; def __init__(self, delims, patterns, route, method, name = None, prefix = ''): self.__args__ = [delims, patterns] self.isParsed = False # lazy init self.handlers = [] self.route = str(route) if route is not None else '' self.prefix = str(prefix) if prefix is not None else '' self.method = method self.pattern = None self.captures = None self.literal = False self.namespace = None self.tpl = None self.name = str(name) if name is not None else None self.key = Route.to_key(self.route, self.method); def __del__(self): self.dispose() def dispose(self): self.__args__ = None self.isParsed = None self.handlers = None self.route = None self.prefix = None self.pattern = None self.captures = None self.tpl = None self.method = None self.literal = None self.namespace = None self.name = None self.key = None return self def parse(self): if self.isParsed: return self r = makeRoute(self.__args__[0], self.__args__[1], self.route, self.method, self.prefix) self.pattern = r[1] self.captures = r[2] self.tpl = r[5] self.literal = r[4] is True self.__args__ = None self.isParsed = True return self def match(self, route, method = '*'): if (method not in self.method) and ('*' != self.method[0]): return None if not self.isParsed: self.parse() # lazy init route = str(route) return (True if self.pattern == route else None) if self.literal else self.pattern.match(route) def make(self, params = dict(), strict = False): out = '' strict = strict is True if not self.isParsed: self.parse() # lazy init tpl = self.tpl i = 0 l = len(tpl) while i < l: tpli = tpl[i] i += 1 if isinstance(tpli,str): out += tpli else: if (tpli['name'] not in params) or (params[tpli['name']] is None): if tpli['optional']: continue else: raise RuntimeError('Dromeo: Route "'+self.name+'" (Pattern: "'+self.route+'") missing parameter "'+tpli['name']+'"!') else: param = str(params[tpli['name']]) if strict and not re.search(tpli['re'], param): raise RuntimeError('Dromeo: Route "'+self.name+'" (Pattern: "'+self.route+'") parameter "'+tpli['name']+'" value "'+param+'" does not match pattern!') part = tpli['tpl'] j = 0 k = len(part) while j < k: out += param if part[j] is True else part[j] j += 1 return out def sub(self, match, data, type = None, originalInput = None, originalKey = None): if (not self.isParsed) or self.literal: return self givenInput = match.group(0) isDifferentInput = isinstance(originalInput, str) and (originalInput != givenInput) hasOriginal = isinstance(originalKey, str) odata = {} if hasOriginal else None for v,g in self.captures.items(): groupIndex = g[0] groupTypecaster = g[1] if match.group(groupIndex): # if original input is given, # get match from original input (eg with original case) # else what matched matchedValue = match.group(groupIndex) matchedOriginalValue = originalInput[match.start(groupIndex):match.end(groupIndex)] if isDifferentInput else matchedValue if type and (v in type) and type[v]: typecaster = type[v] if isinstance(typecaster,str) and (typecaster in Dromeo.TYPES): typecaster = Dromeo.TYPES[typecaster] data[v] = typecaster(matchedValue) if callable(typecaster) else matchedValue if hasOriginal: odata[v] = typecaster(matchedOriginalValue) if callable(typecaster) else matchedOriginalValue elif groupTypecaster: typecaster = groupTypecaster data[v] = typecaster(matchedValue) if callable(typecaster) else matchedValue if hasOriginal: odata[v] = typecaster(matchedOriginalValue) if callable(typecaster) else matchedOriginalValue else: data[v] = matchedValue if hasOriginal: odata[v] = matchedOriginalValue elif v not in data: data[v] = None if hasOriginal: odata[v] = None elif hasOriginal: odata[v] = data[v] if hasOriginal: data[str(originalKey)] = odata return self class Dromeo: """ Dromeo Router for Python, https://github.com/foo123/Dromeo """ VERSION = "1.2.0" HTTP_STATUS = _G.HTTP_STATUS Route = Route to_method = to_method TYPES = { 'INTEGER' : type_to_int, 'STRING' : type_to_str, 'URLDECODE' : type_to_urldecode, 'ARRAY' : type_to_array, 'PARAMS' : type_to_params # aliases , 'INT' : type_to_int, 'STR' : type_to_str, 'VAR' : type_to_urldecode, 'URLENCODED': type_to_params } # build/glue together a uri component from a params object def glue_params(params): component = ''; # http://php.net/manual/en/function.http-build-query.php (for '+' sign convention) if params: component += http_build_query(params, '&', True) return component # unglue/extract params object from uri component def unglue_params(s): if s: PARAMS = parse_str(s) else: PARAMS = {} return PARAMS # parse and extract uri components and optional query/fragment params def parse_components(s, query_p = 'query_params', fragment_p = 'fragment_params'): COMPONENTS = {} if s: COMPONENTS = parse_url(s) if query_p: if 'query' in COMPONENTS: COMPONENTS[query_p] = Dromeo.unglue_params(COMPONENTS['query']) else: COMPONENTS[query_p] = {} if fragment_p: if 'fragment' in COMPONENTS: COMPONENTS[fragment_p] = Dromeo.unglue_params(COMPONENTS['fragment']) else: COMPONENTS[fragment_p] = {} return COMPONENTS # build a url from baseUrl plus query/hash params def build_components(baseUrl, query = None, hash = None, q = '?', h = '#'): url = '' + baseUrl if query: url += q + Dromeo.glue_params(query) if hash: url += h + Dromeo.glue_params(hash) return url def defType(type, caster): if type and caster and callable(caster): Dromeo.TYPES[type] = caster def TYPE(type): if type and (type in Dromeo.TYPES): return Dromeo.TYPES[type] return None def __init__(self, prefix = '', group = '', top = None): self._delims = ['{', '}', '%', '%', ':'] self._patterns = {} self.definePattern('ALPHA', '[a-zA-Z\\-_]+') self.definePattern('ALNUM', '[a-zA-Z0-9\\-_]+') self.definePattern('NUMBR', '[0-9]+') self.definePattern('INT', '[0-9]+', 'INT') self.definePattern('PART', '[^\\/?#]+') self.definePattern('VAR', '[^=?&#\\/]+', 'VAR') self.definePattern('QUERY', '\\?[^?#]+') self.definePattern('FRAGMENT', '#[^?#]+') self.definePattern('URLENCODED', '[^\\/?#]+', 'URLENCODED') self.definePattern('ALL', '.+') self._routes = [] self._named_routes = {} self._fallback = False self._top = top if isinstance(top, Dromeo) else self self.key = '' if self == self._top else self._top.key + str(group) self._prefix = '' if prefix is None else str(prefix) def __del__(self): self.dispose() def dispose(self): self._top = None self._delims = None self._patterns = None self._fallback = None self._prefix = None if self._routes: for r in self._routes: r.dispose() self._routes = None self._named_routes = None return self def top(self): return self._top def isTop(self): return (self._top is None) or (self == self._top) def clone(self, group = ''): cloned = Dromeo(self._prefix, group, self) cloned.defineDelimiters(self._delims) for className in self._patterns: args = self._patterns[className] cloned.definePattern(className, args[0], args[1] if 1 < len(args) else None) return cloned def reset(self): self._routes = [] self._named_routes = {} self._fallback = False return self def defineDelimiters(self, delims): if delims: l = len(delims) if l > 0 and delims[0]: self._delims[0] = delims[0] if l > 1 and delims[1]: self._delims[1] = delims[1] if l > 2 and delims[2]: self._delims[2] = delims[2] if l > 3 and delims[3]: self._delims[3] = delims[3] if l > 4 and delims[4]: self._delims[4] = delims[4] return self def definePattern(self, className, subPattern, typecaster = None): if typecaster and isinstance(typecaster, str) and typecaster in Dromeo.TYPES: typecaster = Dromeo.TYPES[typecaster] if not typecaster or not callable(typecaster): typecaster = None self._patterns[className] = [subPattern, typecaster] return self def dropPattern(self, className): if className in self._patterns: del self._patterns[className] return self def defineType(self, type, caster): Dromeo.defType(type, caster) return self #def debug(self): # print('Routes: ', pprint.pformat(self._routes, 4)) # print('Fallback: ', pprint.pformat(self._fallback, 4)) # build/glue together a uri component from a params object def glue(self, params): return Dromeo.glue_params(params) # unglue/extract params object from uri component def unglue(self, s): return Dromeo.unglue_params(s) # parse and extract uri components and optional query/fragment params def parse(self, s, query_p = 'query_params', fragment_p = 'fragment_params'): return Dromeo.parse_components(s, query_p, fragment_p) # build a url from baseUrl plus query/hash params def build(self, baseUrl, query = None, hash = None, q = '?', h = '#'): return Dromeo.build_components(baseUrl, query, hash, q, h) def redirect(self, url, httpHandler, statusCode = 302, statusMsg = True): #global _G # redirection based on python HttpServer # https://docs.python.org/3/library/http.server.html, https://wiki.python.org/moin/BaseHttpServer if url and httpHandler: if statusMsg: if True == statusMsg: if statusCode in Dromeo.HTTP_STATUS: statusMsg = Dromeo.HTTP_STATUS[statusCode] else: statusMsg = '' httpHandler.send_response(statusCode, statusMsg) else: httpHandler.send_response(statusCode) httpHandler.send_header("Location", url) httpHandler.end_headers() return self def onGroup(self, groupRoute, handler): groupRoute = str(groupRoute) if len(groupRoute) and callable(handler): groupRouter = self.clone(groupRoute) self._routes.append(groupRouter) handler(groupRouter) return self def on(self, *args): args_len = len(args) if 1 == args_len: routes = args[0] if isinstance(args[0], (list, tuple)) else [args[0]] elif 2 == args_len and isinstance(args[0], str) and callable(args[1]): routes = [{ 'route': args[0], 'handler': args[1], 'method': '*', 'defaults': {}, 'types': None }] else: routes = args for route in routes: insertRoute(self, route, False) return self def one(self, *args): args_len = len(args) if 1 == args_len: routes = args[0] if isinstance(args[0], (list, tuple)) else [args[0]] elif 2 == args_len and isinstance(args[0], str) and callable(args[1]): routes = [{ 'route': args[0], 'handler': args[1], 'method': '*', 'defaults': {}, 'types': None }] else: routes = args for route in routes: insertRoute(self, route, True) return self def off(self, route, handler = None, method = '*'): if not route: return self routes = self._routes named_routes = self._named_routes prefix = self._prefix if isinstance(route, dict): handler = route['handler'] if 'handler' in route else handler method = route['method'] if 'method' in route else method route = route['route'] if 'route' in route else None if not route: return self route = str(route) key = Route.to_key(route, to_method(method)) r = None for rt in routes: if isinstance(rt, Dromeo): rt.off(route, handler, method) else: if key == rt.key: r = rt break if not r: return self if handler and callable(handler): l = len(r.handlers)-1 while l>=0: if handler == r.handlers[l][0]: # http://www.php2python.com/wiki/function.array-splice/ del r.handlers[l:l+1] l -= 1 if not len(r.handlers): clearRoute(self, key) else: clearRoute(self, key) elif isinstance(route, str) and len(route): route = str(route) key = Route.to_key(route, to_method(method)) r = None for i,rt in enumerate(routes): if isinstance(rt, Dromeo): if route == rt.key: r = rt break else: rt.off(route, handler, method) else: if key == rt.key: r = rt break if not r: return self if isinstance(r, Dromeo): del routes[i:i+1] r.dispose() else: if handler and callable(handler): l = len(r.handlers)-1 while l>=0: if handler == r.handlers[l][0]: # http://www.php2python.com/wiki/function.array-splice/ del r.handlers[l:l+1] l -= 1 if not len(r.handlers): clearRoute(self, key) else: clearRoute(self, key) return self def fallback(self, handler = False): if handler is False or handler is None or callable(handler): self._fallback = handler return self def make(self, named_route, params = dict(), strict = False): return self._named_routes[named_route].make(params, strict) if named_route in self._named_routes else None def route(self, r, method = "*", breakOnFirstMatch = True, originalR = None, originalKey = None): if (not self.isTop()) and (not len(self._routes)): return False proceed = True found = False r = str(r) if r is not None else '' prefix = self._prefix + self.key if len(prefix): proceed = (prefix == r[0:len(prefix)]) if proceed: breakOnFirstMatch = breakOnFirstMatch is not False method = str(method).lower() if method else "*" routes = self._routes[:] # copy, avoid mutation for route in routes: if isinstance(route, Dromeo): # group router match = route.route(r, method, breakOnFirstMatch, originalR, originalKey) if not match: continue found = True else: # simple route match = route.match(r, method) if not match: continue found = True # copy handlers avoid mutation during calls handlers = route.handlers[:] # make calls to_remove = [] for h in range(len(handlers)): handler = handlers[h] # handler is oneOff and already called if handler[3] and handler[4]: to_remove.insert(0, h) continue defaults = handler[1] type = handler[2] params = { 'route': r, 'method': method, 'pattern': route.route, 'fallback': False, 'data': copy.deepcopy(defaults) } if isinstance(originalR, str): params['route_original'] = originalR route.sub(match, params['data'], type, originalR, originalKey) handler[4] = 1 # handler called if handler[3]: to_remove.insert(0, h) handler[0](params) # remove called oneOffs for h in to_remove: del route.handlers[h:h+1] if not len(route.handlers): clearRoute(self, route.key) if breakOnFirstMatch: return True if found: return True if self._fallback and self.isTop(): self._fallback({ 'route': r, 'method': method, 'pattern': None, 'fallback': True, 'data': None }) return False def _addNamedRoute(self, route): if self.isTop(): if isinstance(route, Dromeo.Route) and route.name and len(route.name): self._named_routes[route.name] = route else: self.top()._addNamedRoute(route) return self def _delNamedRoute(self, route): if self.isTop(): if isinstance(route, Dromeo.Route) and route.name and (route.name in self._named_routes): del self._named_routes[route.name] else: self.top()._delNamedRoute(route) return self # if used with 'import *' __all__ = ['Dromeo']