Codebase list python-webargs / 5d50e68
New upstream version 5.1.2 Sophie Brun 5 years ago
49 changed file(s) with 649 addition(s) and 703 deletion(s). Raw diff Collapse all Expand all
33 hooks:
44 - id: black
55 language_version: python3.6
6 - repo: https://github.com/pre-commit/pre-commit-hooks
7 rev: v2.0.0
6 - repo: https://gitlab.com/pycqa/flake8
7 rev: 3.7.4
88 hooks:
99 - id: flake8
10 additional_dependencies: ["flake8-bugbear==18.8.0"]
1011 - repo: https://github.com/asottile/blacken-docs
1112 rev: v0.3.0
1213 hooks:
1314 - id: blacken-docs
1415 additional_dependencies: [black==18.9b0]
16 - repo: https://github.com/pre-commit/mirrors-mypy
17 rev: v0.660
18 hooks:
19 - id: mypy
20 language_version: python3.6
21 files: ^webargs/
00 language: python
1 sudo: false
21 cache: pip
32 install: travis_retry pip install -U tox
43 script: tox
1716 - { python: '3.6', env: TOXENV=py36-marshmallow2 }
1817 - { python: '3.6', env: TOXENV=py36-marshmallow3 }
1918
20 - { python: '3.7', env: TOXENV=py37-marshmallow2, dist: xenial, sudo: required }
21 - { python: '3.7', env: TOXENV=py37-marshmallow3, dist: xenial, sudo: required }
19 - { python: '3.7', env: TOXENV=py37-marshmallow2, dist: xenial }
20 - { python: '3.7', env: TOXENV=py37-marshmallow3, dist: xenial }
2221
2322 - { python: '3.6', env: TOXENV=docs }
2423
3534 distributions: sdist bdist_wheel
3635 password:
3736 secure: TMeTi5OPl2cYsl5hNP4w1xESd2vQUOy8NgZ0c3KbrVSSeBuUCGOKyYJZNGzD9KDMucCvYFuxCwYiDxP8tB5iT85z3rhdVkzppZTy3/3kXMODjlhMzqTnCdJSOoZZ+D5/Y3Zqb8QxU78NggPutfX4bbUU/wNsVbdODXWHe5y2q3k=
37
38 - stage: PyPI Release
39 if: tag IS present
40 python: "2.7"
41 install: skip
42 script: skip
43 deploy:
44 provider: pypi
45 user: sloria
46 on:
47 tags: true
48 distributions: bdist_wheel
49 password:
50 secure: TMeTi5OPl2cYsl5hNP4w1xESd2vQUOy8NgZ0c3KbrVSSeBuUCGOKyYJZNGzD9KDMucCvYFuxCwYiDxP8tB5iT85z3rhdVkzppZTy3/3kXMODjlhMzqTnCdJSOoZZ+D5/Y3Zqb8QxU78NggPutfX4bbUU/wNsVbdODXWHe5y2q3k=
00 Changelog
11 ---------
2
3 5.1.2 (2019-02-03)
4 ******************
5
6 Bug fixes:
7
8 * Remove lingering usages of ``ValidationError.status_code``
9 (:issue:`365`). Thanks :user:`decaz` for reporting.
10 * Avoid ``AttributeError`` on Python<3.5.4 (:issue:`366`).
11 * Fix incorrect type annotations for ``error_headers``.
12 * Fix outdated docs (:issue:`367`). Thanks :user:`alexandersoto` for reporting.
13
14 5.1.1.post0 (2019-01-30)
15 ************************
16
17 * Include LICENSE in sdist (:issue:`364`).
18
19 5.1.1 (2019-01-28)
20 ******************
21
22 Bug fixes:
23
24 * Fix installing ``simplejson`` on Python 2 by
25 distributing a Python 2-only wheel (:issue:`363`).
26
27 5.1.0 (2019-01-11)
28 ******************
29
30 Features:
31
32 * Error handlers for `AsyncParser` classes may be coroutine functions.
33 * Add type annotations to `AsyncParser` and `AIOHTTPParser`.
34
35 Bug fixes:
36
37 * Fix compatibility with Flask<1.0 (:issue:`355`).
38 Thanks :user:`hoatle` for reporting.
39 * Address warning on Python 3.7 about importing from ``collections.abc``.
40
41 5.0.0 (2019-01-03)
42 ******************
43
44 Features:
45
46 * *Backwards-incompatible*: A 400 HTTPError is raised when an
47 invalid JSON payload is passed. (:issue:`329`).
48 Thanks :user:`zedrdave` for reporting.
49
50 Other changes:
51
52 * *Backwards-incompatible*: `webargs.argmap2schema` is removed. Use
53 `webargs.dict2schema` instead.
54 * *Backwards-incompatible*: `webargs.ValidationError` is removed.
55 Use `marshmallow.ValidationError` instead.
56
57
58 .. code-block:: python
59
60 # <5.0.0
61 from webargs import ValidationError
62
63
64 def auth_validator(value):
65 # ...
66 raise ValidationError("Authentication failed", status_code=401)
67
68
69 @use_args({"auth": fields.Field(validate=auth_validator)})
70 def auth_view(args):
71 return jsonify(args)
72
73
74 # >=5.0.0
75 from marshmallow import ValidationError
76
77
78 def auth_validator(value):
79 # ...
80 raise ValidationError("Authentication failed")
81
82
83 @use_args({"auth": fields.Field(validate=auth_validator)}, error_status_code=401)
84 def auth_view(args):
85 return jsonify(args)
86
87
88 * *Backwards-incompatible*: Missing arguments will no longer be filled
89 in when using ``@use_kwargs`` (:issue:`342,307,252`). Use ``**kwargs``
90 to account for non-required fields.
91
92 .. code-block:: python
93
94 # <5.0.0
95 @use_kwargs(
96 {"first_name": fields.Str(required=True), "last_name": fields.Str(required=False)}
97 )
98 def myview(first_name, last_name):
99 # last_name is webargs.missing if it's missing from the request
100 return {"first_name": first_name}
101
102
103 # >=5.0.0
104 @use_kwargs(
105 {"first_name": fields.Str(required=True), "last_name": fields.Str(required=False)}
106 )
107 def myview(first_name, **kwargs):
108 # last_name will not be in kwargs if it's missing from the request
109 return {"first_name": first_name}
110
111
112 * `simplejson <https://pypi.org/project/simplejson/>`_ is now a required
113 dependency on Python 2 (:pr:`334`).
114 This ensures consistency of behavior across Python 2 and 3.
2115
3116 4.4.1 (2018-01-03)
4117 ******************
0 For code of conduct, see https://marshmallow.readthedocs.io/en/dev/code_of_conduct.html
0 For the code of conduct, see https://marshmallow.readthedocs.io/en/dev/code_of_conduct.html
0 Copyright 2014-2017 Steven Loria and contributors
0 Copyright 2014-2019 Steven Loria and contributors
11
22 Permission is hereby granted, free of charge, to any person obtaining a copy
33 of this software and associated documentation files (the "Software"), to deal
0 include *.rst LICENSE
0 include LICENSE
1 include *.rst
22 *******
33
44 .. image:: https://badgen.net/pypi/v/webargs
5 :target: https://badge.fury.io/py/webargs
5 :target: https://pypi.org/project/webargs/
66 :alt: PyPI version
77
88 .. image:: https://badgen.net/travis/marshmallow-code/webargs
7373
7474 .. image:: https://opencollective.com/marshmallow/donate/button.png
7575 :target: https://opencollective.com/marshmallow
76 :width: 200
7677 :alt: Donate to our collective
7778
7879 Professional Support
+0
-9
docs/_static/css/extra.css less more
0 /* Styles for sponsors.html */
1 .sponsor .logo img {
2 width: 48px;
3 vertical-align: middle;
4 }
5 .sponsor a.logo {
6 float: left;
7 border-bottom: none;
8 }
00 <div class="sponsors">
11 {% if tidelift_url %}
22 <div class="sponsor">
3 <a class="logo" href="{{ tidelift_url }}">
3 <a class="image" href="{{ tidelift_url }}">
44 <img src="https://user-images.githubusercontent.com/2379650/45126032-50b69880-b13f-11e8-9c2c-abd16c433495.png" alt="Sponsor"></a>
5 <div class="sponsor_text">Professionally-supported {{ project }} is available with the
5 <div class="text">Professionally-supported {{ project }} is available with the
66 <a href="{{ tidelift_url }}">Tidelift Subscription</a>.
77 </div>
88 </div>
4747 # Add any paths that contain custom themes here, relative to this directory.
4848 html_theme = "sphinx_typlog_theme"
4949 html_theme_path = [sphinx_typlog_theme.get_path()]
50 html_static_path = ["_static"]
5150
5251 html_theme_options = {
5352 "color": "#268bd2",
7574 "sponsors.html",
7675 ]
7776 }
78
79
80 def setup(app):
81 app.add_stylesheet("css/extra.css")
0 Ecosystem
1 =========
2
3 A list of webargs-related libraries can be found at the GitHub wiki here:
4
5 https://github.com/marshmallow-code/webargs/wiki/Ecosystem
4040
4141 from flask import jsonify
4242
43
4344 # Return validation errors as JSON
4445 @app.errorhandler(422)
45 def handle_validation_error(err):
46 exc = getattr(err, "exc", None)
47 if exc:
48 headers = err.data["headers"]
49 messages = exc.messages
50 else:
51 headers = None
52 messages = ["Invalid request."]
46 @app.errorhandler(400)
47 def handle_error(err):
48 headers = err.data.get("headers", None)
49 messages = err.data.get("messages", ["Invalid request."])
5350 if headers:
5451 return jsonify({"errors": messages}), err.code, headers
5552 else:
130127
131128 from django.http import JsonResponse
132129
133 from webargs import fields, ValidationError
130 from webargs import fields, ValidationError, json
134131
135132 argmap = {"name": fields.Str(required=True)}
136133
139136 try:
140137 args = parser.parse(argmap, request)
141138 except ValidationError as err:
142 return JsonResponse(err.messages, status=err.status_code)
139 return JsonResponse(err.messages, status=422)
140 except json.JSONDecodeError:
141 return JsonResponse({"json": ["Invalid JSON body."]}, status=400)
143142 return JsonResponse({"message": "Hello {name}".format(name=name)})
144143
145144 Tornado
214213 """Write errors as JSON."""
215214 self.set_header("Content-Type", "application/json")
216215 if "exc_info" in kwargs:
217 etype, value, traceback = kwargs["exc_info"]
218 if hasattr(value, "messages"):
219 self.write({"errors": value.messages})
216 etype, exc, traceback = kwargs["exc_info"]
217 if hasattr(exc, "messages"):
218 self.write({"errors": exc.messages})
219 if getattr(exc, "headers", None):
220 for name, val in exc.headers.items():
221 self.set_header(name, val)
220222 self.finish()
221223
222224 Pyramid
8585 quickstart
8686 advanced
8787 framework_support
88 ecosystem
8889
8990 API Reference
9091 -------------
2727 # When value is keyed on a variable-unsafe name
2828 # or you want to rename a key
2929 "content_type": fields.Str(load_from="Content-Type", location="headers"),
30 # OR, on marshmallow 3
31 # "content_type": fields.Str(data_key="Content-Type", location="headers"),
32 # File uploads
33 "profile_image": fields.Field(
34 location="files", validate=lambda f: f.mimetype in ["image/jpeg", "image/png"]
35 ),
3036 }
3137
3238 .. note::
8187
8288 .. note::
8389
84 When using `use_kwargs`, any missing values for non-required fields will take the special value `missing <marshmallow.missing>`.
90 When using `use_kwargs`, any missing values will be omitted from the arguments.
91 Use ``**kwargs`` to handle optional arguments.
8592
8693 .. code-block:: python
8794
8895 from webargs import fields, missing
8996
9097
91 @use_kwargs({"name": fields.Str(), "nickname": fields.Str(required=False)})
92 def myview(name, nickname):
93 if nickname is missing:
98 @use_kwargs({"name": fields.Str(required=True), "nickname": fields.Str(required=False)})
99 def myview(name, **kwargs):
100 if "nickname" not in kwargs:
94101 # ...
95102 pass
96103
169176 --------------
170177
171178 Each parser has a default error handling method. To override the error handling callback, write a function that
172 receives an error, the request, and the `marshmallow.Schema` instance.
179 receives an error, the request, the `marshmallow.Schema` instance, status code, and headers.
173180 Then decorate that function with :func:`Parser.error_handler <webargs.core.Parser.error_handler>`.
174181
175182 .. code-block:: python
00 -e '.[frameworks]'
11 Sphinx==1.8.3
22 sphinx-issues==1.2.0
3 # TODO: Use PyPI release of sphinx-typlog-theme after latest changes are released
4 git+https://github.com/typlog/sphinx-typlog-theme.git@45a71b3cd99c7550e5999042504d3b9c83b68791
3 sphinx-typlog-theme==0.7.1
2828 if isinstance(value, fields.Field) and name != "return"
2929 }
3030 response_schema = annotations.get("return")
31 parsed = parser.parse(reqargs, request, force_all=True)
31 parsed = parser.parse(reqargs, request)
3232 kw.update(parsed)
3333 response_data = func(*a, **kw)
3434 if response_schema:
5252
5353
5454 class UserSchema(Schema):
55 id = fields.Int()
56 name = fields.Str()
55 id = fields.Int(required=True)
56 name = fields.Str(required=True)
5757 date_created = fields.DateTime(dump_only=True)
5858
5959
7373 # Return validation errors as JSON
7474 @app.errorhandler(422)
7575 @app.errorhandler(400)
76 def handle_validation_error(err):
77 exc = getattr(err, "exc", None)
78 if exc:
79 headers = err.data["headers"]
80 messages = exc.messages
81 else:
82 headers = None
83 messages = ["Invalid request."]
76 def handle_error(err):
77 headers = err.data.get("headers", None)
78 messages = err.data.get("messages", ["Invalid request."])
8479 if headers:
8580 return jsonify({"errors": messages}), err.code, headers
8681 else:
5757
5858
5959 # Return validation errors as JSON
60 @error(400)
6061 @error(422)
61 def error422(err):
62 def handle_error(err):
6263 response.content_type = "application/json"
6364 return err.body
6465
1717 """
1818 import datetime as dt
1919
20 try:
21 import simplejson as json
22 except ImportError:
23 import json
20 from webargs.core import json
2421
2522 import falcon
2623 from webargs import fields, validate
6363 # Return validation errors as JSON
6464 @app.errorhandler(422)
6565 @app.errorhandler(400)
66 def handle_validation_error(err):
67 exc = getattr(err, "exc", None)
68 if exc:
69 headers = err.data["headers"]
70 messages = exc.messages
71 else:
72 headers = None
73 messages = ["Invalid request."]
66 def handle_error(err):
67 headers = err.data.get("headers", None)
68 messages = err.data.get("messages", ["Invalid request."])
7469 if headers:
7570 return jsonify({"errors": messages}), err.code, headers
7671 else:
0 [metadata]
1 license_files = LICENSE
2
03 [bdist_wheel]
1 universal = 1
4 # We build separate wheels for
5 # Python 2 and 3 because of the conditional
6 # dependency on simplejson
7 universal = 0
28
39 [flake8]
4 ignore = E203, E266, E501, W503, E302, W504
5 max-line-length = 100
10 ignore = E203, E266, E501, W503
11 max-line-length = 80
612 max-complexity = 18
713 select = B,C,E,F,W,T4,B9
8 exclude = .git,.ropeproject,.tox,build,env,venv,__pycache__
914
10 [tool:pytest]
11 filterwarnings =
12 ignore::webargs.core.RemovedInWebargs5Warning
15 [mypy]
16 ignore_missing_imports = true
00 # -*- coding: utf-8 -*-
1 import sys
12 import re
23 from setuptools import setup, find_packages
34
45 INSTALL_REQUIRES = ["marshmallow>=2.15.2"]
6 if sys.version_info[0] < 3:
7 INSTALL_REQUIRES.append("simplejson>=2.1.0")
8
59 FRAMEWORKS = [
610 "Flask>=0.12.2",
711 "Django>=1.11.16",
812 "bottle>=0.12.13",
913 "tornado>=4.5.2",
10 "pyramid>=1.8.5",
14 "pyramid>=1.9.1",
1115 "webapp2>=3.0.0b1",
12 "falcon>=1.3.0",
16 "falcon>=1.4.0",
1317 'aiohttp>=3.0.0; python_version >= "3.5"',
1418 ]
1519 EXTRAS_REQUIRE = {
2327 ]
2428 + FRAMEWORKS,
2529 "lint": [
30 'mypy==0.650; python_version >= "3.5"',
2631 "flake8==3.6.0",
2732 'flake8-bugbear==18.8.0; python_version >= "3.5"',
2833 "pre-commit==1.13.0",
4853 return version
4954
5055
51 __version__ = find_version("webargs/__init__.py")
52
53
5456 def read(fname):
5557 with open(fname) as fp:
5658 content = fp.read()
5961
6062 setup(
6163 name="webargs",
62 version=__version__,
64 version=find_version("webargs/__init__.py"),
6365 description=(
6466 "Declarative parsing and validation of HTTP request objects, "
6567 "with built-in support for popular web frameworks, including "
103105 "Programming Language :: Python :: 3.5",
104106 "Programming Language :: Python :: 3.6",
105107 "Programming Language :: Python :: 3.7",
106 "Programming Language :: Python :: Implementation :: CPython",
107 "Programming Language :: Python :: Implementation :: PyPy",
108108 "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
109109 "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
110110 ],
44 from aiohttp import web
55 import marshmallow as ma
66
7 from webargs import fields, ValidationError
7 from webargs import fields
88 from webargs.aiohttpparser import parser, use_args, use_kwargs
9 from webargs.core import MARSHMALLOW_VERSION_INFO
9 from webargs.core import MARSHMALLOW_VERSION_INFO, json
1010
1111 hello_args = {"name": fields.Str(missing="World", validate=lambda n: len(n) >= 3)}
1212 hello_multiple = {"name": fields.List(fields.Str())}
2828
2929
3030 async def echo(request):
31 parsed = await parser.parse(hello_args, request)
31 try:
32 parsed = await parser.parse(hello_args, request)
33 except json.JSONDecodeError:
34 raise web.HTTPBadRequest(
35 body=json.dumps(["Invalid JSON."]).encode("utf-8"),
36 content_type="application/json",
37 )
3238 return json_response(parsed)
3339
3440
8086
8187 async def always_error(request):
8288 def always_fail(value):
83 raise ValidationError("something went wrong")
84
85 args = {"text": fields.Str(validate=always_fail)}
86 parsed = await parser.parse(args, request)
87 return json_response(parsed)
88
89
90 async def error400(request):
91 def always_fail(value):
92 raise ValidationError("something went wrong", status_code=400)
93
94 args = {"text": fields.Str(validate=always_fail)}
95 parsed = await parser.parse(args, request)
96 return json_response(parsed)
97
98
99 async def error_invalid(request):
100 def always_fail(value):
101 raise ValidationError("something went wrong", status_code=12345)
89 raise ma.ValidationError("something went wrong")
10290
10391 args = {"text": fields.Str(validate=always_fail)}
10492 parsed = await parser.parse(args, request)
200188 )
201189 add_route(app, ["POST"], "/echo_use_args_multiple", echo_use_args_multiple)
202190 add_route(app, ["GET", "POST"], "/error", always_error)
203 add_route(app, ["GET", "POST"], "/error400", error400)
204 add_route(app, ["GET"], "/error_invalid", error_invalid)
205191 add_route(app, ["GET"], "/echo_headers", echo_headers)
206192 add_route(app, ["GET"], "/echo_cookie", echo_cookie)
207193 add_route(app, ["POST"], "/echo_nested", echo_nested)
0 import json
1 from bottle import Bottle, HTTPResponse, debug, request, response, error
0 from webargs.core import json
1 from bottle import Bottle, HTTPResponse, debug, request, response
22
33 import marshmallow as ma
4 from webargs import fields, ValidationError
4 from webargs import fields
55 from webargs.bottleparser import parser, use_args, use_kwargs
66 from webargs.core import MARSHMALLOW_VERSION_INFO
77
3737 return args
3838
3939
40 @app.route("/echo_use_kwargs", method=["GET", "POST"])
41 @use_kwargs(hello_args)
40 @app.route("/echo_use_kwargs", method=["GET", "POST"], apply=use_kwargs(hello_args))
4241 def echo_use_kwargs(name):
4342 return {"name": name}
4443
4544
46 @app.route("/echo_use_args_validated", method=["GET", "POST"])
47 @use_args({"value": fields.Int()}, validate=lambda args: args["value"] > 42)
45 @app.route(
46 "/echo_use_args_validated",
47 method=["GET", "POST"],
48 apply=use_args({"value": fields.Int()}, validate=lambda args: args["value"] > 42),
49 )
4850 def echo_use_args_validated(args):
4951 return args
5052
6062 return HTTPResponse(body=json.dumps(arguments), content_type="application/json")
6163
6264
63 @app.route("/echo_use_args_with_path_param/<name>")
64 @use_args({"value": fields.Int()})
65 @app.route(
66 "/echo_use_args_with_path_param/<name>", apply=use_args({"value": fields.Int()})
67 )
6568 def echo_use_args_with_path_param(args, name):
6669 return args
6770
6871
69 @app.route("/echo_use_kwargs_with_path_param/<name>")
70 @use_kwargs({"value": fields.Int()})
72 @app.route(
73 "/echo_use_kwargs_with_path_param/<name>", apply=use_kwargs({"value": fields.Int()})
74 )
7175 def echo_use_kwargs_with_path_param(name, value):
7276 return {"value": value}
7377
7579 @app.route("/error", method=["GET", "POST"])
7680 def always_error():
7781 def always_fail(value):
78 raise ValidationError("something went wrong")
79
80 args = {"text": fields.Str(validate=always_fail)}
81 return parser.parse(args)
82
83
84 @app.route("/error400", method=["GET", "POST"])
85 def error400():
86 def always_fail(value):
87 raise ValidationError("something went wrong", status_code=400)
82 raise ma.ValidationError("something went wrong")
8883
8984 args = {"text": fields.Str(validate=always_fail)}
9085 return parser.parse(args)
123118 return parser.parse(args)
124119
125120
126 @error(422)
127 def handle_422(err):
121 @app.error(400)
122 @app.error(422)
123 def handle_error(err):
128124 response.content_type = "application/json"
129125 return err.body
1717 views.echo_use_kwargs_with_path_param,
1818 ),
1919 url(r"^error$", views.always_error),
20 url(r"^error400$", views.error400),
2120 url(r"^echo_headers$", views.echo_headers),
2221 url(r"^echo_cookie$", views.echo_cookie),
2322 url(r"^echo_file$", views.echo_file),
0 import json
0 from webargs.core import json
11 from django.http import HttpResponse
22 from django.views.generic import View
33
44 import marshmallow as ma
5 from webargs import fields, ValidationError
5 from webargs import fields
66 from webargs.djangoparser import parser, use_args, use_kwargs
77 from webargs.core import MARSHMALLOW_VERSION_INFO
88
2525 def echo(request):
2626 try:
2727 args = parser.parse(hello_args, request)
28 except ValidationError as err:
29 return json_response(err.messages, status=err.status_code)
28 except ma.ValidationError as err:
29 return json_response(err.messages, status=parser.DEFAULT_VALIDATION_STATUS)
30 except json.JSONDecodeError:
31 return json_response({"json": ["Invalid JSON body."]}, status=400)
3032 return json_response(args)
3133
3234
5355 return json_response(
5456 parser.parse(hello_many_schema, request, locations=("json",))
5557 )
56 except ValidationError as err:
57 return json_response(err.messages, status=err.status_code)
58 except ma.ValidationError as err:
59 return json_response(err.messages, status=parser.DEFAULT_VALIDATION_STATUS)
5860
5961
6062 @use_args({"value": fields.Int()})
6971
7072 def always_error(request):
7173 def always_fail(value):
72 raise ValidationError("something went wrong")
74 raise ma.ValidationError("something went wrong")
7375
7476 argmap = {"text": fields.Str(validate=always_fail)}
7577 try:
7678 return parser.parse(argmap, request)
77 except ValidationError as err:
78 return json_response(err.messages, status=err.status_code)
79
80
81 def error400(request):
82 def always_fail(value):
83 raise ValidationError("something went wrong", status_code=400)
84
85 argmap = {"text": fields.Str(validate=always_fail)}
86 try:
87 return parser.parse(argmap, request)
88 except ValidationError as err:
89 return json_response(err.messages, status=err.status_code)
79 except ma.ValidationError as err:
80 return json_response(err.messages, status=parser.DEFAULT_VALIDATION_STATUS)
9081
9182
9283 def echo_headers(request):
121112 def get(self, request):
122113 try:
123114 args = parser.parse(hello_args, self.request)
124 except ValidationError as err:
125 return json_response(err.messages, status=err.status_code)
115 except ma.ValidationError as err:
116 return json_response(err.messages, status=parser.DEFAULT_VALIDATION_STATUS)
126117 return json_response(args)
127118
128119 post = get
0 import json
0 from webargs.core import json
11
22 import falcon
33 import marshmallow as ma
4 from webargs import fields, ValidationError
4 from webargs import fields
55 from webargs.falconparser import parser, use_args, use_kwargs
66 from webargs.core import MARSHMALLOW_VERSION_INFO
77
1919
2020 class Echo(object):
2121 def on_get(self, req, resp):
22 parsed = parser.parse(hello_args, req)
23 resp.body = json.dumps(parsed)
22 try:
23 parsed = parser.parse(hello_args, req)
24 except json.JSONDecodeError:
25 resp.body = json.dumps(["Invalid JSON."])
26 resp.status = falcon.HTTP_400
27 else:
28 resp.body = json.dumps(parsed)
2429
2530 on_post = on_get
2631
8691 class AlwaysError(object):
8792 def on_get(self, req, resp):
8893 def always_fail(value):
89 raise ValidationError("something went wrong")
94 raise ma.ValidationError("something went wrong")
9095
9196 args = {"text": fields.Str(validate=always_fail)}
9297 resp.body = json.dumps(parser.parse(args, req))
9398
9499 on_post = on_get
95
96
97 class Error400(object):
98 def on_get(self, req, resp):
99 def always_fail(value):
100 raise ValidationError("something went wrong", status_code=400)
101
102 args = {"text": fields.Str(validate=always_fail)}
103 resp.body = json.dumps(parser.parse(args, req))
104
105 on_post = on_get
106
107
108 class ErrorInvalid(object):
109 def on_get(self, req, resp):
110 def always_fail(value):
111 raise ValidationError("something went wrong", status_code=12345)
112
113 args = {"text": fields.Str(validate=always_fail)}
114 resp.body = json.dumps(parser.parse(args, req))
115100
116101
117102 class EchoHeaders(object):
168153 "/echo_use_kwargs_with_path_param/{name}", EchoUseKwargsWithPathParam()
169154 )
170155 app.add_route("/error", AlwaysError())
171 app.add_route("/error400", Error400())
172156 app.add_route("/echo_headers", EchoHeaders())
173157 app.add_route("/echo_cookie", EchoCookie())
174158 app.add_route("/echo_nested", EchoNested())
175159 app.add_route("/echo_nested_many", EchoNestedMany())
176160 app.add_route("/echo_use_args_hook", EchoUseArgsHook())
177 app.add_route("/error_invalid", ErrorInvalid())
178161 return app
0 import json
0 from webargs.core import json
11 from flask import Flask, jsonify as J, Response, request
22 from flask.views import MethodView
33
44 import marshmallow as ma
5 from webargs import fields, ValidationError, missing
5 from webargs import fields
66 from webargs.flaskparser import parser, use_args, use_kwargs
77 from webargs.core import MARSHMALLOW_VERSION_INFO
88
8080 @app.route("/error", methods=["GET", "POST"])
8181 def error():
8282 def always_fail(value):
83 raise ValidationError("something went wrong")
84
85 args = {"text": fields.Str(validate=always_fail)}
86 return J(parser.parse(args))
87
88
89 @app.route("/error400", methods=["GET", "POST"])
90 def error400():
91 def always_fail(value):
92 raise ValidationError("something went wrong", status_code=400)
83 raise ma.ValidationError("something went wrong")
9384
9485 args = {"text": fields.Str(validate=always_fail)}
9586 return J(parser.parse(args))
173164
174165
175166 @app.route("/echo_use_kwargs_missing", methods=["post"])
176 @use_kwargs({"username": fields.Str(), "password": fields.Str()})
177 def echo_use_kwargs_missing(username, password):
178 assert password is missing
167 @use_kwargs({"username": fields.Str(required=True), "password": fields.Str()})
168 def echo_use_kwargs_missing(username, **kwargs):
169 assert "password" not in kwargs
179170 return J({"username": username})
180171
181172
182173 # Return validation errors as JSON
183174 @app.errorhandler(422)
184 def handle_validation_error(err):
185 assert isinstance(err.data["schema"], ma.Schema)
186 return J({"errors": err.exc.messages}), err.code
175 @app.errorhandler(400)
176 def handle_error(err):
177 if err.code == 422:
178 assert isinstance(err.data["schema"], ma.Schema)
179 return J(err.data["messages"]), err.code
0 from webargs.core import json
1
02 from pyramid.config import Configurator
3 from pyramid.httpexceptions import HTTPBadRequest
14 import marshmallow as ma
25
3 from webargs import fields, ValidationError
6 from webargs import fields
47 from webargs.pyramidparser import parser, use_args, use_kwargs
58 from webargs.core import MARSHMALLOW_VERSION_INFO
69
1720
1821
1922 def echo(request):
20 return parser.parse(hello_args, request)
23 try:
24 return parser.parse(hello_args, request)
25 except json.JSONDecodeError:
26 error = HTTPBadRequest()
27 error.body = json.dumps(["Invalid JSON."]).encode("utf-8")
28 error.content_type = "application/json"
29 raise error
2130
2231
2332 def echo_query(request):
5968
6069 def always_error(request):
6170 def always_fail(value):
62 raise ValidationError("something went wrong")
63
64 argmap = {"text": fields.Str(validate=always_fail)}
65 return parser.parse(argmap, request)
66
67
68 def error400(request):
69 def always_fail(value):
70 raise ValidationError("something went wrong", status_code=400)
71 raise ma.ValidationError("something went wrong")
7172
7273 argmap = {"text": fields.Str(validate=always_fail)}
7374 return parser.parse(argmap, request)
140141 echo_use_kwargs_with_path_param,
141142 )
142143 add_route(config, "/error", always_error)
143 add_route(config, "/error400", error400)
144144 add_route(config, "/echo_headers", echo_headers)
145145 add_route(config, "/echo_cookie", echo_cookie)
146146 add_route(config, "/echo_file", echo_file)
0 import pytest
1
2 pytest.register_assert_rewrite("webargs.testing")
1414 Parser,
1515 get_value,
1616 dict2schema,
17 argmap2schema,
1817 is_json,
1918 get_mimetype,
2019 MARSHMALLOW_VERSION_INFO,
124123 def test_parse_required_arg_raises_validation_error(parser, web_request):
125124 web_request.json = {}
126125 args = {"foo": fields.Field(required=True)}
127 with pytest.raises(ValidationError) as excinfo:
126 with pytest.raises(ValidationError, match="Missing data for required field."):
128127 parser.parse(args, web_request)
129 assert "Missing data for required field." in str(excinfo)
130 exc = excinfo.value
131 assert exc.status_code == 422
132
133
134 # https://github.com/marshmallow-code/webargs/issues/180#issuecomment-394869645
135 def test_overriding_default_status_code(web_request):
136 class MockRequestParserThatReturns400s(MockRequestParser):
137 DEFAULT_VALIDATION_STATUS = 400
138
139 parser = MockRequestParserThatReturns400s()
140
141 web_request.json = {}
142 args = {"foo": fields.Field(required=True)}
143 with pytest.raises(ValidationError) as excinfo:
144 parser.parse(args, web_request)
145 assert "Missing data for required field." in str(excinfo)
146 exc = excinfo.value
147 assert exc.status_code == 400
148128
149129
150130 def test_arg_not_required_excluded_in_parsed_output(parser, web_request):
295275 class CustomError(Exception):
296276 pass
297277
298 def error_handler(error, req, schema):
278 def error_handler(error, req, schema, status_code, headers):
299279 assert isinstance(schema, Schema)
300280 raise CustomError(error)
301281
315295 parser = Parser()
316296
317297 @parser.error_handler
318 def handle_error(error, req, schema):
298 def handle_error(error, req, schema, status_code, headers):
319299 assert isinstance(schema, Schema)
320300 raise CustomError(error)
321301
510490 parser.parse(args, web_request, locations=("json",))
511491 assert "Content-Type" in excinfo.value.messages
512492 assert excinfo.value.messages["Content-Type"] == ["Not a valid string."]
513
514
515 def test_parse_with_force_all(web_request, parser):
516 web_request.json = {"foo": 42}
517
518 args = {"foo": fields.Int(), "bar": fields.Int(required=False)}
519
520 parsed = parser.parse(args, web_request, force_all=True)
521 assert parsed["foo"] == 42
522 assert parsed["bar"] is missing
523493
524494
525495 def test_parse_nested_with_data_key(web_request):
846816
847817
848818 def test_use_kwargs_with_arg_missing(web_request, parser):
849 user_args = {"username": fields.Str(), "password": fields.Str()}
850 web_request.json = {"username": "foo"}
851
852 @parser.use_kwargs(user_args, web_request)
853 def viewfunc(username, password):
854 return {"username": username, "password": password}
855
856 assert viewfunc() == {"username": "foo", "password": missing}
857
858
859 # https://github.com/marshmallow-code/webargs/issues/252
860 def test_use_kwargs_force_all_false(web_request, parser):
861819 user_args = {"username": fields.Str(required=True), "password": fields.Str()}
862820 web_request.json = {"username": "foo"}
863821
864 @parser.use_kwargs(user_args, web_request, force_all=False)
822 @parser.use_kwargs(user_args, web_request)
865823 def viewfunc(username, **kwargs):
866824 assert "password" not in kwargs
867825 return {"username": username}
868826
869827 assert viewfunc() == {"username": "foo"}
870
871 class MySchema(Schema):
872 username = fields.Str(required=True)
873 password = fields.Str()
874
875 web_request.json = {"username": "sloria"}
876
877 # https://github.com/marshmallow-code/webargs/pull/307#issuecomment-441139909
878 @parser.use_kwargs(MySchema(partial=True), web_request, force_all=False)
879 def viewfunc2(username, **kwargs):
880 assert "password" not in kwargs
881 return {"username": username}
882
883 assert viewfunc2() == {"username": "sloria"}
884828
885829
886830 def test_delimited_list_default_delimiter(web_request, parser):
1003947 assert errors["name"] == ["Something went wrong."]
1004948
1005949
1006 class TestValidationError:
1007 def test_status_code_is_deprecated(self):
1008 with pytest.warns(DeprecationWarning):
1009 err = ValidationError("foo", status_code=401)
1010 assert err.status_code == 401
1011
1012 def test_headers_is_deprecated(self):
1013 with pytest.warns(DeprecationWarning):
1014 err = ValidationError("foo", headers={"X-Food-Header": "pizza"})
1015 assert err.headers == {"X-Food-Header": "pizza"}
1016
1017 def test_str(self):
1018 err = ValidationError("foo", status_code=403)
1019 assert str(err) == "foo"
1020
1021 def test_repr(self):
1022 err = ValidationError("foo", status_code=403)
1023 assert repr(err) == (
1024 "ValidationError({0!r}, " "status_code=403, headers=None)".format("foo")
1025 )
1026
1027
1028950 def test_parse_basic(web_request, parser):
1029951 web_request.json = {"foo": "42"}
1030952 args = {"foo": fields.Int()}
1060982 assert schema.fields["id"].required
1061983 if MARSHMALLOW_VERSION_INFO[0] < 3:
1062984 assert schema.opts.strict is True
985 else:
986 assert schema.opts.register is False
1063987
1064988
1065989 # Regression test for https://github.com/marshmallow-code/webargs/issues/101
10941018 assert "foo" in schema.fields["nest"].schema.fields
10951019
10961020
1097 def test_argmap2schema_is_deprecated():
1098 with pytest.warns(DeprecationWarning):
1099 argmap2schema({"arg": fields.Str()})
1100
1101
11021021 def test_is_json():
11031022 assert is_json(None) is False
11041023 assert is_json("application/json") is True
1515 def test_use_args_hook(self, testapp):
1616 assert testapp.get("/echo_use_args_hook?name=Fred").json == {"name": "Fred"}
1717
18 def test_raises_lookup_error_if_invalid_code_is_passed_to_validation_error(
19 self, testapp
20 ):
21 with pytest.raises(LookupError) as excinfo:
22 testapp.get("/error_invalid?text=foo")
23 assert excinfo.value.args[0] == "Status code 12345 not supported"
18 # https://github.com/sloria/webargs/issues/329
19 def test_invalid_json(self, testapp):
20 res = testapp.post(
21 "/echo",
22 '{"foo": "bar", }',
23 headers={"Accept": "application/json", "Content-Type": "application/json"},
24 expect_errors=True,
25 )
26 assert res.status_code == 400
27 assert res.json["errors"] == {"json": ["Invalid JSON body."]}
00 # -*- coding: utf-8 -*-
11 from __future__ import unicode_literals
2 import json
32 import mock
43
54 from werkzeug.exceptions import HTTPException
87 from flask import Flask
98 from webargs import fields, ValidationError, missing
109 from webargs.flaskparser import parser, abort
11 from webargs.core import MARSHMALLOW_VERSION_INFO
10 from webargs.core import MARSHMALLOW_VERSION_INFO, json
1211
1312 from .apps.flask_app import app
1413 from webargs.testing import CommonTestCase
2524 def test_parsing_invalid_view_arg(self, testapp):
2625 res = testapp.get("/echo_view_arg/foo", expect_errors=True)
2726 assert res.status_code == 422
28 assert res.json == {"errors": {"view_arg": ["Not a valid integer."]}}
27 assert res.json == {"view_arg": ["Not a valid integer."]}
2928
3029 def test_use_args_with_view_args_parsing(self, testapp):
3130 res = testapp.get("/echo_view_arg_use_args/42")
3434 assert testapp.get("/echo_method?name=Steve").json == {"name": "Steve"}
3535 assert testapp.get("/echo_method_view").json == {"name": "World"}
3636 assert testapp.get("/echo_method_view?name=Steve").json == {"name": "Steve"}
37
38 def test_invalid_status_code_passed_to_validation_error(self, testapp):
39 res = testapp.get("/error_invalid?text=foo", expect_errors=True)
40 assert res.status_code == 500
4137
4238 # regression test for https://github.com/marshmallow-code/webargs/issues/165
4339 def test_multiple_args(self, testapp):
00 # -*- coding: utf-8 -*-
11
2 import json
2 from webargs.core import json
33
44 try:
5 from urllib import urlencode # python2
6 except ImportError:
7 from urllib.parse import urlencode # python3
5 from urllib.parse import urlencode
6 except ImportError: # PY2
7 from urllib import urlencode # type: ignore
88
99 import mock
1010 import pytest
11
12 import marshmallow as ma
1113
1214 import tornado.web
1315 import tornado.httputil
1719 import tornado.ioloop
1820 from tornado.testing import AsyncHTTPTestCase
1921
20 from webargs import fields, missing, ValidationError
22 from webargs import fields, missing
2123 from webargs.tornadoparser import parser, use_args, use_kwargs, get_value
2224 from webargs.core import parse_json
2325
327329
328330 assert parsed["integer"] == [1, 2]
329331 assert parsed["string"] == value
332
333 def test_it_should_raise_when_json_is_invalid(self):
334 attrs = {"foo": fields.Str()}
335
336 request = make_request(
337 body='{"foo": 42,}', headers={"Content-Type": "application/json"}
338 )
339 with pytest.raises(tornado.web.HTTPError) as excinfo:
340 parser.parse(attrs, request)
341 error = excinfo.value
342 assert error.status_code == 400
343 assert error.messages == {"json": ["Invalid JSON body."]}
330344
331345 def test_it_should_parse_header_arguments(self):
332346 attrs = {"string": fields.Str(), "integer": fields.List(fields.Int())}
579593
580594
581595 def always_fail(val):
582 raise ValidationError("something went wrong")
596 raise ma.ValidationError("something went wrong")
583597
584598
585599 class AlwaysFailHandler(tornado.web.RequestHandler):
590604 self.write(args)
591605
592606
593 def always_fail_with_400(val):
594 raise ValidationError("something went wrong", status_code=400)
595
596
597 class AlwaysFailWith400Handler(tornado.web.RequestHandler):
598 ARGS = {"name": fields.Str(validate=always_fail_with_400)}
599
600 @use_args(ARGS)
601 def post(self, args):
602 self.write(args)
603
604
605607 validate_app = tornado.web.Application(
606 [
607 (r"/echo", ValidateHandler),
608 (r"/alwaysfail", AlwaysFailHandler),
609 (r"/alwaysfailwith400", AlwaysFailWith400Handler),
610 ]
608 [(r"/echo", ValidateHandler), (r"/alwaysfail", AlwaysFailHandler)]
611609 )
612610
613611
643641 )
644642 assert res.code == 422
645643
646 def test_user_validator_with_status_code(self):
647 res = self.fetch(
648 "/alwaysfailwith400",
649 method="POST",
650 headers={"Content-Type": "application/json"},
651 body=json.dumps({"name": "Steve"}),
652 )
653 assert res.code == 400
654
655644 def test_use_kwargs_with_error(self):
656645 res = self.fetch("/echo", method="GET")
657646 assert res.code == 422
22 try:
33 from urllib.parse import urlencode
44 except ImportError: # PY2
5 from urllib import urlencode
6 import json
5 from urllib import urlencode # type: ignore
6 from webargs.core import json
77
88 import pytest
9 from marshmallow import fields
10 from webargs import ValidationError
9 from marshmallow import fields, ValidationError
1110
1211 import webtest
1312 import webapp2
5958 "/echo", POST=json.dumps(expected), headers={"content-type": "application/json"}
6059 )
6160 assert parser.parse(hello_args, req=request) == expected
61
62
63 def test_parse_invalid_json():
64 request = webapp2.Request.blank(
65 "/echo", POST='{"foo": "bar", }', headers={"content-type": "application/json"}
66 )
67 with pytest.raises(json.JSONDecodeError):
68 parser.parse(hello_args, req=request)
6269
6370
6471 def test_parse_json_with_vendor_media_type():
77 extras = tests
88 deps =
99 marshmallow2: marshmallow==2.15.2
10 marshmallow3: marshmallow>=3.0.0a1,<4.0.0
10 marshmallow3: marshmallow>=3.0.0rc2,<4.0.0
1111 commands =
1212 py27: pytest --ignore=tests/test_py3/ {posargs}
1313 py{35,36,37}: pytest {posargs}
1414
1515 [testenv:lint]
16 extras = lint
17 commands = pre-commit run --all-files --show-diff-on-failure
16 deps = pre-commit~=1.14
17 skip_install = true
18 commands = pre-commit run --all-files
1819
1920 [testenv:docs]
2021 deps = -rdocs/requirements.txt
33 # Make marshmallow's validation functions importable from webargs
44 from marshmallow import validate
55
6 from webargs.core import argmap2schema, dict2schema, WebargsError, ValidationError
6 from webargs.core import dict2schema, ValidationError
77 from webargs import fields
88
9 __version__ = "4.4.1"
9 __version__ = "5.1.2"
1010 __author__ = "Steven Loria"
1111 __license__ = "MIT"
1212
1313
14 __all__ = (
15 "dict2schema",
16 "argmap2schema",
17 "WebargsError",
18 "ValidationError",
19 "fields",
20 "missing",
21 "validate",
22 )
14 __all__ = ("dict2schema", "ValidationError", "fields", "missing", "validate")
0 # -*- coding: utf-8 -*-
10 """aiohttp request argument parsing module.
21
32 Example: ::
2221 app = web.Application()
2322 app.router.add_route('GET', '/', index)
2423 """
25 import json
26 import warnings
24 import typing
2725
28 import aiohttp
2926 from aiohttp import web
27 from aiohttp.web import Request
3028 from aiohttp import web_exceptions
29 from marshmallow import Schema, ValidationError
30 from marshmallow.fields import Field
3131
3232 from webargs import core
33 from webargs.core import json
3334 from webargs.asyncparser import AsyncParser
3435
35 AIOHTTP_MAJOR_VERSION = int(aiohttp.__version__.split(".")[0])
36 if AIOHTTP_MAJOR_VERSION < 2:
37 warnings.warn(
38 "Support for aiohttp<2.0.0 is deprecated and is removed in webargs 2.0.0",
39 DeprecationWarning,
40 )
4136
42
43 def is_json_request(req):
37 def is_json_request(req: Request) -> bool:
4438 content_type = req.content_type
4539 return core.is_json(content_type)
4640
5246 # Mapping of status codes to exception classes
5347 # Adapted from werkzeug
5448 exception_map = {422: HTTPUnprocessableEntity}
55 # Collect all exceptions from aiohttp.web_exceptions
56 def _find_exceptions():
49
50
51 def _find_exceptions() -> None:
5752 for name in web_exceptions.__all__:
5853 obj = getattr(web_exceptions, name)
5954 try:
6863 exception_map[obj.status_code] = obj
6964
7065
66 # Collect all exceptions from aiohttp.web_exceptions
7167 _find_exceptions()
7268 del _find_exceptions
7369
7975 match_info="parse_match_info", **core.Parser.__location_map__
8076 )
8177
82 def parse_querystring(self, req, name, field):
78 def parse_querystring(self, req: Request, name: str, field: Field) -> typing.Any:
8379 """Pull a querystring value from the request."""
8480 return core.get_value(req.query, name, field)
8581
86 async def parse_form(self, req, name, field):
82 async def parse_form(self, req: Request, name: str, field: Field) -> typing.Any:
8783 """Pull a form value from the request."""
8884 post_data = self._cache.get("post")
8985 if post_data is None:
9086 self._cache["post"] = await req.post()
9187 return core.get_value(self._cache["post"], name, field)
9288
93 async def parse_json(self, req, name, field):
89 async def parse_json(self, req: Request, name: str, field: Field) -> typing.Any:
9490 """Pull a json value from the request."""
9591 json_data = self._cache.get("json")
9692 if json_data is None:
9793 if not (req.body_exists and is_json_request(req)):
9894 return core.missing
9995 try:
100 json_data = await req.json()
96 json_data = await req.json(loads=json.loads)
10197 except json.JSONDecodeError as e:
10298 if e.doc == "":
10399 return core.missing
104100 else:
105 raise e
101 return self.handle_invalid_json_error(e, req)
106102 self._cache["json"] = json_data
107103 return core.get_value(json_data, name, field, allow_many_nested=True)
108104
109 def parse_headers(self, req, name, field):
105 def parse_headers(self, req: Request, name: str, field: Field) -> typing.Any:
110106 """Pull a value from the header data."""
111107 return core.get_value(req.headers, name, field)
112108
113 def parse_cookies(self, req, name, field):
109 def parse_cookies(self, req: Request, name: str, field: Field) -> typing.Any:
114110 """Pull a value from the cookiejar."""
115111 return core.get_value(req.cookies, name, field)
116112
117 def parse_files(self, req, name, field):
113 def parse_files(self, req: Request, name: str, field: Field) -> None:
118114 raise NotImplementedError(
119115 "parse_files is not implemented. You may be able to use parse_form for "
120116 "parsing upload data."
121117 )
122118
123 def parse_match_info(self, req, name, field):
119 def parse_match_info(self, req: Request, name: str, field: Field) -> typing.Any:
124120 """Pull a value from the request's ``match_info``."""
125121 return core.get_value(req.match_info, name, field)
126122
127 def get_request_from_view_args(self, view, args, kwargs):
123 def get_request_from_view_args(
124 self, view: typing.Callable, args: typing.Iterable, kwargs: typing.Mapping
125 ) -> Request:
128126 """Get request object from a handler function or method. Used internally by
129127 ``use_args`` and ``use_kwargs``.
130128 """
139137 assert isinstance(req, web.Request), "Request argument not found for handler"
140138 return req
141139
142 def handle_error(self, error, req, schema, error_status_code, error_headers):
143 """Handle ValidationErrors and return a JSON response of error messages to the client."""
144 error_class = exception_map.get(error_status_code or error.status_code)
140 def handle_error(
141 self,
142 error: ValidationError,
143 req: Request,
144 schema: Schema,
145 error_status_code: typing.Union[int, None] = None,
146 error_headers: typing.Union[typing.Mapping[str, str], None] = None,
147 ) -> "typing.NoReturn":
148 """Handle ValidationErrors and return a JSON response of error messages
149 to the client.
150 """
151 error_class = exception_map.get(
152 error_status_code or self.DEFAULT_VALIDATION_STATUS
153 )
145154 if not error_class:
146 raise LookupError("No exception for {0}".format(error.status_code))
147 headers = error_headers or error.headers
155 raise LookupError("No exception for {0}".format(error_status_code))
156 headers = error_headers
148157 raise error_class(
149158 body=json.dumps(error.messages).encode("utf-8"),
150159 headers=headers,
151160 content_type="application/json",
152161 )
153162
163 def handle_invalid_json_error(
164 self, error: json.JSONDecodeError, req: Request, *args, **kwargs
165 ) -> "typing.NoReturn":
166 error_class = exception_map[400]
167 messages = {"json": ["Invalid JSON body."]}
168 raise error_class(
169 body=json.dumps(messages).encode("utf-8"), content_type="application/json"
170 )
171
154172
155173 parser = AIOHTTPParser()
156 use_args = parser.use_args
157 use_kwargs = parser.use_kwargs
174 use_args = parser.use_args # type: typing.Callable
175 use_kwargs = parser.use_kwargs # type: typing.Callable
0 # -*- coding: utf-8 -*-
10 """Asynchronous request parser. Compatible with Python>=3.5."""
21 import asyncio
3 import collections
42 import functools
53 import inspect
6 import warnings
7
4 import typing
5 from collections.abc import Mapping
6
7 from marshmallow import Schema, ValidationError
8 from marshmallow.fields import Field
89 import marshmallow as ma
910 from marshmallow.utils import missing
1011
1112 from webargs import core
12 from webargs.core import RemovedInWebargs5Warning
13
14 Request = typing.TypeVar("Request")
15 ArgMap = typing.Union[Schema, typing.Mapping[str, Field]]
16 Validate = typing.Union[typing.Callable, typing.Iterable[typing.Callable]]
1317
1418
1519 class AsyncParser(core.Parser):
1721 either coroutines or regular methods.
1822 """
1923
20 async def _parse_request(self, schema, req, locations):
24 async def _parse_request(
25 self, schema: Schema, req: Request, locations: typing.Iterable
26 ) -> typing.Union[dict, list]:
2127 if schema.many:
2228 assert (
2329 "json" in locations
2430 ), "schema.many=True is only supported for JSON location"
25 # The ad hoc Nested field is more like a workaround or a helper, and it servers its
26 # purpose fine. However, if somebody has a desire to re-design the support of
27 # bulk-type arguments, go ahead.
31 # The ad hoc Nested field is more like a workaround or a helper,
32 # and it servers its purpose fine. However, if somebody has a desire
33 # to re-design the support of bulk-type arguments, go ahead.
2834 parsed = await self.parse_arg(
2935 name="json",
3036 field=ma.fields.Nested(schema, many=True),
5965 # TODO: Lots of duplication from core.Parser here. Rethink.
6066 async def parse(
6167 self,
62 argmap,
63 req=None,
64 locations=None,
65 validate=None,
66 force_all=False,
67 error_status_code=None,
68 error_headers=None,
69 ):
68 argmap: ArgMap,
69 req: Request = None,
70 locations: typing.Iterable = None,
71 validate: Validate = None,
72 error_status_code: typing.Union[int, None] = None,
73 error_headers: typing.Union[typing.Mapping[str, str], None] = None,
74 ) -> typing.Union[typing.Mapping, None]:
7075 """Coroutine variant of `webargs.core.Parser`.
7176
7277 Receives the same arguments as `webargs.core.Parser.parse`.
7883 schema = self._get_schema(argmap, req)
7984 try:
8085 parsed = await self._parse_request(
81 schema=schema, req=req, locations=locations
86 schema=schema, req=req, locations=locations or self.locations
8287 )
8388 result = schema.load(parsed)
8489 data = result.data if core.MARSHMALLOW_VERSION_INFO[0] < 3 else result
8590 self._validate_arguments(data, validators)
8691 except ma.exceptions.ValidationError as error:
87 self._on_validation_error(
92 await self._on_validation_error(
8893 error, req, schema, error_status_code, error_headers
8994 )
9095 finally:
9196 self.clear_cache()
92 if force_all:
93 warnings.warn(
94 "Missing arguments will no longer be added to the parsed arguments "
95 "dictionary in version 5.0.0. Pass force_all=False for the new behavior.",
96 RemovedInWebargs5Warning,
97 )
98 core.fill_in_missing_args(data, schema)
9997 return data
98
99 async def _on_validation_error(
100 self,
101 error: ValidationError,
102 req: Request,
103 schema: Schema,
104 error_status_code: typing.Union[int, None],
105 error_headers: typing.Union[typing.Mapping[str, str], None] = None,
106 ) -> None:
107 error_handler = self.error_callback or self.handle_error
108 await error_handler(error, req, schema, error_status_code, error_headers)
100109
101110 def use_args(
102111 self,
103 argmap,
104 req=None,
105 locations=None,
106 as_kwargs=False,
107 validate=None,
108 force_all=None,
109 error_status_code=None,
110 error_headers=None,
111 ):
112 argmap: ArgMap,
113 req: typing.Optional[Request] = None,
114 locations: typing.Iterable = None,
115 as_kwargs: bool = False,
116 validate: Validate = None,
117 error_status_code: typing.Optional[int] = None,
118 error_headers: typing.Union[typing.Mapping[str, str], None] = None,
119 ) -> typing.Callable[..., typing.Callable]:
112120 """Decorator that injects parsed arguments into a view function or method.
113121
114122 Receives the same arguments as `webargs.core.Parser.use_args`.
115123 """
116124 locations = locations or self.locations
117125 request_obj = req
118 force_all_ = force_all if force_all is not None else as_kwargs
119126 # Optimization: If argmap is passed as a dictionary, we only need
120127 # to generate a Schema once
121 if isinstance(argmap, collections.Mapping):
128 if isinstance(argmap, Mapping):
122129 argmap = core.dict2schema(argmap)()
123130
124 def decorator(func):
131 def decorator(func: typing.Callable) -> typing.Callable:
125132 req_ = request_obj
126133
127134 if inspect.iscoroutinefunction(func):
138145 req=req_obj,
139146 locations=locations,
140147 validate=validate,
141 force_all=force_all_,
142148 error_status_code=error_status_code,
143149 error_headers=error_headers,
144150 )
145151 if as_kwargs:
146 kwargs.update(parsed_args)
152 kwargs.update(parsed_args or {})
147153 return await func(*args, **kwargs)
148154 else:
149155 # Add parsed_args after other positional arguments
152158
153159 else:
154160
155 @functools.wraps(func)
161 @functools.wraps(func) # type: ignore
156162 def wrapper(*args, **kwargs):
157163 req_obj = req_
158164
159165 if not req_obj:
160166 req_obj = self.get_request_from_view_args(func, args, kwargs)
161167 # NOTE: At this point, argmap may be a Schema, callable, or dict
162 parsed_args = yield from self.parse( # noqa: B901
168 parsed_args = yield from self.parse( # type: ignore
163169 argmap,
164170 req=req_obj,
165171 locations=locations,
166172 validate=validate,
167 force_all=force_all_,
168173 error_status_code=error_status_code,
169174 error_headers=error_headers,
170175 )
176181 new_args = args + (parsed_args,)
177182 return func(*new_args, **kwargs)
178183
179 wrapper.__wrapped__ = func
180184 return wrapper
181185
182186 return decorator
183187
184 def use_kwargs(self, *args, **kwargs):
188 def use_kwargs(self, *args, **kwargs) -> typing.Callable:
185189 """Decorator that injects parsed arguments into a view function or method.
186190
187191 Receives the same arguments as `webargs.core.Parser.use_kwargs`.
189193 """
190194 return super().use_kwargs(*args, **kwargs)
191195
192 async def parse_arg(self, name, field, req, locations=None):
196 async def parse_arg(
197 self, name: str, field: Field, req: Request, locations: typing.Iterable = None
198 ) -> typing.Any:
193199 location = field.metadata.get("location")
194200 if location:
195201 locations_to_check = self._validated_locations([location])
203209 return value
204210 return core.missing
205211
206 async def _get_value(self, name, argobj, req, location):
207 # Parsing function to call
208 # May be a method name (str) or a function
209 func = self.__location_map__.get(location)
210 if func:
211 if inspect.isfunction(func):
212 function = func
213 else:
214 function = getattr(self, func)
215 if asyncio.iscoroutinefunction(function):
216 value = await function(req, name, argobj)
217 else:
218 value = function(req, name, argobj)
212 async def _get_value(
213 self, name: str, argobj: Field, req: Request, location: str
214 ) -> typing.Any:
215 function = self._get_handler(location)
216 if asyncio.iscoroutinefunction(function):
217 value = await function(req, name, argobj)
219218 else:
220 raise ValueError('Invalid location: "{0}"'.format(location))
219 value = function(req, name, argobj)
221220 return value
1919 import bottle
2020
2121 from webargs import core
22 from webargs.core import json
2223
2324
2425 class BottleParser(core.Parser):
3839 if json_data is None:
3940 try:
4041 self._cache["json"] = json_data = req.json
41 except (AttributeError, ValueError):
42 except AttributeError:
4243 return core.missing
44 except json.JSONDecodeError as e:
45 if e.doc == "":
46 return core.missing
47 else:
48 return self.handle_invalid_json_error(e, req)
4349 if json_data is None:
4450 return core.missing
4551 return core.get_value(json_data, name, field, allow_many_nested=True)
6066 """Handles errors during parsing. Aborts the current request with a
6167 400 error.
6268 """
63 status_code = error_status_code or getattr(
64 error, "status_code", self.DEFAULT_VALIDATION_STATUS
69 status_code = error_status_code or self.DEFAULT_VALIDATION_STATUS
70 raise bottle.HTTPError(
71 status=status_code,
72 body=error.messages,
73 headers=error_headers,
74 exception=error,
6575 )
66 headers = error_headers or getattr(error, "headers", {})
76
77 def handle_invalid_json_error(self, error, req, *args, **kwargs):
6778 raise bottle.HTTPError(
68 status=status_code, body=error.messages, headers=headers, exception=error
79 status=400, body={"json": ["Invalid JSON body."]}, exception=error
6980 )
7081
7182 def get_default_request(self):
00 # -*- coding: utf-8 -*-
11 from __future__ import unicode_literals
22
3 import collections
43 import functools
54 import inspect
65 import logging
87 from distutils.version import LooseVersion
98
109 try:
10 from collections.abc import Mapping
11 except ImportError:
12 from collections import Mapping
13
14 try:
1115 import simplejson as json
1216 except ImportError:
13 import json
17 import json # type: ignore
1418
1519 import marshmallow as ma
20 from marshmallow import ValidationError
1621 from marshmallow.compat import iteritems
1722 from marshmallow.utils import missing, is_collection
1823
2025
2126
2227 __all__ = [
23 "WebargsError",
2428 "ValidationError",
2529 "dict2schema",
2630 "is_multiple",
3034 "parse_json",
3135 ]
3236
33
34 # Copied from marshmallow.utils
35 def _signature(func):
36 if hasattr(inspect, "signature"):
37 return list(inspect.signature(func).parameters.keys())
38 if hasattr(func, "__self__"):
39 # Remove bound arg to match inspect.signature()
40 return inspect.getargspec(func).args[1:]
41 # All args are unbound
42 return inspect.getargspec(func).args
43
44
45 def get_func_args(func):
46 """Given a callable, return a tuple of argument names. Handles
47 `functools.partial` objects and class-based callables.
48 """
49 if isinstance(func, functools.partial):
50 return _signature(func.func)
51 if inspect.isfunction(func) or inspect.ismethod(func):
52 return _signature(func)
53 # Callable class
54 return _signature(func.__call__)
55
56
57 MARSHMALLOW_VERSION_INFO = tuple(LooseVersion(ma.__version__).version)
58
59 DEFAULT_VALIDATION_STATUS = 422
60
61
62 class RemovedInWebargs5Warning(DeprecationWarning):
63 pass
64
65
66 class WebargsError(Exception):
67 """Base class for all webargs-related errors."""
68
69 pass
70
71
72 class ValidationError(WebargsError, ma.exceptions.ValidationError):
73 """Raised when validation fails on user input.
74
75 .. versionchanged:: 4.2.0
76 status_code and headers arguments are deprecated. Pass
77 error_status_code and error_headers to `Parser.parse`,
78 `Parser.use_args`, and `Parser.use_kwargs` instead.
79 """
80
81 def __init__(self, message, status_code=None, headers=None, **kwargs):
82 if status_code is not None:
83 warnings.warn(
84 "The status_code argument to ValidationError is deprecated "
85 "and will be removed in 5.0.0. "
86 "Pass error_status_code to Parser.parse, Parser.use_args, "
87 "or Parser.use_kwargs instead.",
88 RemovedInWebargs5Warning,
89 )
90 self.status_code = status_code or DEFAULT_VALIDATION_STATUS
91 if headers is not None:
92 warnings.warn(
93 "The headers argument to ValidationError is deprecated "
94 "and will be removed in 5.0.0. "
95 "Pass error_headers to Parser.parse, Parser.use_args, "
96 "or Parser.use_kwargs instead.",
97 RemovedInWebargs5Warning,
98 )
99 self.headers = headers
100 ma.exceptions.ValidationError.__init__(
101 self, message, status_code=status_code, headers=headers, **kwargs
102 )
103
104 def __repr__(self):
105 return "ValidationError({0!r}, status_code={1}, headers={2})".format(
106 self.args[0], self.status_code, self.headers
107 )
37 MARSHMALLOW_VERSION_INFO = tuple(LooseVersion(ma.__version__).version) # type: tuple
38
39 DEFAULT_VALIDATION_STATUS = 422 # type: int
10840
10941
11042 def _callable_or_raise(obj):
11749 return obj
11850
11951
120 def get_field_names_for_argmap(argmap):
121 if isinstance(argmap, ma.Schema):
122 all_field_names = set(
123 [fname for fname, fobj in iteritems(argmap.fields) if not fobj.dump_only]
124 )
125 else:
126 all_field_names = set(argmap.keys())
127 return all_field_names
128
129
130 def fill_in_missing_args(ret, argmap):
131 # WARNING: We modify ret in-place
132 all_field_names = get_field_names_for_argmap(argmap)
133 missing_args = all_field_names - set(ret.keys())
134 for key in missing_args:
135 ret[key] = missing
136 return ret
137
138
13952 def dict2schema(dct):
14053 """Generate a `marshmallow.Schema` class given a dictionary of
14154 `Fields <marshmallow.fields.Field>`.
14255 """
14356 attrs = dct.copy()
144 if MARSHMALLOW_VERSION_INFO[0] < 3:
145
146 class Meta(object):
57
58 class Meta(object):
59 if MARSHMALLOW_VERSION_INFO[0] < 3:
14760 strict = True
148
149 attrs["Meta"] = Meta
61 else:
62 register = False
63
64 attrs["Meta"] = Meta
15065 return type(str(""), (ma.Schema,), attrs)
151
152
153 def argmap2schema(argmap):
154 warnings.warn(
155 "argmap2schema is deprecated. Use dict2schema instead.",
156 RemovedInWebargs5Warning,
157 )
158 return dict2schema(argmap)
15966
16067
16168 def is_multiple(field):
16774 return content_type.split(";")[0].strip() if content_type else None
16875
16976
170 # Adapted from werkzeug: https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/wrappers.py
77 # Adapted from werkzeug:
78 # https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/wrappers.py
17179 def is_json(mimetype):
17280 """Indicates if this mimetype is JSON or not. By default a request
17381 is considered to include JSON data if the mimetype is
220128 return val
221129
222130
223 def parse_json(s):
131 def parse_json(s, encoding="utf-8"):
224132 if isinstance(s, bytes):
225 s = s.decode("utf-8")
133 s = s.decode(encoding)
226134 return json.loads(s)
227135
228136
290198 raise ValueError(msg)
291199 return locations
292200
293 def _get_value(self, name, argobj, req, location):
201 def _get_handler(self, location):
294202 # Parsing function to call
295203 # May be a method name (str) or a function
296204 func = self.__location_map__.get(location)
299207 function = func
300208 else:
301209 function = getattr(self, func)
302 value = function(req, name, argobj)
303210 else:
304211 raise ValueError('Invalid location: "{0}"'.format(location))
305 return value
212 return function
213
214 def _get_value(self, name, argobj, req, location):
215 function = self._get_handler(location)
216 return function(req, name, argobj)
306217
307218 def parse_arg(self, name, field, req, locations=None):
308219 """Parse a single argument from a request.
316227 :param req: The request object to parse.
317228 :param tuple locations: The locations ('json', 'querystring', etc.) where
318229 to search for the value.
319 :return: The unvalidated argument value or `missing` if the value cannot be found
320 on the request.
230 :return: The unvalidated argument value or `missing` if the value cannot
231 be found on the request.
321232 """
322233 location = field.metadata.get("location")
323234 if location:
338249 assert (
339250 "json" in locations
340251 ), "schema.many=True is only supported for JSON location"
341 # The ad hoc Nested field is more like a workaround or a helper, and it servers its
342 # purpose fine. However, if somebody has a desire to re-design the support of
343 # bulk-type arguments, go ahead.
252 # The ad hoc Nested field is more like a workaround or a helper,
253 # and it servers its purpose fine. However, if somebody has a desire
254 # to re-design the support of bulk-type arguments, go ahead.
344255 parsed = self.parse_arg(
345256 name="json",
346257 field=ma.fields.Nested(schema, many=True),
371282 def _on_validation_error(
372283 self, error, req, schema, error_status_code, error_headers
373284 ):
374 if isinstance(error, ma.exceptions.ValidationError) and not isinstance(
375 error, ValidationError
376 ):
377 # Raise a webargs error instead
378 kwargs = getattr(error, "kwargs", {})
379 kwargs["data"] = error.data
380 if MARSHMALLOW_VERSION_INFO[0] < 3:
381 kwargs["fields"] = error.fields
382 kwargs["field_names"] = error.field_names
383 else:
384 kwargs["field_name"] = error.field_name
385 if "status_code" not in kwargs:
386 kwargs["status_code"] = self.DEFAULT_VALIDATION_STATUS
387 error = ValidationError(error.messages, **kwargs)
388 if self.error_callback:
389 if len(get_func_args(self.error_callback)) > 3:
390 self.error_callback(
391 error, req, schema, error_status_code, error_headers
392 )
393 else: # Backwards compat with webargs<=4.2.0
394 warnings.warn(
395 "Error handler functions should include error_status_code and "
396 "error_headers args, or include **kwargs in the signature",
397 DeprecationWarning,
398 )
399 self.error_callback(error, req, schema)
400 else:
401 if len(get_func_args(self.handle_error)) > 3:
402 self.handle_error(error, req, schema, error_status_code, error_headers)
403 else:
404 warnings.warn(
405 "handle_error methods should include error_status_code and "
406 "error_headers args, or include **kwargs in the signature",
407 DeprecationWarning,
408 )
409 self.handle_error(error, req, schema)
285 error_handler = self.error_callback or self.handle_error
286 error_handler(error, req, schema, error_status_code, error_headers)
410287
411288 def _validate_arguments(self, data, validators):
412289 for validator in validators:
445322 req=None,
446323 locations=None,
447324 validate=None,
448 force_all=False,
449325 error_status_code=None,
450326 error_headers=None,
451327 ):
461337 :param callable validate: Validation function or list of validation functions
462338 that receives the dictionary of parsed arguments. Validator either returns a
463339 boolean or raises a :exc:`ValidationError`.
464 :param bool force_all: If `True`, missing arguments will be replaced with
465 `missing <marshmallow.utils.missing>`.
466340 :param int error_status_code: Status code passed to error handler functions when
467341 a `ValidationError` is raised.
468342 :param dict error_headers: Headers passed to error handler functions when a
476350 validators = _ensure_list_of_callables(validate)
477351 schema = self._get_schema(argmap, req)
478352 try:
479 parsed = self._parse_request(schema=schema, req=req, locations=locations)
353 parsed = self._parse_request(
354 schema=schema, req=req, locations=locations or self.locations
355 )
480356 result = schema.load(parsed)
481357 data = result.data if MARSHMALLOW_VERSION_INFO[0] < 3 else result
482358 self._validate_arguments(data, validators)
486362 )
487363 finally:
488364 self.clear_cache()
489 if force_all:
490 warnings.warn(
491 "Missing arguments will no longer be added to the parsed arguments "
492 "dictionary in version 5.0.0. Pass force_all=False for the new behavior.",
493 RemovedInWebargs5Warning,
494 )
495 fill_in_missing_args(data, schema)
496365 return data
497366
498367 def clear_cache(self):
527396 locations=None,
528397 as_kwargs=False,
529398 validate=None,
530 force_all=None,
531399 error_status_code=None,
532400 error_headers=None,
533401 ):
548416 :param callable validate: Validation function that receives the dictionary
549417 of parsed arguments. If the function returns ``False``, the parser
550418 will raise a :exc:`ValidationError`.
551 :param bool force_all: If `True`, missing arguments will be included
552 in the parsed arguments dictionary with the ``missing`` value.
553 If `False`, missing values will be omitted. If `None`, fall back
554 to the value of ``as_kwargs``.
555419 :param int error_status_code: Status code passed to error handler functions when
556420 a `ValidationError` is raised.
557421 :param dict error_headers: Headers passed to error handler functions when a
561425 request_obj = req
562426 # Optimization: If argmap is passed as a dictionary, we only need
563427 # to generate a Schema once
564 if isinstance(argmap, collections.Mapping):
428 if isinstance(argmap, Mapping):
565429 argmap = dict2schema(argmap)()
566430
567431 def decorator(func):
568432 req_ = request_obj
569 force_all_ = force_all if force_all is not None else as_kwargs
570433
571434 @functools.wraps(func)
572435 def wrapper(*args, **kwargs):
580443 req=req_obj,
581444 locations=locations,
582445 validate=validate,
583 force_all=force_all_,
584446 error_status_code=error_status_code,
585447 error_headers=error_headers,
586448 )
1717 def get(self, args, request):
1818 return HttpResponse('Hello ' + args['name'])
1919 """
20 import json
21
2220 from webargs import core
21 from webargs.core import json
2322
2423
2524 class DjangoParser(core.Parser):
4342
4443 def parse_json(self, req, name, field):
4544 """Pull a json value from the request body."""
46 try:
47 json_data = json.loads(req.body.decode("utf-8"))
48 except (AttributeError, ValueError):
49 return core.missing
45 json_data = self._cache.get("json")
46 if json_data is None:
47 try:
48 self._cache["json"] = json_data = core.parse_json(req.body)
49 except AttributeError:
50 return core.missing
51 except json.JSONDecodeError as e:
52 if e.doc == "":
53 return core.missing
54 else:
55 return self.handle_invalid_json_error(e, req)
5056 return core.get_value(json_data, name, field, allow_many_nested=True)
5157
5258 def parse_cookies(self, req, name, field):
6975 except AttributeError: # first arg is request
7076 return args[0]
7177
78 def handle_invalid_json_error(self, error, req, *args, **kwargs):
79 raise error
80
7281
7382 parser = DjangoParser()
7483 use_args = parser.use_args
11 """Falcon request argument parsing module.
22 """
33 import falcon
4 from falcon.util.uri import parse_query_string
45
56 from webargs import core
6 from falcon.util.uri import parse_query_string
7 from webargs.core import json
78
89 HTTP_422 = "422 Unprocessable Entity"
910
3738 if body:
3839 try:
3940 return core.parse_json(body)
40 except (TypeError, ValueError):
41 pass
41 except json.JSONDecodeError as e:
42 if e.doc == "":
43 return core.missing
44 else:
45 raise
4246 return {}
4347
4448
111115 """
112116 json_data = self._cache.get("json_data")
113117 if json_data is None:
114 self._cache["json_data"] = json_data = parse_json_body(req)
118 try:
119 self._cache["json_data"] = json_data = parse_json_body(req)
120 except json.JSONDecodeError as e:
121 return self.handle_invalid_json_error(e, req)
115122 return core.get_value(json_data, name, field, allow_many_nested=True)
116123
117124 def parse_headers(self, req, name, field):
141148
142149 def handle_error(self, error, req, schema, error_status_code, error_headers):
143150 """Handles errors during parsing."""
144 status = status_map.get(error_status_code or error.status_code)
151 status = status_map.get(error_status_code or self.DEFAULT_VALIDATION_STATUS)
145152 if status is None:
146 raise LookupError("Status code {0} not supported".format(error.status_code))
153 raise LookupError("Status code {0} not supported".format(error_status_code))
147154 raise HTTPError(status, errors=error.messages, headers=error_headers)
155
156 def handle_invalid_json_error(self, error, req, *args, **kwargs):
157 status = status_map[400]
158 messages = {"json": ["Invalid JSON body."]}
159 raise HTTPError(status, errors=messages)
148160
149161
150162 parser = FalconParser()
33 Includes all fields from `marshmallow.fields` in addition to a custom
44 `Nested` field and `DelimitedList`.
55
6 All fields can optionally take a special `location` keyword argument, which tells webargs
7 where to parse the request argument from. ::
6 All fields can optionally take a special `location` keyword argument, which
7 tells webargs where to parse the request argument from. ::
88
99 args = {
1010 'active': fields.Bool(location='query')
1212 location='headers')
1313 }
1414
15 Note: `data_key` replaced `load_from` in marshmallow 3. When using marshmallow 2, use `load_from`.
15 Note: `data_key` replaced `load_from` in marshmallow 3.
16 When using marshmallow 2, use `load_from`.
1617 """
1718 import marshmallow as ma
1819
2222 from werkzeug.exceptions import HTTPException
2323
2424 from webargs import core
25 from webargs.core import json
2526
2627
2728 def abort(http_status_code, exc=None, **kwargs):
5354
5455 def parse_json(self, req, name, field):
5556 """Pull a json value from the request."""
56 # Pass force in order to handle vendor media types,
57 # e.g. applications/vnd.json+api
58 # this should be unnecessary in Flask 1.0
59 force = is_json_request(req)
60 # Fail silently so that the webargs parser can handle the error
61 if hasattr(req, "get_json"):
62 # Flask >= 0.10.x
63 json_data = req.get_json(force=force, silent=True)
64 else:
65 # Flask <= 0.9.x
66 json_data = req.json
57 json_data = self._cache.get("json")
6758 if json_data is None:
68 return core.missing
59 # We decode the json manually here instead of
60 # using req.get_json() so that we can handle
61 # JSONDecodeErrors consistently
62 data = req.get_data(cache=True)
63 try:
64 self._cache["json"] = json_data = core.parse_json(data)
65 except json.JSONDecodeError as e:
66 if e.doc == "":
67 return core.missing
68 else:
69 return self.handle_invalid_json_error(e, req)
6970 return core.get_value(json_data, name, field, allow_many_nested=True)
7071
7172 def parse_querystring(self, req, name, field):
9697 """Handles errors during parsing. Aborts the current HTTP request and
9798 responds with a 422 error.
9899 """
99 status_code = error_status_code or getattr(
100 error, "status_code", self.DEFAULT_VALIDATION_STATUS
101 )
102 headers = error_headers or getattr(error, "headers", None)
100 status_code = error_status_code or self.DEFAULT_VALIDATION_STATUS
103101 abort(
104102 status_code,
105103 exc=error,
106104 messages=error.messages,
107105 schema=schema,
108 headers=headers,
106 headers=error_headers,
109107 )
108
109 def handle_invalid_json_error(self, error, req, *args, **kwargs):
110 abort(400, exc=error, messages={"json": ["Invalid JSON body."]})
110111
111112 def get_default_request(self):
112113 """Override to use Flask's thread-local request objec by default"""
3232
3333 from marshmallow.compat import text_type
3434 from webargs import core
35 from webargs.core import json
3536
3637
3738 class PyramidParser(core.Parser):
4950
5051 def parse_json(self, req, name, field):
5152 """Pull a json value from the request."""
52 try:
53 json_data = req.json_body
54 except ValueError:
55 return core.missing
53 json_data = self._cache.get("json")
54 if json_data is None:
55 try:
56 self._cache["json"] = json_data = core.parse_json(req.body, req.charset)
57 except json.JSONDecodeError as e:
58 if e.doc == "":
59 return core.missing
60 else:
61 return self.handle_invalid_json_error(e, req)
62 if json_data is None:
63 return core.missing
5664 return core.get_value(json_data, name, field, allow_many_nested=True)
5765
5866 def parse_cookies(self, req, name, field):
7684 """Handles errors during parsing. Aborts the current HTTP request and
7785 responds with a 400 error.
7886 """
79 status_code = error_status_code or getattr(error, "status_code", 422)
80 raise exception_response(
81 status_code, detail=text_type(error), headers=error_headers
87 status_code = error_status_code or self.DEFAULT_VALIDATION_STATUS
88 response = exception_response(
89 status_code,
90 detail=text_type(error),
91 headers=error_headers,
92 content_type="application/json",
8293 )
94 body = json.dumps(error.messages)
95 response.body = body.encode("utf-8") if isinstance(body, text_type) else body
96 raise response
97
98 def handle_invalid_json_error(self, error, req, *args, **kwargs):
99 messages = {"json": ["Invalid JSON body."]}
100 response = exception_response(
101 400, detail=text_type(messages), content_type="application/json"
102 )
103 body = json.dumps(messages)
104 response.body = body.encode("utf-8") if isinstance(body, text_type) else body
105 raise response
83106
84107 def use_args(
85108 self,
88111 locations=core.Parser.DEFAULT_LOCATIONS,
89112 as_kwargs=False,
90113 validate=None,
91 force_all=None,
92114 error_status_code=None,
93115 error_headers=None,
94116 ):
105127 :param callable validate: Validation function that receives the dictionary
106128 of parsed arguments. If the function returns ``False``, the parser
107129 will raise a :exc:`ValidationError`.
108 :param bool force_all: If `True`, missing arguments will be included
109 in the parsed arguments dictionary with the ``missing`` value.
110 If `False`, missing values will be omitted. If `None`, fall back
111 to the value of ``as_kwargs``.
112130 :param int error_status_code: Status code passed to error handler functions when
113131 a `ValidationError` is raised.
114132 :param dict error_headers: Headers passed to error handler functions when a
121139 argmap = core.dict2schema(argmap)()
122140
123141 def decorator(func):
124 force_all_ = force_all if force_all is not None else as_kwargs
125
126142 @functools.wraps(func)
127143 def wrapper(obj, *args, **kwargs):
128144 # The first argument is either `self` or `request`
136152 req=request,
137153 locations=locations,
138154 validate=validate,
139 force_all=force_all_,
140155 error_status_code=error_status_code,
141156 error_headers=error_headers,
142157 )
66 Methods and functions in this module may change without
77 warning and without a major version change.
88 """
9 import json
10
119 import pytest
1210 import webtest
1311
14 from webargs.core import MARSHMALLOW_VERSION_INFO
12 from webargs.core import json
1513
1614
1715 class CommonTestCase(object):
126124 res = testapp.post_json("/error", {"text": "foo"}, expect_errors=True)
127125 assert res.status_code == 422
128126
129 @pytest.mark.skipif(
130 MARSHMALLOW_VERSION_INFO < (2, 7),
131 reason="status_code only works in marshmallow>=2.7",
132 )
133 def test_user_validation_error_with_status_code(self, testapp):
134 res = testapp.post_json("/error400", {"text": "foo"}, expect_errors=True)
135 assert res.status_code == 400
136
137127 def test_use_args_decorator(self, testapp):
138128 assert testapp.get("/echo_use_args?name=Fred").json == {"name": "Fred"}
139129
193183 "/echo_file", {"myfile": webtest.Upload("README.rst", b"data")}
194184 )
195185 assert res.json == {"myfile": "data"}
186
187 # https://github.com/sloria/webargs/pull/297
188 def test_empty_json(self, testapp):
189 res = testapp.post(
190 "/echo",
191 "",
192 headers={"Accept": "application/json", "Content-Type": "application/json"},
193 )
194 assert res.status_code == 200
195 assert res.json == {"name": "World"}
196
197 # https://github.com/sloria/webargs/issues/329
198 def test_invalid_json(self, testapp):
199 res = testapp.post(
200 "/echo",
201 '{"foo": "bar", }',
202 headers={"Accept": "application/json", "Content-Type": "application/json"},
203 expect_errors=True,
204 )
205 assert res.status_code == 400
206 assert res.json == {"json": ["Invalid JSON body."]}
1818
1919 from marshmallow.compat import basestring
2020 from webargs import core
21 from webargs.core import json
2122
2223
2324 class HTTPError(tornado.web.HTTPError):
3536 if content_type and core.is_json(content_type):
3637 try:
3738 return core.parse_json(req.body)
38 except (TypeError, ValueError):
39 except TypeError:
3940 pass
41 except json.JSONDecodeError as e:
42 if e.doc == "":
43 return core.missing
44 else:
45 raise
4046 return {}
4147
4248
8389 """Pull a json value from the request."""
8490 json_data = self._cache.get("json")
8591 if json_data is None:
86 self._cache["json"] = json_data = parse_json_body(req)
92 try:
93 self._cache["json"] = json_data = parse_json_body(req)
94 except json.JSONDecodeError as e:
95 return self.handle_invalid_json_error(e, req)
8796 if json_data is None:
8897 return core.missing
8998 return core.get_value(json_data, name, field, allow_many_nested=True)
117126 """Handles errors during parsing. Raises a `tornado.web.HTTPError`
118127 with a 400 error.
119128 """
120 status_code = error_status_code or getattr(
121 error, "status_code", core.DEFAULT_VALIDATION_STATUS
122 )
129 status_code = error_status_code or self.DEFAULT_VALIDATION_STATUS
123130 if status_code == 422:
124131 reason = "Unprocessable Entity"
125132 else:
132139 headers=error_headers,
133140 )
134141
142 def handle_invalid_json_error(self, error, req, *args, **kwargs):
143 raise HTTPError(
144 400,
145 log_message="Invalid JSON body.",
146 reason="Bad Request",
147 messages={"json": ["Invalid JSON body."]},
148 )
149
135150 def get_request_from_view_args(self, view, args, kwargs):
136151 return args[0].request
137152
2626 webapp2.Route(r'/hello_dict', MainPage, handler_method='get_kwargs'),
2727 ], debug=True)
2828 """
29 import webapp2
30 import webob.multidict
31
2932 from webargs import core
30 import webapp2
31 import webapp2_extras.json
32 import webob.multidict
33 from webargs.core import json
3334
3435
3536 class Webapp2Parser(core.Parser):
3738
3839 def parse_json(self, req, name, field):
3940 """Pull a json value from the request."""
40 try:
41 json_data = webapp2_extras.json.decode(req.body)
42 except ValueError:
43 return core.missing
41 json_data = self._cache.get("json")
42 if json_data is None:
43 try:
44 self._cache["json"] = json_data = core.parse_json(req.body)
45 except json.JSONDecodeError as e:
46 if e.doc == "":
47 return core.missing
48 else:
49 raise
4450 return core.get_value(json_data, name, field, allow_many_nested=True)
4551
4652 def parse_querystring(self, req, name, field):