1from flask import Flask, jsonify as J, Response, request 2from flask.views import MethodView 3import marshmallow as ma 4 5from webargs import fields 6from webargs.flaskparser import parser, use_args, use_kwargs 7from webargs.core import json 8 9 10class TestAppConfig: 11 TESTING = True 12 13 14hello_args = {"name": fields.Str(missing="World", validate=lambda n: len(n) >= 3)} 15hello_multiple = {"name": fields.List(fields.Str())} 16 17 18class HelloSchema(ma.Schema): 19 name = fields.Str(missing="World", validate=lambda n: len(n) >= 3) 20 21 22hello_many_schema = HelloSchema(many=True) 23 24app = Flask(__name__) 25app.config.from_object(TestAppConfig) 26 27 28@app.route("/echo", methods=["GET"]) 29def echo(): 30 return J(parser.parse(hello_args, location="query")) 31 32 33@app.route("/echo_form", methods=["POST"]) 34def echo_form(): 35 return J(parser.parse(hello_args, location="form")) 36 37 38@app.route("/echo_json", methods=["POST"]) 39def echo_json(): 40 return J(parser.parse(hello_args, location="json")) 41 42 43@app.route("/echo_json_or_form", methods=["POST"]) 44def echo_json_or_form(): 45 return J(parser.parse(hello_args, location="json_or_form")) 46 47 48@app.route("/echo_use_args", methods=["GET"]) 49@use_args(hello_args, location="query") 50def echo_use_args(args): 51 return J(args) 52 53 54@app.route("/echo_use_args_validated", methods=["POST"]) 55@use_args( 56 {"value": fields.Int()}, validate=lambda args: args["value"] > 42, location="form" 57) 58def echo_use_args_validated(args): 59 return J(args) 60 61 62@app.route("/echo_ignoring_extra_data", methods=["POST"]) 63def echo_json_ignore_extra_data(): 64 return J(parser.parse(hello_args, unknown=ma.EXCLUDE)) 65 66 67@app.route("/echo_use_kwargs", methods=["GET"]) 68@use_kwargs(hello_args, location="query") 69def echo_use_kwargs(name): 70 return J({"name": name}) 71 72 73@app.route("/echo_multi", methods=["GET"]) 74def multi(): 75 return J(parser.parse(hello_multiple, location="query")) 76 77 78@app.route("/echo_multi_form", methods=["POST"]) 79def multi_form(): 80 return J(parser.parse(hello_multiple, location="form")) 81 82 83@app.route("/echo_multi_json", methods=["POST"]) 84def multi_json(): 85 return J(parser.parse(hello_multiple)) 86 87 88@app.route("/echo_many_schema", methods=["GET", "POST"]) 89def many_nested(): 90 arguments = parser.parse(hello_many_schema) 91 return Response(json.dumps(arguments), content_type="application/json") 92 93 94@app.route("/echo_use_args_with_path_param/<name>") 95@use_args({"value": fields.Int()}, location="query") 96def echo_use_args_with_path(args, name): 97 return J(args) 98 99 100@app.route("/echo_use_kwargs_with_path_param/<name>") 101@use_kwargs({"value": fields.Int()}, location="query") 102def echo_use_kwargs_with_path(name, value): 103 return J({"value": value}) 104 105 106@app.route("/error", methods=["GET", "POST"]) 107def error(): 108 def always_fail(value): 109 raise ma.ValidationError("something went wrong") 110 111 args = {"text": fields.Str(validate=always_fail)} 112 return J(parser.parse(args)) 113 114 115@app.route("/echo_headers") 116def echo_headers(): 117 return J(parser.parse(hello_args, location="headers")) 118 119 120# as above, but in this case, turn off the default `EXCLUDE` behavior for 121# `headers`, so that errors will be raised 122@app.route("/echo_headers_raising") 123@use_args(HelloSchema(), location="headers", unknown=None) 124def echo_headers_raising(args): 125 return J(args) 126 127 128@app.route("/echo_cookie") 129def echo_cookie(): 130 return J(parser.parse(hello_args, request, location="cookies")) 131 132 133@app.route("/echo_file", methods=["POST"]) 134def echo_file(): 135 args = {"myfile": fields.Field()} 136 result = parser.parse(args, location="files") 137 fp = result["myfile"] 138 content = fp.read().decode("utf8") 139 return J({"myfile": content}) 140 141 142@app.route("/echo_view_arg/<view_arg>") 143def echo_view_arg(view_arg): 144 return J(parser.parse({"view_arg": fields.Int()}, location="view_args")) 145 146 147@app.route("/echo_view_arg_use_args/<view_arg>") 148@use_args({"view_arg": fields.Int()}, location="view_args") 149def echo_view_arg_with_use_args(args, **kwargs): 150 return J(args) 151 152 153@app.route("/echo_nested", methods=["POST"]) 154def echo_nested(): 155 args = {"name": fields.Nested({"first": fields.Str(), "last": fields.Str()})} 156 return J(parser.parse(args)) 157 158 159@app.route("/echo_nested_many", methods=["POST"]) 160def echo_nested_many(): 161 args = { 162 "users": fields.Nested({"id": fields.Int(), "name": fields.Str()}, many=True) 163 } 164 return J(parser.parse(args)) 165 166 167@app.route("/echo_nested_many_data_key", methods=["POST"]) 168def echo_nested_many_with_data_key(): 169 args = { 170 "x_field": fields.Nested({"id": fields.Int()}, many=True, data_key="X-Field") 171 } 172 return J(parser.parse(args)) 173 174 175class EchoMethodViewUseArgs(MethodView): 176 @use_args({"val": fields.Int()}) 177 def post(self, args): 178 return J(args) 179 180 181app.add_url_rule( 182 "/echo_method_view_use_args", 183 view_func=EchoMethodViewUseArgs.as_view("echo_method_view_use_args"), 184) 185 186 187class EchoMethodViewUseKwargs(MethodView): 188 @use_kwargs({"val": fields.Int()}) 189 def post(self, val): 190 return J({"val": val}) 191 192 193app.add_url_rule( 194 "/echo_method_view_use_kwargs", 195 view_func=EchoMethodViewUseKwargs.as_view("echo_method_view_use_kwargs"), 196) 197 198 199@app.route("/echo_use_kwargs_missing", methods=["post"]) 200@use_kwargs({"username": fields.Str(required=True), "password": fields.Str()}) 201def echo_use_kwargs_missing(username, **kwargs): 202 assert "password" not in kwargs 203 return J({"username": username}) 204 205 206# Return validation errors as JSON 207@app.errorhandler(422) 208@app.errorhandler(400) 209def handle_error(err): 210 if err.code == 422: 211 assert isinstance(err.data["schema"], ma.Schema) 212 213 return J(err.data["messages"]), err.code 214