Skip to content

Commit

Permalink
Code cleanup and some tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
defnull committed Nov 11, 2011
1 parent ee4b91b commit fe4ae94
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 27 deletions.
28 changes: 12 additions & 16 deletions bottle.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,12 @@ def tob(data, enc='utf8'):
""" Convert anything to bytes """
return data.encode(enc) if isinstance(data, unicode) else bytes(data)

# Convert strings and unicode to native strings
if py3k:
tonat = touni
else:
tonat = tob
tonat = touni if py3k else tob
tonat.__doc__ = """ Convert anything to native strings """


# Backward compatibility
def depr(message, critical=False):
if critical: raise DeprecationWarning(message)
def depr(message):
warnings.warn(message, DeprecationWarning, stacklevel=3)


Expand Down Expand Up @@ -253,6 +248,7 @@ class Router(object):
'''

default_pattern = '[^/]+'
default_filter = 're'
#: Sorry for the mess. It works. Trust me.
rule_syntax = re.compile('(\\\\*)'\
'(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\
Expand All @@ -266,8 +262,8 @@ def __init__(self, strict=False):
self.dynamic = [] # Cache for dynamic routes. See _compile()
#: If true, static routes are no longer checked first.
self.strict_order = strict
self.modes = {'re': self.re_filter, 'int': self.int_filter,
'float': self.re_filter, 'path': self.path_filter}
self.filters = {'re': self.re_filter, 'int': self.int_filter,
'float': self.re_filter, 'path': self.path_filter}

def re_filter(self, conf):
return conf or self.default_pattern, None, None
Expand All @@ -285,10 +281,10 @@ def add_filter(self, name, func):
''' Add a filter. The provided function is called with the configuration
string as parameter and must return a (regexp, to_python, to_url) tuple.
The first element is a string, the last two are callables or None. '''
self.modes[name] = func
self.filters[name] = func

def parse_rule(self, rule):
''' Parses a rule into a (name, mode, conf) token stream. If mode is
''' Parses a rule into a (name, filter, conf) token stream. If mode is
None, name contains a static rule part. '''
offset, prefix = 0, ''
for match in self.rule_syntax.finditer(rule):
Expand All @@ -300,9 +296,9 @@ def parse_rule(self, rule):
offset = match.end()
continue
if prefix: yield prefix, None, None
name, mode, conf = g[1:4] if not g[2] is None else g[4:7]
if not mode: mode = 'default'
yield name, mode, conf or None
name, filtr, conf = g[1:4] if not g[2] is None else g[4:7]
if not filtr: filtr = self.default_filter
yield name, filtr, conf or None
offset, prefix = match.end(), ''
if offset <= len(rule) or prefix:
yield prefix+rule[offset:], None, None
Expand All @@ -325,7 +321,7 @@ def add(self, rule, method, target, name=None):
for key, mode, conf in self.parse_rule(rule):
if mode:
is_static = False
mask, in_filter, out_filter = self.modes[mode](conf)
mask, in_filter, out_filter = self.filters[mode](conf)
if key:
pattern += '(?P<%s>%s)' % (key, mask)
else:
Expand Down Expand Up @@ -1965,7 +1961,7 @@ def validate(**vkargs):
Validates and manipulates keyword arguments by user defined callables.
Handles ValueError and missing arguments by raising HTTPError(403).
"""
dept('Use route wildcard filters instead.')
depr('Use route wildcard filters instead.')
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kargs):
Expand Down
46 changes: 35 additions & 11 deletions test/test_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def testBasic(self):
self.assertMatches('/:#anon#/match', '/anon/match') # Anon wildcards
self.assertRaises(bottle.HTTPError, self.match, '//no/m/at/ch/')

def testNeewSyntax(self):
def testNewSyntax(self):
self.assertMatches('/static', '/static')
self.assertMatches('/\\<its>/<:re:.+>/<test>/<name:re:[a-z]+>/',
'/<its>/a/cruel/world/',
Expand All @@ -46,6 +46,13 @@ def testNeewSyntax(self):
self.assertMatches('/<:re:anon>/match', '/anon/match') # Anon wildcards
self.assertRaises(bottle.HTTPError, self.match, '//no/m/at/ch/')

def testValueErrorInFilter(self):
self.r.add_filter('test', lambda x: ('.*', int, int))

self.assertMatches('/int/<i:test>', '/int/5', i=5) # No tail
self.assertRaises(bottle.HTTPError, self.match, '/int/noint')


def testIntFilter(self):
self.assertMatches('/object/<id:int>', '/object/567', id=567)
self.assertRaises(bottle.HTTPError, self.match, '/object/abc')
Expand All @@ -64,23 +71,40 @@ def testErrorInPattern(self):
self.assertRaises(Exception, self.assertMatches, '/:bug#(#/', '/foo/')

def testBuild(self):
add = self.add
build = self.r.build
add, build = self.add, self.r.build
add('/:test/:name#[a-z]+#/', 'handler', name='testroute')
add('/anon/:#.#', 'handler', name='anonroute')

url = build('testroute', test='hello', name='world')
self.assertEqual('/hello/world/', url)

url = build('testroute', test='hello', name='world', q='value')
self.assertEqual('/hello/world/?q=value', url)

# RouteBuildError: Missing URL argument: 'test'
self.assertRaises(bottle.RouteBuildError, build, 'test')
# RouteBuildError: No route found with name 'test'.
self.assertRaises(bottle.RouteBuildError, build, 'testroute')
# RouteBuildError: Missing parameter 'test' in route 'testroute'

def testBuildAnon(self):
add, build = self.add, self.r.build
add('/anon/:#.#', 'handler', name='anonroute')

url = build('anonroute', 'hello')
self.assertEqual('/anon/hello', url)

url = build('anonroute', 'hello', q='value')
self.assertEqual('/anon/hello?q=value', url)

# RouteBuildError: Missing URL argument: anon0.
self.assertRaises(bottle.RouteBuildError, build, 'anonroute')
# RouteBuildError: Anonymous pattern found. Can't generate the route 'anonroute'.
url = build('anonroute', 'world')
self.assertEqual('/anon/world', url)
# RouteBuildError: Anonymous pattern found. Can't generate the route 'anonroute'.

def testBuildFilter(self):
add, build = self.add, self.r.build
add('/int/<:int>', 'handler', name='introute')

url = build('introute', '5')
self.assertEqual('/int/5', url)

# RouteBuildError: Missing URL argument: anon0.
self.assertRaises(ValueError, build, 'introute', 'hello')

def test_method(self):
#TODO Test method handling. This is done in the router now.
Expand Down

0 comments on commit fe4ae94

Please sign in to comment.