Permalink
Browse files

Added document about extension development

  • Loading branch information...
1 parent 707b307 commit ee69fb58906818f50a2afe71e4ecde7af96b791a @mitsuhiko mitsuhiko committed May 31, 2010
Showing with 278 additions and 1 deletion.
  1. +1 −1 docs/_themes
  2. +1 −0 docs/contents.rst.inc
  3. +276 −0 docs/extensiondev.rst
View
@@ -40,6 +40,7 @@ Design notes, legal information and changelog are here for the interested.
:maxdepth: 2
design
+ extensiondev
license
upgrading
changelog
View
@@ -0,0 +1,276 @@
+Flask Extension Development
+===========================
+
+Flask, being a microframework, often requires some repetitive steps to get
+a third party library working. Because very often these steps could be
+abstracted to support multiple projects the `Flask Extension Registry`_
+was created.
+
+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.
+
+.. _Flask Extension Registry: http://flask.pocoo.org/extensions/
+
+Anatomy of an Extension
+-----------------------
+
+Extensions are all located in a package called ``flaskext.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 ``flaskext.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 `setup.py` files.
+
+The magic that makes it possible to have your library in a package called
+``flaskext.something`` is called a "namespace package". Check out the
+guide below how to create something like that.
+
+But how 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 behaviour.
+
+Most importantly the extension must be shipped with a `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 as BSD or MIT or a more liberal license
+to be enlisted on 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.
+
+There is a script on github called `Flask Extension Wizard`_ which helps
+you create the initial folder structure. But for this very basic example
+we want to create all by hand to get a better feeling for it.
+
+First we create the following folder structure::
+
+ flask-sqlite3/
+ flaskext/
+ __init__.py
+ sqlite3.py
+ setup.py
+ LICENSE
+
+Here the contents of the most important files:
+
+flaskext/__init__.py
+````````````````````
+
+The only purpose of this file is to mark the package as namespace package.
+This is required so that multiple modules from different PyPI packages can
+reside in the same Python package::
+
+ __import__('pkg_resources').declare_namespace(__name__)
+
+If you want to know exactly what is happening there, checkout the
+distribute or setuptools docs which explain how this works.
+
+Just make sure to not put anything else in there!
+
+setup.py
+````````
+
+The next file that is absolutely required is the `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__,
+ packages=['flaskext'],
+ namespace_packages=['flaskext'],
+ zip_safe=False,
+ 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. This is also what the wizard creates for you if you
+use it.
+
+flaskext/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 your application is currently connecting to SQLite like the
+documentation suggests (:ref:`sqlite3`) you will need to provide a few
+functions and before / after request handlers. 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 initalizes 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 behaviour. For an example look at how the
+ `OAuth extension`_ works: ther 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 need to use the class based approach because we have to use a
+controller object that can be used to connect to the database.
+
+The Extension Code
+------------------
+
+Here the contents of the `flaskext/sqlite3.py` for copy/paste::
+
+ from __future__ import absolute_import
+ import sqlite3
+ from flask import g
+
+ class SQLite3(object):
+
+ def __init__(self, app):
+ self.app = app
+ self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
+
+ self.app.before_request(self.before_request)
+ self.app.after_request(self.after_request)
+
+ def connect(self):
+ return sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
+
+ def before_request(self):
+ g.sqlite3_db = self.connect()
+
+ def after_request(self, response):
+ g.sqlite3_db.close()
+ return response
+
+So here what the lines of code do:
+
+1. the ``__future__`` import is necessary to activate absolute imports.
+ This is needed because otherwise we could not call our module
+ `sqlite3.py` and import the top-level `sqlite3` module which actually
+ implements the connection to SQLite.
+2. We create a class for our extension that sets a default configuration
+ for the SQLite 3 database if it's not there (:meth:`dict.setdefault`)
+ and connects two functions as before and after request handlers.
+3. Then it implements a `connect` function that returns a new database
+ connection and the two handlers.
+
+So why did we decide on a class based approach here? Because using that
+extension looks something like this::
+
+ from flask import Flask, g
+ from flaskext.sqlite3 import SQLite3
+
+ app = Flask(__name__)
+ app.config.from_pyfile('the-config.cfg')
+ db = SQLite(app)
+
+Either way you can use the database from the views like this::
+
+ @app.route('/')
+ def show_all():
+ cur = g.sqlite_db.cursor()
+ cur.execute(...)
+
+But how would you open a database connection from outside a view function?
+This is where the `db` object now comes into play:
+
+>>> from yourapplication import db
+>>> con = db.connect()
+>>> cur = con.cursor()
+
+If you don't need that, you can go with initialization functions.
+
+Initialization Functions
+------------------------
+
+Here how the module would look like with initialization functions::
+
+ from __future__ import absolute_import
+ import sqlite3
+ from flask import g
+
+ def init_sqlite3(app):
+ app = app
+ app.config.setdefault('SQLITE3_DATABASE', ':memory:')
+
+ @app.before_request
+ def before_request():
+ g.sqlite3_db = sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
+
+ @app.after_request
+ def after_request(response):
+ g.sqlite3_db.close()
+ return response
+
+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 `Flask Extension Registry`_. If you feel
+lost there is still the `mailinglist`_ and the `IRC channel`_ 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.
+
+Remember: good API design is hard :(
+
+
+.. _Flask Extension Wizard:
+ http://github.com/mitsuhiko/flask-extension-wizard
+.. _OAuth extension: http://packages.python.org/Flask-OAuth/
+.. _mailinglist: http://flask.pocoo.org/mailinglist/
+.. _IRC channel: http://flask.pocoo.org/community/irc/

0 comments on commit ee69fb5

Please sign in to comment.