File: extensiondev.rst

package info (click to toggle)
flask 3.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,536 kB
  • sloc: python: 10,083; makefile: 32; sql: 22; sh: 19
file content (304 lines) | stat: -rw-r--r-- 12,787 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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
Flask Extension Development
===========================

.. currentmodule:: flask

Extensions are extra packages that add functionality to a Flask
application. While `PyPI`_ contains many Flask extensions, you may not
find one that fits your need. If this is the case, you can create your
own, and publish it for others to use as well.

This guide will show how to create a Flask extension, and some of the
common patterns and requirements involved. Since extensions can do
anything, this guide won't be able to cover every possibility.

The best ways to learn about extensions are to look at how other
extensions you use are written, and discuss with others. Discuss your
design ideas with others on our `Discord Chat`_ or
`GitHub Discussions`_.

The best extensions share common patterns, so that anyone familiar with
using one extension won't feel completely lost with another. This can
only work if collaboration happens early.


Naming
------

A Flask extension typically has ``flask`` in its name as a prefix or
suffix. If it wraps another library, it should include the library name
as well. This makes it easy to search for extensions, and makes their
purpose clearer.

A general Python packaging recommendation is that the install name from
the package index and the name used in ``import`` statements should be
related. The import name is lowercase, with words separated by
underscores (``_``). The install name is either lower case or title
case, with words separated by dashes (``-``). If it wraps another
library, prefer using the same case as that library's name.

Here are some example install and import names:

-   ``Flask-Name`` imported as ``flask_name``
-   ``flask-name-lower`` imported as ``flask_name_lower``
-   ``Flask-ComboName`` imported as ``flask_comboname``
-   ``Name-Flask`` imported as ``name_flask``


The Extension Class and Initialization
--------------------------------------

All extensions will need some entry point that initializes the
extension with the application. The most common pattern is to create a
class that represents the extension's configuration and behavior, with
an ``init_app`` method to apply the extension instance to the given
application instance.

.. code-block:: python

    class HelloExtension:
        def __init__(self, app=None):
            if app is not None:
                self.init_app(app)

        def init_app(self, app):
            app.before_request(...)

It is important that the app is not stored on the extension, don't do
``self.app = app``. The only time the extension should have direct
access to an app is during ``init_app``, otherwise it should use
:data:`current_app`.

This allows the extension to support the application factory pattern,
avoids circular import issues when importing the extension instance
elsewhere in a user's code, and makes testing with different
configurations easier.

.. code-block:: python

    hello = HelloExtension()

    def create_app():
        app = Flask(__name__)
        hello.init_app(app)
        return app

Above, the ``hello`` extension instance exists independently of the
application. This means that other modules in a user's project can do
``from project import hello`` and use the extension in blueprints before
the app exists.

The :attr:`Flask.extensions` dict can be used to store a reference to
the extension on the application, or some other state specific to the
application. Be aware that this is a single namespace, so use a name
unique to your extension, such as the extension's name without the
"flask" prefix.


Adding Behavior
---------------

There are many ways that an extension can add behavior. Any setup
methods that are available on the :class:`Flask` object can be used
during an extension's ``init_app`` method.

A common pattern is to use :meth:`~Flask.before_request` to initialize
some data or a connection at the beginning of each request, then
:meth:`~Flask.teardown_request` to clean it up at the end. This can be
stored on :data:`g`, discussed more below.

A more lazy approach is to provide a method that initializes and caches
the data or connection. For example, a ``ext.get_db`` method could
create a database connection the first time it's called, so that a view
that doesn't use the database doesn't create a connection.

Besides doing something before and after every view, your extension
might want to add some specific views as well. In this case, you could
define a :class:`Blueprint`, then call :meth:`~Flask.register_blueprint`
during ``init_app`` to add the blueprint to the app.


Configuration Techniques
------------------------

There can be multiple levels and sources of configuration for an
extension. You should consider what parts of your extension fall into
each one.

-   Configuration per application instance, through ``app.config``
    values. This is configuration that could reasonably change for each
    deployment of an application. A common example is a URL to an
    external resource, such as a database. Configuration keys should
    start with the extension's name so that they don't interfere with
    other extensions.
-   Configuration per extension instance, through ``__init__``
    arguments. This configuration usually affects how the extension
    is used, such that it wouldn't make sense to change it per
    deployment.
-   Configuration per extension instance, through instance attributes
    and decorator methods. It might be more ergonomic to assign to
    ``ext.value``, or use a ``@ext.register`` decorator to register a
    function, after the extension instance has been created.
-   Global configuration through class attributes. Changing a class
    attribute like ``Ext.connection_class`` can customize default
    behavior without making a subclass. This could be combined
    per-extension configuration to override defaults.
-   Subclassing and overriding methods and attributes. Making the API of
    the extension itself something that can be overridden provides a
    very powerful tool for advanced customization.

The :class:`~flask.Flask` object itself uses all of these techniques.

It's up to you to decide what configuration is appropriate for your
extension, based on what you need and what you want to support.

Configuration should not be changed after the application setup phase is
complete and the server begins handling requests. Configuration is
global, any changes to it are not guaranteed to be visible to other
workers.


Data During a Request
---------------------

When writing a Flask application, the :data:`~flask.g` object is used to
store information during a request. For example the
:doc:`tutorial <tutorial/database>` stores a connection to a SQLite
database as ``g.db``. Extensions can also use this, with some care.
Since ``g`` is a single global namespace, extensions must use unique
names that won't collide with user data. For example, use the extension
name as a prefix, or as a namespace.

.. code-block:: python

    # an internal prefix with the extension name
    g._hello_user_id = 2

    # or an internal prefix as a namespace
    from types import SimpleNamespace
    g._hello = SimpleNamespace()
    g._hello.user_id = 2

The data in ``g`` lasts for an application context. An application
context is active when a request context is, or when a CLI command is
run. If you're storing something that should be closed, use
:meth:`~flask.Flask.teardown_appcontext` to ensure that it gets closed
when the application context ends. If it should only be valid during a
request, or would not be used in the CLI outside a request, use
:meth:`~flask.Flask.teardown_request`.


Views and Models
----------------

Your extension views might want to interact with specific models in your
database, or some other extension or data connected to your application.
For example, let's consider a ``Flask-SimpleBlog`` extension that works
with Flask-SQLAlchemy to provide a ``Post`` model and views to write
and read posts.

The ``Post`` model needs to subclass the Flask-SQLAlchemy ``db.Model``
object, but that's only available once you've created an instance of
that extension, not when your extension is defining its views. So how
can the view code, defined before the model exists, access the model?

One method could be to use :doc:`views`. During ``__init__``, create
the model, then create the views by passing the model to the view
class's :meth:`~views.View.as_view` method.

.. code-block:: python

    class PostAPI(MethodView):
        def __init__(self, model):
            self.model = model

        def get(self, id):
            post = self.model.query.get(id)
            return jsonify(post.to_json())

    class BlogExtension:
        def __init__(self, db):
            class Post(db.Model):
                id = db.Column(primary_key=True)
                title = db.Column(db.String, nullable=False)

            self.post_model = Post

        def init_app(self, app):
            api_view = PostAPI.as_view(model=self.post_model)

    db = SQLAlchemy()
    blog = BlogExtension(db)
    db.init_app(app)
    blog.init_app(app)

Another technique could be to use an attribute on the extension, such as
``self.post_model`` from above. Add the extension to ``app.extensions``
in ``init_app``, then access
``current_app.extensions["simple_blog"].post_model`` from views.

You may also want to provide base classes so that users can provide
their own ``Post`` model that conforms to the API your extension
expects. So they could implement ``class Post(blog.BasePost)``, then
set it as ``blog.post_model``.

As you can see, this can get a bit complex. Unfortunately, there's no
perfect solution here, only different strategies and tradeoffs depending
on your needs and how much customization you want to offer. Luckily,
this sort of resource dependency is not a common need for most
extensions. Remember, if you need help with design, ask on our
`Discord Chat`_ or `GitHub Discussions`_.


Recommended Extension Guidelines
--------------------------------

Flask previously had the concept of "approved extensions", where the
Flask maintainers evaluated the quality, support, and compatibility of
the extensions before listing them. While the list became too difficult
to maintain over time, the guidelines are still relevant to all
extensions maintained and developed today, as they help the Flask
ecosystem remain consistent and compatible.

1.  An extension requires a maintainer. In the event an extension author
    would like to move beyond the project, the project should find a new
    maintainer and transfer access to the repository, documentation,
    PyPI, and any other services. The `Pallets-Eco`_ organization on
    GitHub allows for community maintenance with oversight from the
    Pallets maintainers.
2.  The naming scheme is *Flask-ExtensionName* or *ExtensionName-Flask*.
    It must provide exactly one package or module named
    ``flask_extension_name``.
3.  The extension must use an open source license. The Python web
    ecosystem tends to prefer BSD or MIT. It must be open source and
    publicly available.
4.  The extension's API must have the following characteristics:

    -   It must support multiple applications running in the same Python
        process. Use ``current_app`` instead of ``self.app``, store
        configuration and state per application instance.
    -   It must be possible to use the factory pattern for creating
        applications. Use the ``ext.init_app()`` pattern.

5.  From a clone of the repository, an extension with its dependencies
    must be installable in editable mode with ``pip install -e .``.
6.  It must ship tests that can be invoked with a common tool like
    ``tox -e py``, ``nox -s test`` or ``pytest``. If not using ``tox``,
    the test dependencies should be specified in a requirements file.
    The tests must be part of the sdist distribution.
7.  A link to the documentation or project website must be in the PyPI
    metadata or the readme. The documentation should use the Flask theme
    from the `Official Pallets Themes`_.
8.  The extension's dependencies should not use upper bounds or assume
    any particular version scheme, but should use lower bounds to
    indicate minimum compatibility support. For example,
    ``sqlalchemy>=1.4``.
9.  Indicate the versions of Python supported using ``python_requires=">=version"``.
    Flask itself supports Python >=3.9 as of October 2024, and this will update
    over time.

.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask
.. _Discord Chat: https://discord.gg/pallets
.. _GitHub Discussions: https://github.com/pallets/flask/discussions
.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/
.. _Pallets-Eco: https://github.com/pallets-eco