From ee69fb58906818f50a2afe71e4ecde7af96b791a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 31 May 2010 21:14:27 +0200 Subject: [PATCH] Added document about extension development --- docs/_themes | 2 +- docs/contents.rst.inc | 1 + docs/extensiondev.rst | 276 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 docs/extensiondev.rst diff --git a/docs/_themes b/docs/_themes index 0d8f3d85..77a1db55 160000 --- a/docs/_themes +++ b/docs/_themes @@ -1 +1 @@ -Subproject commit 0d8f3d85558168647632c768bdea7d58cf6f8e42 +Subproject commit 77a1db551aa956069ff4408b6c05814d86b0dc0d diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc index 918ad804..9a3ff5a8 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -40,6 +40,7 @@ Design notes, legal information and changelog are here for the interested. :maxdepth: 2 design + extensiondev license upgrading changelog diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst new file mode 100644 index 00000000..ca8a4ea4 --- /dev/null +++ b/docs/extensiondev.rst @@ -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/