Skip to content

Commit 1852337

Browse files
committed
Update docs and examples
- Add Falcon section in Framework Support page - Use webargs.validate in examples - Add logging in FalconParser.handle_error
1 parent c3748fa commit 1852337

13 files changed

+199
-38
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ webargs
1010

1111
Homepage: https://webargs.readthedocs.org/
1212

13-
webargs is a Python library for parsing HTTP request arguments, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, and webapp2.
13+
webargs is a Python library for parsing HTTP request arguments, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, and Falcon.
1414

1515
.. code-block:: python
1616

docs/api.rst

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,48 @@ webargs.fields
1414
--------------
1515

1616
.. automodule:: webargs.fields
17-
:members: Nested
17+
:members: Nested, DelimitedList
1818

1919
webargs.flaskparser
2020
-------------------
2121

2222
.. automodule:: webargs.flaskparser
23-
:inherited-members:
23+
:members:
2424

2525
webargs.djangoparser
2626
--------------------
2727

2828
.. automodule:: webargs.djangoparser
29-
:inherited-members:
29+
:members:
3030

3131
webargs.bottleparser
3232
--------------------
3333

3434
.. automodule:: webargs.bottleparser
35-
:inherited-members:
35+
:members:
3636

3737
webargs.tornadoparser
3838
---------------------
3939

4040
.. automodule:: webargs.tornadoparser
41-
:inherited-members:
41+
:members:
4242

4343
webargs.pyramidparser
4444
---------------------
4545

4646
.. automodule:: webargs.pyramidparser
47-
:inherited-members:
47+
:members:
4848

4949

5050
webargs.webapp2parser
5151
---------------------
5252

5353
.. automodule:: webargs.webapp2parser
54-
:inherited-members:
54+
:members:
55+
56+
57+
webargs.falconparser
58+
---------------------
5559

60+
.. automodule:: webargs.falconparser
61+
:members:

docs/framework_support.rst

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,59 @@ The `PyramidParser` supports parsing values from a request's matchdict.
248248
@parser.use_args({'mymatch': fields.Int()}, locations=('matchdict',))
249249
def matched(request, args):
250250
return Response('The value for mymatch is {}'.format(args['mymatch'])))
251+
252+
Falcon
253+
------
254+
255+
Falcon support is available via the :mod:`webargs.falconparser` module.
256+
257+
Decorator Usage
258+
+++++++++++++++
259+
260+
When using the :meth:`use_args <webargs.falconparser.FalconParser.use_args>` decorator on a resource method, the arguments dictionary will be positioned as the first argument after ``self``.
261+
262+
.. code-block:: python
263+
264+
import falcon
265+
from webargs import fields
266+
from webargs.falconparser import use_args
267+
268+
class BlogResource:
269+
request_args = {
270+
'title': fields.Str(required=True)
271+
}
272+
273+
@use_args(request_args)
274+
def on_post(self, args, req, resp, post_id):
275+
content = args['title']
276+
# ...
277+
278+
api = application = falcon.API()
279+
api.add_route('/blogs/{post_id}')
280+
281+
As with the other parser modules, :meth:`use_kwargs <webargs.falconparser.FalconParser.use_kwargs>` will add keyword arguments to your resource methods.
282+
283+
Hook Usage
284+
++++++++++
285+
286+
You can easily implement hooks by using `parser.parse <webargs.falconparser.FalconParser.parse>` directly.
287+
288+
.. code-block:: python
289+
290+
import falcon
291+
from webargs import fields
292+
from webargs.falconparser import parser
293+
294+
def add_args(argmap, **kwargs):
295+
def hook(req, resp, params):
296+
parsed_args = parser.parse(argmap, req=req, **kwargs)
297+
req.context['args'] = parsed_args
298+
return hook
299+
300+
@falcon.before(add_args({'page': fields.Int(location='query')}))
301+
class AuthorResource:
302+
303+
def on_get(self, req, resp):
304+
args = req.context['args']
305+
author_name = args.get('page')
306+
# ...

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ webargs
1111

1212
Release v\ |version|. (:ref:`Changelog <changelog>`)
1313

14-
**webargs** is a Python library for parsing HTTP request arguments, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, and Pyramid.
14+
**webargs** is a Python library for parsing HTTP request arguments, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, and Falcon.
1515

1616

1717
.. code-block:: python

examples/__init__.py

Whitespace-only changes.

examples/bottle_example.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import json
1717

1818
from bottle import route, run, error, response
19-
from webargs import fields, ValidationError
19+
from webargs import fields, validate
2020
from webargs.bottleparser import use_args, use_kwargs
2121

2222

@@ -40,14 +40,10 @@ def add(x, y):
4040
"""An addition endpoint."""
4141
return {'result': x + y}
4242

43-
def validate_unit(val):
44-
if val not in ['minutes', 'days']:
45-
raise ValidationError("Unit must be either 'minutes' or 'days'.")
46-
4743
dateadd_args = {
4844
'value': fields.DateTime(required=False),
49-
'addend': fields.Int(required=True, validate=lambda val: val >= 0),
50-
'unit': fields.Str(validate=validate_unit)
45+
'addend': fields.Int(required=True, validate=validate.Range(min=1)),
46+
'unit': fields.Str(missing='days', validate=validate.OneOf(['minutes', 'days']))
5147
}
5248
@route('/dateadd', method='POST')
5349
@use_kwargs(dateadd_args)

examples/falcon_example.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""A simple number and datetime addition JSON API.
2+
Demonstrates different strategies for parsing arguments
3+
with the FalconParser.
4+
5+
Run the app:
6+
7+
$ pip install gunicorn
8+
$ gunicorn examples.falcon_example:app
9+
10+
Try the following with httpie (a cURL-like utility, http://httpie.org):
11+
12+
$ pip install httpie
13+
$ http GET :8000/
14+
$ http GET :8000/ name==Ada
15+
$ http POST :8000/add x=40 y=2
16+
$ http POST :8000/dateadd value=1973-04-10 addend=63
17+
$ http POST :8000/dateadd value=2014-10-23 addend=525600 unit=minutes
18+
"""
19+
import datetime as dt
20+
try:
21+
import simplejson as json
22+
except ImportError:
23+
import json
24+
25+
import falcon
26+
from webargs import fields, validate
27+
from webargs.falconparser import use_args, use_kwargs, parser
28+
29+
### Middleware and hooks ###
30+
31+
class JSONTranslator(object):
32+
def process_response(self, req, resp, resource):
33+
if 'result' not in req.context:
34+
return
35+
resp.body = json.dumps(req.context['result'])
36+
37+
38+
def add_args(argmap, **kwargs):
39+
def hook(req, resp, params):
40+
req.context['args'] = parser.parse(argmap, req=req, **kwargs)
41+
return hook
42+
43+
### Resources ###
44+
45+
class HelloResource(object):
46+
"""A welcome page."""
47+
48+
hello_args = {
49+
'name': fields.Str(missing='Friend', location='query')
50+
}
51+
52+
@use_args(hello_args)
53+
def on_get(self, args, req, resp):
54+
req.context['result'] = {'message': 'Welcome, {}!'.format(args['name'])}
55+
56+
class AdderResource(object):
57+
"""An addition endpoint."""
58+
59+
adder_args = {
60+
'x': fields.Float(required=True),
61+
'y': fields.Float(required=True),
62+
}
63+
64+
@use_kwargs(adder_args)
65+
def on_post(self, req, resp, x, y):
66+
req.context['result'] = {'result': x + y}
67+
68+
69+
class DateAddResource(object):
70+
"""A datetime adder endpoint."""
71+
72+
dateadd_args = {
73+
'value': fields.DateTime(required=False),
74+
'addend': fields.Int(required=True, validate=validate.Range(min=1)),
75+
'unit': fields.Str(missing='days', validate=validate.OneOf(['minutes', 'days']))
76+
}
77+
78+
@falcon.before(add_args(dateadd_args))
79+
def on_post(self, req, resp):
80+
"""A datetime adder endpoint."""
81+
args = req.context['args']
82+
value = args['value'] or dt.datetime.utcnow()
83+
if args['unit'] == 'minutes':
84+
delta = dt.timedelta(minutes=args['addend'])
85+
else:
86+
delta = dt.timedelta(days=args['addend'])
87+
result = value + delta
88+
req.context['result'] = {'result': result.isoformat()}
89+
90+
app = falcon.API(middleware=[
91+
JSONTranslator()
92+
])
93+
app.add_route('/', HelloResource())
94+
app.add_route('/add', AdderResource())
95+
app.add_route('/dateadd', DateAddResource())

examples/flask_example.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import datetime as dt
1616

1717
from flask import Flask, jsonify
18-
from webargs import fields, ValidationError
18+
from webargs import fields, validate
1919
from webargs.flaskparser import use_args, use_kwargs
2020

2121
app = Flask(__name__)
@@ -40,14 +40,10 @@ def add(x, y):
4040
"""An addition endpoint."""
4141
return jsonify({'result': x + y})
4242

43-
def validate_unit(val):
44-
if val not in ['minutes', 'days']:
45-
raise ValidationError("Unit must be either 'minutes' or 'days'.")
46-
4743
dateadd_args = {
4844
'value': fields.DateTime(required=False),
49-
'addend': fields.Int(required=True, validate=lambda val: val >= 0),
50-
'unit': fields.Str(validate=validate_unit)
45+
'addend': fields.Int(required=True, validate=validate.Range(min=1)),
46+
'unit': fields.Str(missing='days', validate=validate.OneOf(['minutes', 'days']))
5147
}
5248
@app.route('/dateadd', methods=['POST'])
5349
@use_kwargs(dateadd_args)

examples/flaskrestful_example.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from flask import Flask
1919
from flask.ext import restful
2020

21-
from webargs import fields, ValidationError
21+
from webargs import fields, validate
2222
from webargs.flaskparser import use_args, use_kwargs, parser
2323

2424
app = Flask(__name__)
@@ -50,16 +50,12 @@ def post(self, x, y):
5050
"""An addition endpoint."""
5151
return {'result': x + y}
5252

53-
def validate_unit(val):
54-
if val not in ['minutes', 'days']:
55-
raise ValidationError("Unit must be either 'minutes' or 'days'.")
56-
5753
class DateAddResource(restful.Resource):
5854

5955
dateadd_args = {
6056
'value': fields.DateTime(required=False),
61-
'addend': fields.Int(required=True, validate=lambda val: val >= 0),
62-
'unit': fields.Str(validate=validate_unit)
57+
'addend': fields.Int(required=True, validate=validate.Range(min=1)),
58+
'unit': fields.Str(missing='days', validate=validate.OneOf(['minutes', 'days']))
6359
}
6460

6561
@use_kwargs(dateadd_args)

examples/tornado_example.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import tornado.ioloop
1818
from tornado.web import RequestHandler
19-
from webargs import fields, ValidationError
19+
from webargs import fields, validate
2020
from webargs.tornadoparser import use_args, use_kwargs
2121

2222

@@ -57,17 +57,13 @@ def post(self, x, y):
5757
self.write({'result': x + y})
5858

5959

60-
def validate_unit(val):
61-
if val not in ['minutes', 'days']:
62-
raise ValidationError("Unit must be either 'minutes' or 'days'.")
63-
6460
class DateAddHandler(BaseRequestHandler):
6561
"""A datetime adder endpoint."""
6662

6763
dateadd_args = {
6864
'value': fields.DateTime(required=False),
69-
'addend': fields.Int(required=True, validate=lambda val: val >= 0),
70-
'unit': fields.Str(validate=validate_unit)
65+
'addend': fields.Int(required=True, validate=validate.Range(min=1)),
66+
'unit': fields.Str(missing='days', validate=validate.OneOf(['minutes', 'days']))
7167
}
7268

7369
@use_kwargs(dateadd_args)

tests/test_falconparser.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
from webargs import fields
99
from webargs.falconparser import parser, use_args, use_kwargs
1010

11+
def use_args_hook(args, context_key='args', **kwargs):
12+
def hook(req, resp, params):
13+
parsed_args = parser.parse(args, req=req, **kwargs)
14+
req.context[context_key] = parsed_args
15+
return hook
1116

1217
@pytest.fixture()
1318
def api():
@@ -56,10 +61,17 @@ class AlwaysErrorResource(object):
5661
def on_get(self, req, resp):
5762
parser.parse(self.args, req=req)
5863

64+
@falcon.before(use_args_hook(hello_args))
65+
class HookResource(object):
66+
67+
def on_get(self, req, resp):
68+
resp.body(req.context['args'])
69+
5970
api_.add_route('/parse', ParseResource())
6071
api_.add_route('/use_args', UseArgsResource())
6172
api_.add_route('/use_args_with_param/{_id}', UseArgsWithParamResource())
6273
api_.add_route('/use_kwargs', UseKwargsResource())
74+
api_.add_route('/hook', UseKwargsResource())
6375
api_.add_route('/error', AlwaysErrorResource())
6476

6577
return api_
@@ -120,3 +132,9 @@ class TestUseKwargsResource:
120132

121133
def test_parse_querystring(self, testapp):
122134
assert testapp.get(self.url + '?name=Fred').json == {'name': 'Fred'}
135+
136+
class TestHookResource:
137+
url = '/hook'
138+
139+
def test_parse_querystring(self, testapp):
140+
assert testapp.get(self.url + '?name=Fred').json == {'name': 'Fred'}

tests/test_tornadoparser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from tornado.testing import AsyncHTTPTestCase
1919

2020
from webargs import fields, missing, ValidationError
21-
from webargs.tornadoparser import parser, use_args, use_kwargs, parse_json, get_value
21+
from webargs.tornadoparser import parser, use_args, use_kwargs, get_value
22+
from webargs.core import parse_json
2223

2324
name = 'name'
2425
value = 'value'

webargs/falconparser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def parse_files(self, req, name, field):
8585

8686
def handle_error(self, error):
8787
"""Handles errors during parsing."""
88+
logger.error(error)
8889
raise HTTPError(HTTP_422, errors=error.messages)
8990

9091
parser = FalconParser()

0 commit comments

Comments
 (0)