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 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
|
Flask Extension Development
===========================
Flask, being a microframework, often requires some repetitive steps to get
a third party library working. Many such extensions are already available
on `PyPI`_.
If you want to create your own Flask extension for something that does not
exist yet, this guide to extension development will help you get your
extension running in no time and to feel like users would expect your
extension to behave.
Anatomy of an Extension
-----------------------
Extensions are all located in a package called ``flask_something``
where "something" is the name of the library you want to bridge. So for
example if you plan to add support for a library named `simplexml` to
Flask, you would name your extension's package ``flask_simplexml``.
The name of the actual extension (the human readable name) however would
be something like "Flask-SimpleXML". Make sure to include the name
"Flask" somewhere in that name and that you check the capitalization.
This is how users can then register dependencies to your extension in
their :file:`setup.py` files.
But what do extensions look like themselves? An extension has to ensure
that it works with multiple Flask application instances at once. This is
a requirement because many people will use patterns like the
:ref:`app-factories` pattern to create their application as needed to aid
unittests and to support multiple configurations. Because of that it is
crucial that your application supports that kind of behavior.
Most importantly the extension must be shipped with a :file:`setup.py` file and
registered on PyPI. Also the development checkout link should work so
that people can easily install the development version into their
virtualenv without having to download the library by hand.
Flask extensions must be licensed under a BSD, MIT or more liberal license
in order to be listed in the Flask Extension Registry. Keep in mind
that the Flask Extension Registry is a moderated place and libraries will
be reviewed upfront if they behave as required.
"Hello Flaskext!"
-----------------
So let's get started with creating such a Flask extension. The extension
we want to create here will provide very basic support for SQLite3.
First we create the following folder structure::
flask-sqlite3/
flask_sqlite3.py
LICENSE
README
Here's the contents of the most important files:
setup.py
````````
The next file that is absolutely required is the :file:`setup.py` file which is
used to install your Flask extension. The following contents are
something you can work with::
"""
Flask-SQLite3
-------------
This is the description for that library
"""
from setuptools import setup
setup(
name='Flask-SQLite3',
version='1.0',
url='http://example.com/flask-sqlite3/',
license='BSD',
author='Your Name',
author_email='your-email@example.com',
description='Very short description',
long_description=__doc__,
py_modules=['flask_sqlite3'],
# if you would be using a package instead use packages instead
# of py_modules:
# packages=['flask_sqlite3'],
zip_safe=False,
include_package_data=True,
platforms='any',
install_requires=[
'Flask'
],
classifiers=[
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries :: Python Modules'
]
)
That's a lot of code but you can really just copy/paste that from existing
extensions and adapt.
flask_sqlite3.py
````````````````
Now this is where your extension code goes. But how exactly should such
an extension look like? What are the best practices? Continue reading
for some insight.
Initializing Extensions
-----------------------
Many extensions will need some kind of initialization step. For example,
consider an application that's currently connecting to SQLite like the
documentation suggests (:ref:`sqlite3`). So how does the extension
know the name of the application object?
Quite simple: you pass it to it.
There are two recommended ways for an extension to initialize:
initialization functions:
If your extension is called `helloworld` you might have a function
called ``init_helloworld(app[, extra_args])`` that initializes the
extension for that application. It could attach before / after
handlers etc.
classes:
Classes work mostly like initialization functions but can later be
used to further change the behavior. For an example look at how the
`OAuth extension`_ works: there is an `OAuth` object that provides
some helper functions like `OAuth.remote_app` to create a reference to
a remote application that uses OAuth.
What to use depends on what you have in mind. For the SQLite 3 extension
we will use the class-based approach because it will provide users with an
object that handles opening and closing database connections.
When designing your classes, it's important to make them easily reusable
at the module level. This means the object itself must not under any
circumstances store any application specific state and must be shareable
between different applications.
The Extension Code
------------------
Here's the contents of the `flask_sqlite3.py` for copy/paste::
import sqlite3
from flask import current_app, _app_ctx_stack
class SQLite3(object):
def __init__(self, app=None):
self.app = app
if app is not None:
self.init_app(app)
def init_app(self, app):
app.config.setdefault('SQLITE3_DATABASE', ':memory:')
app.teardown_appcontext(self.teardown)
def connect(self):
return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])
def teardown(self, exception):
ctx = _app_ctx_stack.top
if hasattr(ctx, 'sqlite3_db'):
ctx.sqlite3_db.close()
@property
def connection(self):
ctx = _app_ctx_stack.top
if ctx is not None:
if not hasattr(ctx, 'sqlite3_db'):
ctx.sqlite3_db = self.connect()
return ctx.sqlite3_db
So here's what these lines of code do:
1. The ``__init__`` method takes an optional app object and, if supplied, will
call ``init_app``.
2. The ``init_app`` method exists so that the ``SQLite3`` object can be
instantiated without requiring an app object. This method supports the
factory pattern for creating applications. The ``init_app`` will set the
configuration for the database, defaulting to an in memory database if
no configuration is supplied. In addition, the ``init_app`` method
attaches the ``teardown`` handler.
3. Next, we define a ``connect`` method that opens a database connection.
4. Finally, we add a ``connection`` property that on first access opens
the database connection and stores it on the context. This is also
the recommended way to handling resources: fetch resources lazily the
first time they are used.
Note here that we're attaching our database connection to the top
application context via ``_app_ctx_stack.top``. Extensions should use
the top context for storing their own information with a sufficiently
complex name.
So why did we decide on a class-based approach here? Because using our
extension looks something like this::
from flask import Flask
from flask_sqlite3 import SQLite3
app = Flask(__name__)
app.config.from_pyfile('the-config.cfg')
db = SQLite3(app)
You can then use the database from views like this::
@app.route('/')
def show_all():
cur = db.connection.cursor()
cur.execute(...)
Likewise if you are outside of a request you can use the database by
pushing an app context::
with app.app_context():
cur = db.connection.cursor()
cur.execute(...)
At the end of the ``with`` block the teardown handles will be executed
automatically.
Additionally, the ``init_app`` method is used to support the factory pattern
for creating apps::
db = SQLite3()
# Then later on.
app = create_app('the-config.cfg')
db.init_app(app)
Keep in mind that supporting this factory pattern for creating apps is required
for approved flask extensions (described below).
.. admonition:: Note on ``init_app``
As you noticed, ``init_app`` does not assign ``app`` to ``self``. This
is intentional! Class based Flask extensions must only store the
application on the object when the application was passed to the
constructor. This tells the extension: I am not interested in using
multiple applications.
When the extension needs to find the current application and it does
not have a reference to it, it must either use the
:data:`~flask.current_app` context local or change the API in a way
that you can pass the application explicitly.
Using _app_ctx_stack
--------------------
In the example above, before every request, a ``sqlite3_db`` variable is
assigned to ``_app_ctx_stack.top``. In a view function, this variable is
accessible using the ``connection`` property of ``SQLite3``. During the
teardown of a request, the ``sqlite3_db`` connection is closed. By using
this pattern, the *same* connection to the sqlite3 database is accessible
to anything that needs it for the duration of the request.
Learn from Others
-----------------
This documentation only touches the bare minimum for extension development.
If you want to learn more, it's a very good idea to check out existing extensions
on the `PyPI`_. If you feel lost there is still the `mailinglist`_ and the
`Discord server`_ to get some ideas for nice looking APIs. Especially if you do
something nobody before you did, it might be a very good idea to get some more
input. This not only generates useful feedback on what people might want from
an extension, but also avoids having multiple developers working in isolation
on pretty much the same problem.
Remember: good API design is hard, so introduce your project on the
mailing list, and let other developers give you a helping hand with
designing the API.
The best Flask extensions are extensions that share common idioms for the
API. And this can only work if collaboration happens early.
Approved Extensions
-------------------
Flask previously had the concept of approved extensions. These came with
some vetting of support and compatibility. While this 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.
0. An approved Flask 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. If no maintainer
is available, give access to the Pallets core team.
1. The naming scheme is *Flask-ExtensionName* or *ExtensionName-Flask*.
It must provide exactly one package or module named
``flask_extension_name``.
2. The extension must be BSD or MIT licensed. It must be open source
and publicly available.
3. 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.
4. From a clone of the repository, an extension with its dependencies
must be installable with ``pip install -e .``.
5. It must ship a testing suite that can be invoked with ``tox -e py``
or ``pytest``. If not using ``tox``, the test dependencies should be
specified in a ``requirements.txt`` file. The tests must be part of
the sdist distribution.
6. The documentation must use the ``flask`` theme from the
`Official Pallets Themes`_. A link to the documentation or project
website must be in the PyPI metadata or the readme.
7. For maximum compatibility, the extension should support the same
versions of Python that Flask supports. 3.6+ is recommended as of
2020. Use ``python_requires=">= 3.6"`` in ``setup.py`` to indicate
supported versions.
.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask
.. _OAuth extension: https://pythonhosted.org/Flask-OAuth/
.. _mailinglist: https://mail.python.org/mailman/listinfo/flask
.. _Discord server: https://discord.gg/t6rrQZH
.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/
|