From efa763042f450063dc89e40774919b23eade2208 Mon Sep 17 00:00:00 2001 From: BenjamenMeyer Date: Thu, 19 Feb 2015 19:14:13 +0000 Subject: [PATCH] feat(routing): Allow digits and underscore in compile_uri_template 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 --- falcon/routing/util.py | 4 ++- tests/test_uri_templates.py | 39 ++++++++++++++++++++++++++++++ tests/test_uri_templates_legacy.py | 21 +++++++++++++--- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/falcon/routing/util.py b/falcon/routing/util.py index e4f258e52..e82c044fd 100644 --- a/falcon/routing/util.py +++ b/falcon/routing/util.py @@ -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)) diff --git a/tests/test_uri_templates.py b/tests/test_uri_templates.py index 12ae7b50a..165f76b29 100644 --- a/tests/test_uri_templates.py +++ b/tests/test_uri_templates.py @@ -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): @@ -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) @@ -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) diff --git a/tests/test_uri_templates_legacy.py b/tests/test_uri_templates_legacy.py index 538f4be7d..2410b54a4 100644 --- a/tests/test_uri_templates_legacy.py +++ b/tests/test_uri_templates_legacy.py @@ -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}')