Skip to content

Commit

Permalink
feat(routing): Allow digits and underscore in compile_uri_template
Browse files Browse the repository at this point in the history
Allow URI template fields to contain numbers so long as the field name
does not start with a numeral. Example: "test123" is valid, while
"123test" is not.

This patch adds support for this in the legacy template helpers, and also
adds an additional test for these chars for the current router spec.

Co-Authored-By: Kurt Griffiths <mail@kgriffs.com>
  • Loading branch information
BenjamenMeyer and kgriffs committed Aug 7, 2015
1 parent e10ff51 commit efa7630
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 4 deletions.
4 changes: 3 additions & 1 deletion falcon/routing/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ def compile_uri_template(template):
if template != '/' and template.endswith('/'):
template = template[:-1]

expression_pattern = r'{([a-zA-Z][a-zA-Z_]*)}'
# template names should be able to start with A-Za-z
# but also contain 0-9_ in the remaining portion
expression_pattern = r'{([a-zA-Z]\w*)}'

# Get a list of field names
fields = set(re.findall(expression_pattern, template))
Expand Down
39 changes: 39 additions & 0 deletions tests/test_uri_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ def on_get(self, req, resp, id, name):
self.called = True


class NameAndDigitResource(object):
def __init__(self):
self.id = None
self.name51 = None
self.called = False

def on_get(self, req, resp, id, name51):
self.id = id
self.name51 = name51
self.called = True


class TestUriTemplates(testing.TestBase):

def before(self):
Expand Down Expand Up @@ -116,6 +128,20 @@ def test_single(self):
self.assertNotIn(kwargs, 'Id')
self.assertEqual(req.get_param('id'), None)

def test_single_with_trailing_digits(self):
self.api.add_route('/widgets/{id12}', self.resource)

self.simulate_request('/widgets/123')
self.assertTrue(self.resource.called)
self.assertEqual(self.resource.kwargs['id12'], '123')

def test_single_with_underscore(self):
self.api.add_route('/widgets/{widget_id}', self.resource)

self.simulate_request('/widgets/123')
self.assertTrue(self.resource.called)
self.assertEqual(self.resource.kwargs['widget_id'], '123')

def test_single_trailing_slash(self):
resource1 = IDResource()
self.api.add_route('/1/{id}/', resource1)
Expand Down Expand Up @@ -158,6 +184,19 @@ def test_multiple(self):
self.assertEqual(resource.id, test_id)
self.assertEqual(resource.name, test_name)

def test_multiple_with_digits(self):
resource = NameAndDigitResource()
self.api.add_route('/messages/{id}/names/{name51}', resource)

test_id = self.getUniqueString()
test_name = self.getUniqueString()
path = '/messages/' + test_id + '/names/' + test_name
self.simulate_request(path)
self.assertTrue(resource.called)

self.assertEqual(resource.id, test_id)
self.assertEqual(resource.name51, test_name)

def test_empty_path_component(self):
self.assertRaises(ValueError, self.api.add_route,
'//', self.resource)
Expand Down
21 changes: 18 additions & 3 deletions tests/test_uri_templates_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,30 @@ def test_one_field(self):
self.assertTrue(result)
self.assertEqual(result.groupdict(), {'name': 'Kelsier'})

def test_one_field_with_digits(self):
fields, pattern = routing.compile_uri_template('/{name123}')
self.assertEqual(fields, set(['name123']))

result = pattern.match('/Kelsier')
self.assertTrue(result)
self.assertEqual(result.groupdict(), {'name123': 'Kelsier'})

def test_one_field_with_prefixed_digits(self):
fields, pattern = routing.compile_uri_template('/{37signals}')
self.assertEqual(fields, set())

result = pattern.match('/s2n')
self.assertFalse(result)

@ddt.data('', '/')
def test_two_fields(self, postfix):
path = '/book/{id}/characters/{name}' + postfix
path = '/book/{book_id}/characters/{n4m3}' + postfix
fields, pattern = routing.compile_uri_template(path)
self.assertEqual(fields, set(['name', 'id']))
self.assertEqual(fields, set(['n4m3', 'book_id']))

result = pattern.match('/book/0765350386/characters/Vin')
self.assertTrue(result)
self.assertEqual(result.groupdict(), {'name': 'Vin', 'id': '0765350386'})
self.assertEqual(result.groupdict(), {'n4m3': 'Vin', 'book_id': '0765350386'})

def test_three_fields(self):
fields, pattern = routing.compile_uri_template('/{a}/{b}/x/{c}')
Expand Down

0 comments on commit efa7630

Please sign in to comment.