def __init__(self, endpoint_): if self.var_pat is None: Route.var_pat = self.var_pat = re.compile(r'{(.+?)}') self.endpoint = endpoint_ del endpoint_ if not self.endpoint.route.startswith('/'): raise RouteError('A route must start with /, %s does not' % self.endpoint.route) parts = filter(None, self.endpoint.route.split('/')) matchers = self.matchers = [] self.defaults = {} found_optional_part = False self.soak_up_extra = False self.type_checkers = self.endpoint.types.copy() def route_error(msg): return RouteError('%s is not valid: %s' % (self.endpoint.route, msg)) for i, p in enumerate(parts): if p[0] == '{': if p[-1] != '}': raise route_error( 'Invalid route, variable components must be in a {}') name = p[1:-1] is_sponge = name.startswith('+') if is_sponge: if p is not parts[-1]: raise route_error( 'Can only specify + in the last component') name = name[1:] if '=' in name: found_optional_part = i name, default = name.partition('=')[::2] if '{' in default or '}' in default: raise route_error( 'The characters {} are not allowed in default values' ) default = self.defaults[name] = eval(default) if isinstance(default, numbers.Number): self.type_checkers[name] = type(default) if is_sponge and not isinstance(default, type('')): raise route_error( 'Soak up path component must have a default value of string type' ) else: if found_optional_part is not False: raise route_error( 'Cannot have non-optional path components after optional ones' ) if is_sponge: self.soak_up_extra = name matchers.append((name, True)) else: if found_optional_part is not False: raise route_error( 'Cannot have non-optional path components after optional ones' ) matchers.append((None, p.__eq__)) self.names = [n for n, m in matchers if n is not None] self.all_names = frozenset(self.names) self.required_names = self.all_names - frozenset(self.defaults) argspec = inspect.getargspec(self.endpoint) if len(self.names) + 2 != len(argspec.args) - len(argspec.defaults or ()): raise route_error('Function must take %d non-default arguments' % (len(self.names) + 2)) if argspec.args[2:len(self.names) + 2] != self.names: raise route_error( 'Function\'s argument names do not match the variable names in the route' ) if not frozenset(self.type_checkers).issubset(frozenset(self.names)): raise route_error( 'There exist type checkers that do not correspond to route variables: %r' % (set(self.type_checkers) - set(self.names))) self.min_size = found_optional_part if found_optional_part is not False else len( matchers) self.max_size = sys.maxsize if self.soak_up_extra else len(matchers)
def route_error(msg): return RouteError('%s is not valid: %s' % (self.endpoint.route, msg))