summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt1
-rw-r--r--cherrypy/_cptools.py1
-rw-r--r--cherrypy/lib/cptools.py18
-rw-r--r--cherrypy/test/test_params.py55
4 files changed, 75 insertions, 0 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 20877f63..b06461ad 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,7 @@
* Issue #1411: Fix issue where autoreload fails when
the host interpreter for CherryPy was launched using
``python -m``.
+* #1441: Added tool to automatically convert request params.
6.1.0
-----
diff --git a/cherrypy/_cptools.py b/cherrypy/_cptools.py
index 54c63734..060ca76f 100644
--- a/cherrypy/_cptools.py
+++ b/cherrypy/_cptools.py
@@ -533,5 +533,6 @@ _d.json_in = Tool('before_request_body', jsontools.json_in, priority=30)
_d.json_out = Tool('before_handler', jsontools.json_out, priority=30)
_d.auth_basic = Tool('before_handler', auth_basic.basic_auth, priority=1)
_d.auth_digest = Tool('before_handler', auth_digest.digest_auth, priority=1)
+_d.params = Tool('before_handler', cptools.convert_params)
del _d, cptools, encoding, auth, static
diff --git a/cherrypy/lib/cptools.py b/cherrypy/lib/cptools.py
index fbed0e23..073216e0 100644
--- a/cherrypy/lib/cptools.py
+++ b/cherrypy/lib/cptools.py
@@ -630,3 +630,21 @@ def autovary(ignore=None, debug=False):
v.sort()
resp_h['Vary'] = ', '.join(v)
request.hooks.attach('before_finalize', set_response_header, 95)
+
+
+def convert_params(exception=ValueError, error=400):
+ """Convert request params based on function annotations, with error handling.
+
+ exception
+ Exception class to catch.
+
+ status
+ The HTTP error code to return to the client on failure.
+ """
+ request = cherrypy.serving.request
+ types = request.handler.callable.__annotations__
+ try:
+ for key in set(types).intersection(request.params):
+ request.params[key] = types[key](request.params[key])
+ except exception as exc:
+ raise cherrypy.HTTPError(error, str(exc))
diff --git a/cherrypy/test/test_params.py b/cherrypy/test/test_params.py
new file mode 100644
index 00000000..2d57c279
--- /dev/null
+++ b/cherrypy/test/test_params.py
@@ -0,0 +1,55 @@
+import sys
+import cherrypy
+from cherrypy.test import helper
+
+
+class ParamsTest(helper.CPWebCase):
+ @staticmethod
+ def setup_server():
+ class Root:
+ @cherrypy.expose
+ @cherrypy.tools.params()
+ def resource(self, limit=None, sort=None):
+ return type(limit).__name__
+ # for testing on Py 2
+ resource.__annotations__ = {'limit': int}
+ conf = {'/': {'tools.params.on': True}}
+ cherrypy.tree.mount(Root(), config=conf)
+
+ def test_pass(self):
+ self.getPage('/resource')
+ self.assertStatus(200)
+ self.assertBody('NoneType')
+
+ self.getPage('/resource?limit=0')
+ self.assertStatus(200)
+ self.assertBody('int')
+
+ def test_error(self):
+ self.getPage('/resource?limit=')
+ self.assertStatus(400)
+ self.assertInBody('invalid literal for int')
+
+ cherrypy.config['tools.params.error'] = 422
+ self.getPage('/resource?limit=')
+ self.assertStatus(422)
+ self.assertInBody('invalid literal for int')
+
+ cherrypy.config['tools.params.exception'] = TypeError
+ self.getPage('/resource?limit=')
+ self.assertStatus(500)
+
+ def test_syntax(self):
+ if sys.version_info < (3,):
+ return self.skip("skipped (Python 3 only)")
+ exec("""class Root:
+ @cherrypy.expose
+ @cherrypy.tools.params()
+ def resource(self, limit: int):
+ return type(limit).__name__
+conf = {'/': {'tools.params.on': True}}
+cherrypy.tree.mount(Root(), config=conf)""")
+
+ self.getPage('/resource?limit=0')
+ self.assertStatus(200)
+ self.assertBody('int')