summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Dague <sean@dague.net>2016-01-11 12:33:37 -0500
committerSean Dague <sean@dague.net>2016-01-13 11:46:28 -0500
commit3c24bc4273d78376c56c09c5c653b351882f6d6e (patch)
treeea113b32315b68b4a5fff36070345319e13b5052
parent920e4084c55bc0e4b97b7bb9ee8f81782dee515c (diff)
downloadroutes-3c24bc4273d78376c56c09c5c653b351882f6d6e.tar.gz
Add support for ``requirements`` to mapper.resource
This adds support for ``requirements`` option to mapper.resource, which makes it possible to restrict matching in urls (most often useful for capturing variables with path_prefix). In OpenStack Nova we've used the prefix_path on Mapper.resource to specify additional variables we want to capture (specifically {project_id}). Project_id is a uuid. When trying to restrict project_id to only valid uuid format a couple of issues were exposed. - #1 '/{project_id:[a-f0-9]{32}}/...' builds an incorrect regex because of the nested {} - #2 the preferred method that works on connect() to pass requirements doesn't work here (requirements are reset to only an id match) That leaves us with having to build a custom project_id match with 32 [a-f0-9] strings appended for every resource added to get the support we need without effectively vendoring our own version of Mapper.resource. This small change to allow requirements to pass through would make it possible to get this tighter validation with much smaller regexes.
-rw-r--r--CHANGELOG.rst3
-rw-r--r--routes/mapper.py18
-rw-r--r--tests/test_functional/test_resources.py15
3 files changed, 36 insertions, 0 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c50d3f1..7433724 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,6 +4,9 @@ Routes Changelog
Release 2.3 (**dev**)
=====================
+* Add support for the ``requirements`` option when using
+ mapper.resource to create routes. PR #57. Patch by Sean Dague.
+
* Concatenation fix when using submappers with path prefixes. Multiple
submappers combined the path prefix inside the controller argument in
non-obvious ways. The controller argument will now be properly carried
diff --git a/routes/mapper.py b/routes/mapper.py
index 7ca9ee9..382c01d 100644
--- a/routes/mapper.py
+++ b/routes/mapper.py
@@ -982,6 +982,22 @@ class Mapper(SubMapperParent):
# GET /category/7/message/1
# has named route "category_message"
+ ``requirements``
+
+ A dictionary that restricts the matching of a
+ variable. Can be used when matching variables with path_prefix.
+
+ Example::
+
+ map.resource('message', 'messages',
+ path_prefix='{project_id}/',
+ requirements={"project_id": R"\d+"})
+ # POST /01234/message
+ # success, project_id is set to "01234"
+ # POST /foo/message
+ # 404 not found, won't be matched by this route
+
+
``parent_resource``
A ``dict`` containing information about the parent
resource, for creating a nested resource. It should contain
@@ -1105,6 +1121,8 @@ class Mapper(SubMapperParent):
'_parent_resource': parent_resource,
'_filter': kwargs.get('_filter')
}
+ if 'requirements' in kwargs:
+ options['requirements'] = kwargs['requirements']
def requirements_for(meth):
"""Returns a new dict to be used for all route creation as the
diff --git a/tests/test_functional/test_resources.py b/tests/test_functional/test_resources.py
index 06bebcd..0855cd5 100644
--- a/tests/test_functional/test_resources.py
+++ b/tests/test_functional/test_resources.py
@@ -85,6 +85,21 @@ class TestResourceGeneration(unittest.TestCase):
eq_('/messages/new/preview', url_for('category_preview_new_message'))
assert_raises(Exception, url_for, 'category_preview_new_message', method='get')
+ def test_resources_with_requirements(self):
+ m = Mapper()
+ m.resource('message', 'messages', path_prefix='/{project_id}/{user_id}/',
+ requirements={'project_id': r'[0-9a-f]{4}', 'user_id': r'\d+'})
+ options = dict(controller='messages', project_id='cafe', user_id='123')
+ self._assert_restful_routes(m, options, path_prefix='cafe/123/')
+
+ # in addition to the positive tests we need to guarantee we
+ # are not matching when the requirements don't match.
+ eq_({'action': u'create', 'project_id': u'cafe', 'user_id': u'123', 'controller': u'messages'},
+ m.match('/cafe/123/messages'))
+ eq_(None, m.match('/extensions/123/messages'))
+ eq_(None, m.match('/b0a3/123b/messages'))
+ eq_(None, m.match('/foo/bar/messages'))
+
class TestResourceRecognition(unittest.TestCase):
def test_resource(self):