File: intermediate-usage.rst

package info (click to toggle)
flask-restful 0.3.9-6
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 968 kB
  • sloc: python: 3,922; makefile: 278; pascal: 26; sh: 20
file content (278 lines) | stat: -rw-r--r-- 9,785 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
.. _intermediate:

Intermediate Usage
==================

.. currentmodule:: flask_restful

This page covers building a slightly more complex Flask-RESTful app that will
cover out some best practices when setting up a real-world Flask-RESTful-based
API. The :ref:`quickstart` section is great for getting started with your first
Flask-RESTful app, so if you're new to Flask-RESTful you'd be better off
checking that out first.


Project Structure
-----------------

There are many different ways to organize your Flask-RESTful app, but here
we'll describe one that scales pretty well with larger apps and maintains
a nice level organization.

The basic idea is to split your app into three main parts: the routes, the
resources, and any common infrastructure.

Here's an example directory structure: ::

    myapi/
        __init__.py
        app.py          # this file contains your app and routes
        resources/
            __init__.py
            foo.py      # contains logic for /Foo
            bar.py      # contains logic for /Bar
        common/
            __init__.py
            util.py     # just some common infrastructure

The common directory would probably just contain a set of helper functions
to fulfill common needs across your application. It could also contain, for
example, any custom input/output types your resources need to get the job done.

In the resource files, you just have your resource objects. So here's what
``foo.py`` might look like: ::

    from flask_restful import Resource

    class Foo(Resource):
        def get(self):
            pass
        def post(self):
            pass

The key to this setup lies in ``app.py``: ::

    from flask import Flask
    from flask_restful import Api
    from myapi.resources.foo import Foo
    from myapi.resources.bar import Bar
    from myapi.resources.baz import Baz

    app = Flask(__name__)
    api = Api(app)

    api.add_resource(Foo, '/Foo', '/Foo/<string:id>')
    api.add_resource(Bar, '/Bar', '/Bar/<string:id>')
    api.add_resource(Baz, '/Baz', '/Baz/<string:id>')

As you can imagine with a particularly large or complex API, this file ends up
being very valuable as a comprehensive list of all the routes and resources in
your API. You would also use this file to set up any config values
(:meth:`~flask.Flask.before_request`, :meth:`~flask.Flask.after_request`).
Basically, this file configures your entire API.

The things in the common directory are just things you'd want to support your
resource modules.

Use With Blueprints
-------------------

See :ref:`blueprints` in the Flask documentation for what blueprints are and
why you should use them. Here's an example of how to link an :class:`Api`
up to a :class:`~flask.Blueprint`. ::

    from flask import Flask, Blueprint
    from flask_restful import Api, Resource, url_for

    app = Flask(__name__)
    api_bp = Blueprint('api', __name__)
    api = Api(api_bp)

    class TodoItem(Resource):
        def get(self, id):
            return {'task': 'Say "Hello, World!"'}

    api.add_resource(TodoItem, '/todos/<int:id>')
    app.register_blueprint(api_bp)

.. note ::

    Calling :meth:`Api.init_app` is not required here because registering the
    blueprint with the app takes care of setting up the routing for the
    application.

Full Parameter Parsing Example
------------------------------

Elsewhere in the documentation, we've described how to use the reqparse example
in detail. Here we'll set up a resource with multiple input parameters that
exercise a larger amount of options. We'll define a resource named "User". ::

    from flask_restful import fields, marshal_with, reqparse, Resource

    def email(email_str):
        """Return email_str if valid, raise an exception in other case."""
        if valid_email(email_str):
            return email_str
        else:
            raise ValueError('{} is not a valid email'.format(email_str))

    post_parser = reqparse.RequestParser()
    post_parser.add_argument(
        'username', dest='username',
        location='form', required=True,
        help='The user\'s username',
    )
    post_parser.add_argument(
        'email', dest='email',
        type=email, location='form',
        required=True, help='The user\'s email',
    )
    post_parser.add_argument(
        'user_priority', dest='user_priority',
        type=int, location='form',
        default=1, choices=range(5), help='The user\'s priority',
    )

    user_fields = {
        'id': fields.Integer,
        'username': fields.String,
        'email': fields.String,
        'user_priority': fields.Integer,
        'custom_greeting': fields.FormattedString('Hey there {username}!'),
        'date_created': fields.DateTime,
        'date_updated': fields.DateTime,
        'links': fields.Nested({
            'friends': fields.Url('user_friends'),
            'posts': fields.Url('user_posts'),
        }),
    }

    class User(Resource):

        @marshal_with(user_fields)
        def post(self):
            args = post_parser.parse_args()
            user = create_user(args.username, args.email, args.user_priority)
            return user

        @marshal_with(user_fields)
        def get(self, id):
            args = post_parser.parse_args()
            user = fetch_user(id)
            return user

As you can see, we create a ``post_parser`` specifically to handle the parsing
of arguments provided on POST. Let's step through the definition of each
argument. ::

    post_parser.add_argument(
        'username', dest='username',
        location='form', required=True,
        help='The user\'s username',
    )

The ``username`` field is the most normal out of all of them. It takes
a string from the POST body and converts it to a string type. This argument
is required (``required=True``), which means that if it isn't provided,
Flask-RESTful will automatically return a 400 with a message along the lines
of 'the username field is required'. ::

    post_parser.add_argument(
        'email', dest='email',
        type=email, location='form',
        required=True, help='The user\'s email',
    )

The ``email`` field has a custom type of ``email``. A few lines earlier we
defined an ``email`` function that takes a string and returns it if the type is
valid, else it raises an exception, exclaiming that the email type was
invalid. ::

    post_parser.add_argument(
        'user_priority', dest='user_priority',
        type=int, location='form',
        default=1, choices=range(5), help='The user\'s priority',
    )

The ``user_priority`` type takes advantage of the ``choices`` argument. This
means that if the provided `user_priority` value doesn't fall in the range
specified by the ``choices`` argument (in this case ``[0, 1, 2, 3, 4]``),
Flask-RESTful will automatically respond with a 400 and a descriptive error
message.

That covers the inputs. We also defined some interesting field types in the
``user_fields`` dictionary to showcase a couple of the more exotic types.  ::

    user_fields = {
        'id': fields.Integer,
        'username': fields.String,
        'email': fields.String,
        'user_priority': fields.Integer,
        'custom_greeting': fields.FormattedString('Hey there {username}!'),
        'date_created': fields.DateTime,
        'date_updated': fields.DateTime,
        'links': fields.Nested({
            'friends': fields.Url('user_friends', absolute=True),
            'posts': fields.Url('user_posts', absolute=True),
        }),
    }

First up, there's :class:`fields.FormattedString`. ::

    'custom_greeting': fields.FormattedString('Hey there {username}!'),

This field is primarily used to interpolate values from the response into
other values. In this instance, ``custom_greeting`` will always contain the
value returned from the ``username`` field.

Next up, check out :class:`fields.Nested`. ::

    'links': fields.Nested({
        'friends': fields.Url('user_friends', absolute=True),
        'posts': fields.Url('user_posts', absolute=True),
    }),

This field is used to create a sub-object in the response. In this case,
we want to create a ``links`` sub-object to contain urls of related objects.
Note that we passed `fields.Nested` another dict which is built in such a
way that it would be an acceptable argument to :func:`marshal` by itself.

Finally, we used the :class:`fields.Url` field type. ::

        'friends': fields.Url('user_friends', absolute=True),
        'posts': fields.Url('user_posts', absolute=True),

It takes as its first parameter the name of the endpoint associated with the
urls of the objects in the ``links`` sub-object.  Passing ``absolute=True``
ensures that the generated urls will have the hostname included.


Passing Constructor Parameters Into Resources
---------------------------------------------
Your :class:`Resource` implementation may require outside dependencies. Those
dependencies are best passed-in through the constructor to loosely couple each
other. The :meth:`Api.add_resource` method has two keyword arguments:
``resource_class_args`` and ``resource_class_kwargs``. Their values will be forwarded
and passed into your Resource implementation's constructor.

So you could have a :class:`Resource`: ::

    from flask_restful import Resource

    class TodoNext(Resource):
        def __init__(self, **kwargs):
            # smart_engine is a black box dependency
            self.smart_engine = kwargs['smart_engine']

        def get(self):
            return self.smart_engine.next_todo()

You can inject the required dependency into TodoNext like so: ::

    smart_engine = SmartEngine()

    api.add_resource(TodoNext, '/next',
        resource_class_kwargs={ 'smart_engine': smart_engine })

Same idea applies for forwarding `args`.