From 8d1546f8e64e093847ad3d5579ad5f9b7c3d0e45 Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Mon, 12 Mar 2012 16:28:39 -0700 Subject: [PATCH] Reword the docs for writing a flask extension There was a minor bug in the example extension that's been fixed. I also updated the description of the fixed code accordingly, and expanded on the usage of _request_ctx_stack.top for adding data that should be accesible to view functions. I verified that the existing code as is works as expected. --- docs/extensiondev.rst | 119 +++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 70 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 074d06ab..ab038e0c 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -125,9 +125,8 @@ 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 +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. @@ -135,12 +134,14 @@ 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 behaviour. For an example look at how the `OAuth extension`_ works: there is an `OAuth` object that provides @@ -148,22 +149,29 @@ classes: 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 a -manager object that handles opening and closing database connections. +we will use the class-based approach because it will provide users with an +object that handles opening and closing database connections. The Extension Code ------------------ Here's the contents of the `flask_sqlite3.py` for copy/paste:: - from __future__ import absolute_import import sqlite3 from flask import _request_ctx_stack + class SQLite3(object): - def __init__(self, app): + def __init__(self, app=None): + if app is not None: + self.app = app + self.init_app(self.app) + else: + self.app = None + + def init_app(self, app): self.app = app self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') self.app.teardown_request(self.teardown_request) @@ -180,27 +188,29 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste:: ctx = _request_ctx_stack.top ctx.sqlite3_db.close() - def get_db(self): + @property + def connection(self): ctx = _request_ctx_stack.top if ctx is not None: return ctx.sqlite3_db + So here's what these lines of code do: -1. The ``__future__`` import is necessary to activate absolute imports. - 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 requires a supplied `app` object, - sets a configuration for the database if it's not there - (:meth:`dict.setdefault`), and attaches `before_request` and - `teardown_request` handlers. -3. Next, we define a `connect` function that opens a database connection. +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 + ``before_request`` and ``teardown_request`` handlers. +3. Next, we define a ``connect`` method that opens a database connection. 4. Then we set up the request handlers we bound to the app above. Note here that we're attaching our database connection to the top request context via - `_request_ctx_stack.top`. Extensions should use the top context and not the - `g` object to store things like database connections. -5. Finally, we add a `get_db` function that simplifies access to the context's + ``_request_ctx_stack.top``. Extensions should use the top context and not the + ``g`` object to store things like database connections. +5. Finally, we add a ``connection`` property that simplifies access to the context's database. So why did we decide on a class-based approach here? Because using our @@ -211,68 +221,36 @@ extension looks something like this:: app = Flask(__name__) app.config.from_pyfile('the-config.cfg') - manager = SQLite3(app) - db = manager.get_db() + db = SQLite3(app) You can then use the database from views like this:: @app.route('/') def show_all(): - cur = db.cursor() + cur = db.connection.cursor() cur.execute(...) -Opening a database connection from outside a view function is simple. - ->>> from yourapplication import db ->>> cur = db.cursor() ->>> cur.execute(...) - -Adding an `init_app` Function ------------------------------ - -In practice, you'll almost always want to permit users to initialize your -extension and provide an app object after the fact. This can help avoid -circular import problems when a user is breaking their app into multiple files. -Our extension could add an `init_app` function as follows:: - - class SQLite3(object): - - def __init__(self, app=None): - if app is not None: - self.app = app - self.init_app(self.app) - else: - self.app = None - - def init_app(self, app): - self.app = app - self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') - self.app.teardown_request(self.teardown_request) - self.app.before_request(self.before_request) - - def connect(self): - return sqlite3.connect(app.config['SQLITE3_DATABASE']) - - def before_request(self): - ctx = _request_ctx_stack.top - ctx.sqlite3_db = self.connect() +Additionally, the ``init_app`` method is used to support the factory pattern +for creating apps:: - def teardown_request(self, exception): - ctx = _request_ctx_stack.top - ctx.sqlite3_db.close() + db = Sqlite3() + # Then later on. + app = create_app('the-config.cfg') + db.init_app(app) - def get_db(self): - ctx = _request_ctx_stack.top - if ctx is not None: - return ctx.sqlite3_db +Keep in mind that supporting this factory pattern for creating apps is required +for approved flask extensions (described below). -The user could then initialize the extension in one file:: - manager = SQLite3() +Using _request_ctx_stack +------------------------ -and bind their app to the extension in another file:: - - manager.init_app(app) +In the example above, before every request, a ``sqlite3_db`` variable is assigned +to ``_request_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. End-Of-Request Behavior ----------------------- @@ -292,6 +270,7 @@ pattern is a good way to support both:: else: app.after_request(close_connection) + Strictly speaking the above code is wrong, because teardown functions are passed the exception and typically don't return anything. However because the return value is discarded this will just work assuming that the code