diff options
-rw-r--r-- | .travis.yml | 17 | ||||
-rw-r--r-- | routes/mapper.py | 12 | ||||
-rw-r--r-- | routes/route.py | 25 | ||||
-rw-r--r-- | routes/util.py | 4 | ||||
-rw-r--r-- | setup.py | 6 | ||||
-rw-r--r-- | tests/test_functional/test_explicit_use.py | 30 | ||||
-rw-r--r-- | tests/test_units/test_route_escapes.py | 38 | ||||
-rw-r--r-- | tox.ini | 10 |
8 files changed, 110 insertions, 32 deletions
diff --git a/.travis.yml b/.travis.yml index a38f7f3..f9a351f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,18 @@ language: python python: -- '2.6' -- '2.7' -- '3.3' -- '3.4' -- '3.5' -- '3.6' -- nightly -- pypy -- pypy3 + - '2.7' + - '3.5' + - '3.6' + - nightly + - pypy + - pypy3 matrix: allow_failures: - python: nightly install: pip install tox-travis script: tox after_success: -- codecov + - codecov deploy: provider: pypi user: bbangert diff --git a/routes/mapper.py b/routes/mapper.py index a819c7d..e88feef 100644 --- a/routes/mapper.py +++ b/routes/mapper.py @@ -490,7 +490,11 @@ class Mapper(SubMapperParent): routepath = path_prefix + route.routepath else: routepath = route.routepath - self.connect(route.name, routepath, **route._kargs) + self.connect(route.name, + routepath, + conditions=route.conditions, + **route._kargs + ) def make_route(self, *args, **kargs): """Make a new Route object @@ -511,7 +515,7 @@ class Mapper(SubMapperParent): m.connect('date/:year/:month/:day', controller="blog", action="view") m.connect('archives/:page', controller="blog", action="by_page", - requirements = { 'page':'\d{1,2}' }) + requirements = { 'page':'\\d{1,2}' }) m.connect('category_list', 'archives/category/:section', controller='blog', action='category', section='home', type='list') @@ -995,7 +999,7 @@ class Mapper(SubMapperParent): map.resource('message', 'messages', path_prefix='{project_id}/', - requirements={"project_id": R"\d+"}) + requirements={"project_id": R"\\d+"}) # POST /01234/message # success, project_id is set to "01234" # POST /foo/message @@ -1177,7 +1181,7 @@ class Mapper(SubMapperParent): **route_options) self.connect(name_prefix + name, path, **route_options) - requirements_regexp = '[^\/]+(?<!\\\)' + requirements_regexp = '[^\\/]+(?<!\\\\)' # Add the routes that deal with member methods of a resource for method, lst in six.iteritems(member_methods): diff --git a/routes/route.py b/routes/route.py index 860a911..cf6df10 100644 --- a/routes/route.py +++ b/routes/route.py @@ -1,7 +1,5 @@ import re import sys -if sys.version < '2.4': - from sets import ImmutableSet as frozenset import six from six.moves.urllib import parse as urlparse @@ -37,9 +35,9 @@ class Route(object): >>> newroute = Route(None, 'date/:year/:month/:day', ... controller="blog", action="view") >>> newroute = Route(None, 'archives/:page', controller="blog", - ... action="by_page", requirements = { 'page':'\d{1,2}' }) + ... action="by_page", requirements = { 'page':'\\d{1,2}' }) >>> newroute.reqs - {'page': '\\\d{1,2}'} + {'page': '\\\\d{1,2}'} .. Note:: Route is generally not called directly, a Mapper instance @@ -148,13 +146,22 @@ class Route(object): """Utility function to walk the route, and pull out the valid dynamic/wildcard keys.""" collecting = False + escaping = False current = '' done_on = '' var_type = '' just_started = False routelist = [] for char in routepath: - if char in [':', '*', '{'] and not collecting and not self.static \ + if escaping: + if char in ['\\', ':', '*', '{', '}']: + current += char + else: + current += '\\' + char + escaping = False + elif char == '\\': + escaping = True + elif char in [':', '*', '{'] and not collecting and not self.static \ or char in ['{'] and not collecting: just_started = True collecting = True @@ -327,7 +334,7 @@ class Route(object): else: regpart = '(?:%s)' % partmatch if part['type'] == '.': - regparts.append('(?:\.%s)??' % regpart) + regparts.append(r'(?:\.%s)??' % regpart) else: regparts.append(regpart) else: @@ -370,7 +377,7 @@ class Route(object): else: partreg = '(?:%s)' % self.reqs[var] if typ == '.': - partreg = '(?:\.%s)??' % partreg + partreg = r'(?:\.%s)??' % partreg elif var == 'controller': if include_names: partreg = '(?P<%s>%s)' % (var, '|'.join(map(re.escape, @@ -393,7 +400,7 @@ class Route(object): else: partreg = '(?:[^%s]+?)' % exclude_chars if typ == '.': - partreg = '(?:\.%s)??' % partreg + partreg = r'(?:\.%s)??' % partreg else: end = ''.join(self.done_chars) rem = rest @@ -533,7 +540,7 @@ class Route(object): if sub_domains and environ and 'HTTP_HOST' in environ: host = environ['HTTP_HOST'].split(':')[0] - sub_match = re.compile('^(.+?)\.%s$' % domain_match) + sub_match = re.compile(r'^(.+?)\.%s$' % domain_match) subdomain = re.sub(sub_match, r'\1', host) if subdomain not in sub_domains_ignore and host != subdomain: sub_domain = subdomain diff --git a/routes/util.py b/routes/util.py index c48445f..54c8951 100644 --- a/routes/util.py +++ b/routes/util.py @@ -94,7 +94,7 @@ def _subdomain_check(kargs, mapper, environ): if len(hostmatch) > 1: port += ':' + hostmatch[1] - match = re.match('^(.+?)\.(%s)$' % mapper.domain_match, host) + match = re.match(r'^(.+?)\.(%s)$' % mapper.domain_match, host) host_subdomain, domain = match.groups() if match else (None, host) subdomain = as_unicode(subdomain, mapper.encoding) @@ -512,7 +512,7 @@ def controller_scan(directory=None): for fname in os.listdir(dirname): filename = os.path.join(dirname, fname) if os.path.isfile(filename) and \ - re.match('^[^_]{1,1}.*\.py$', fname): + re.match(r'^[^_]{1,1}.*\.py$', fname): controllers.append(prefix + fname[:-3]) elif os.path.isdir(filename): controllers.extend(find_controllers(filename, @@ -43,12 +43,8 @@ setup(name="Routes", "Programming Language :: Python :: Implementation :: CPython", 'Programming Language :: Python', "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6" ], @@ -60,7 +56,7 @@ setup(name="Routes", test_suite="nose.collector", include_package_data=True, zip_safe=False, - tests_require=['nose', 'webtest', 'webob', 'coverage'], + tests_require=["soupsieve<2.0", 'nose', 'webtest', 'webob', 'coverage'], install_requires=[ "six", "repoze.lru>=0.3" diff --git a/tests/test_functional/test_explicit_use.py b/tests/test_functional/test_explicit_use.py index ccd3b7a..b1e1cd7 100644 --- a/tests/test_functional/test_explicit_use.py +++ b/tests/test_functional/test_explicit_use.py @@ -1,6 +1,6 @@ """test_explicit_use""" import os, sys, time, unittest -from nose.tools import eq_, assert_raises +from nose.tools import eq_, assert_raises, assert_is_none from routes import * from routes.route import Route @@ -101,6 +101,34 @@ class TestUtils(unittest.TestCase): map.extend(routes) eq_(map.match('/foo'), {}) + def test_add_routes_conditions_unmet(self): + map = Mapper(explicit=True) + map.minimization = False + routes = [ + Route('foo', '/foo', conditions=dict(method=["POST"])) + ] + environ = { + 'HTTP_HOST': 'localhost.com', + 'PATH_INFO': '/foo', + 'REQUEST_METHOD': 'GET', + } + map.extend(routes) + assert_is_none(map.match('/foo', environ=environ)) + + def test_add_routes_conditions_met(self): + map = Mapper(explicit=True) + map.minimization = False + routes = [ + Route('foo', '/foo', conditions=dict(method=["POST"])) + ] + environ = { + 'HTTP_HOST': 'localhost.com', + 'PATH_INFO': '/foo', + 'REQUEST_METHOD': 'POST', + } + map.extend(routes) + eq_(map.match('/foo', environ=environ), {}) + def test_using_func(self): def fred(view): pass diff --git a/tests/test_units/test_route_escapes.py b/tests/test_units/test_route_escapes.py new file mode 100644 index 0000000..5db07c4 --- /dev/null +++ b/tests/test_units/test_route_escapes.py @@ -0,0 +1,38 @@ +import unittest +from routes.route import Route + + +class TestRouteEscape(unittest.TestCase): + def test_normal_route(self): + r = Route('test', '/foo/bar') + self.assertEqual(r.routelist, ['/foo/bar']) + + def test_route_with_backslash(self): + r = Route('test', '/foo\\\\bar') + self.assertEqual(r.routelist, ['/foo\\bar']) + + def test_route_with_random_escapes(self): + r = Route('test', '\\/f\\oo\\/ba\\r') + self.assertEqual(r.routelist, ['\\/f\\oo\\/ba\\r']) + + def test_route_with_colon(self): + r = Route('test', '/foo:bar/baz') + self.assertEqual( + r.routelist, ['/foo', {'name': 'bar', 'type': ':'}, '/', 'baz']) + + def test_route_with_escaped_colon(self): + r = Route('test', '/foo\\:bar/baz') + self.assertEqual(r.routelist, ['/foo:bar/baz']) + + def test_route_with_both_colons(self): + r = Route('test', '/prefix/escaped\\:escaped/foo=:notescaped/bar=42') + self.assertEqual( + r.routelist, ['/prefix/escaped:escaped/foo=', + {'name': 'notescaped', 'type': ':'}, '/', 'bar=42']) + + def test_route_with_all_escapes(self): + r = Route('test', '/hmm\\:\\*\\{\\}*star/{brackets}/:colon') + self.assertEqual( + r.routelist, ['/hmm:*{}', {'name': 'star', 'type': '*'}, '/', + {'name': 'brackets', 'type': ':'}, '/', + {'name': 'colon', 'type': ':'}]) @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,py33,py34,py35,py36,pypy,pypy3 +envlist = py{27,35,36},pypy,pypy3,style [testenv] deps= @@ -12,3 +12,11 @@ commands= pip install .[middleware] # webob optional dependency is fulfilled by [middleware] extra requirement python -c "import webob" + +[testenv:style] +deps = flake8 +commands = flake8 routes + +[flake8] +# These are all ignored until someone decided to go fix them +ignore = E125,E127,E128,E226,E305,E402,E501,E502,E504,F401,W503,W504 |