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