diff options
author | Marcel Hellkamp <marc@gsites.de> | 2011-05-02 21:29:05 +0200 |
---|---|---|
committer | Marcel Hellkamp <marc@gsites.de> | 2011-05-02 21:29:05 +0200 |
commit | e82f43583044f192dd7ba681bb624d93a8d203d8 (patch) | |
tree | d2f3fa64d129ddc6917940cb94c8aab46a901287 | |
parent | 287b12a0140d039720933823853e59053b285217 (diff) | |
download | bottle-e82f43583044f192dd7ba681bb624d93a8d203d8.tar.gz |
Added sqlite plugin (experimental).
-rw-r--r-- | plugins/sqlite/bottle_sqlite.py | 98 | ||||
-rw-r--r-- | plugins/sqlite/setup.py | 45 | ||||
-rw-r--r-- | plugins/sqlite/test.py | 33 |
3 files changed, 176 insertions, 0 deletions
diff --git a/plugins/sqlite/bottle_sqlite.py b/plugins/sqlite/bottle_sqlite.py new file mode 100644 index 0000000..02f28c9 --- /dev/null +++ b/plugins/sqlite/bottle_sqlite.py @@ -0,0 +1,98 @@ +''' +Bottle-sqlite is a plugin that integrates SQLite3 with your Bottle +application. It automatically connects to a database at the beginning of a +request, passes the database handle to the route callback and closes the +connection afterwards. + +To automatically detect routes that need a database connection, the plugin +searches for route callbacks that require a `db` keyword argument +(configurable) and skips routes that do not. This removes any overhead for +routes that don't need a database connection. + +Usage Example:: + + import bottle + from bottle.ext import sqlite + + app = bottle.Bottle() + plugin = sqlite.Plugin(dbfile='/tmp/test.db') + app.install(plugin) + + @app.route('/show/:item') + def show(item, db): + row = db.execute('SELECT * from items where name=?', item).fetchone() + if row: + return template('showitem', page=row) + return HTTPError(404, "Page not found") +''' + +__author__ = "Marcel Hellkamp" +__version__ = '0.1' +__license__ = 'MIT' + +### CUT HERE (see setup.py) + +import sqlite3 +import inspect + + +class SQLitePlugin(object): + ''' This plugin passes an sqlite3 database handle to route callbacks + that accept a `db` keyword argument. If a callback does not expect + such a parameter, no connection is made. You can override the database + settings on a per-route basis. ''' + + name = 'sqlite' + + def __init__(self, dbfile=':memory:', autocommit=True, dictrows=True, + keyword='db'): + self.dbfile = dbfile + self.autocommit = autocommit + self.dictrows = dictrows + self.keyword = keyword + + def setup(self, app): + ''' Make sure that other installed plugins don't affect the same + keyword argument.''' + for other in app.plugins: + if not isinstance(other, SQLitePlugin): continue + if other.keyword == self.keyword: + raise PluginError("Found another sqlite plugin with "\ + "conflicting settings (non-unique keyword).") + + def apply(self, callback, context): + # Override global configuration with route-specific values. + conf = context['config'].get('sqlite') or {} + dbfile = conf.get('dbfile', self.dbfile) + autocommit = conf.get('autocommit', self.autocommit) + dictrows = conf.get('dictrows', self.dictrows) + keyword = conf.get('keyword', self.keyword) + + # Test if the original callback accepts a 'db' keyword. + # Ignore it if it does not need a database handle. + args = inspect.getargspec(context['callback'])[0] + if keyword not in args: + return callback + + def wrapper(*args, **kwargs): + # Connect to the database + db = sqlite3.connect(dbfile) + # This enables column access by name: row['column_name'] + if dictrows: db.row_factory = sqlite3.Row + # Add the connection handle as a keyword argument. + kwargs[keyword] = db + + try: + rv = callback(*args, **kwargs) + if autocommit: db.commit() + except sqlite3.IntegrityError, e: + db.rollback() + raise HTTPError(500, "Database Error", e) + finally: + db.close() + return rv + + # Replace the route callback with the wrapped one. + return wrapper + +Plugin = SQLitePlugin
\ No newline at end of file diff --git a/plugins/sqlite/setup.py b/plugins/sqlite/setup.py new file mode 100644 index 0000000..a889c88 --- /dev/null +++ b/plugins/sqlite/setup.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +import sys +import os +from distutils.core import setup + +try: + from distutils.command.build_py import build_py_2to3 as build_py +except ImportError: + from distutils.command.build_py import build_py + +# This ugly hack executes the first few lines of the module file to look up some +# common variables. We cannot just import the module because it depends on other +# modules that might not be installed yet. +filename = os.path.join(os.path.dirname(__file__), 'bottle_sqlite.py') +source = open(filename).read().split('### CUT HERE')[0] +exec(source) + +setup( + name = 'bottle-sqlite', + version = __version__, + url = 'http://bottlepy.org/docs/dev/plugin/sqlite/', + description = 'SQLite3 integration for Bottle.', + long_description = __doc__, + author = 'Marcel Hellkamp', + author_email = 'marc@gsites.de', + license = __license__, + platforms = 'any', + py_modules = [ + 'bottle_sqlite' + ], + requires = [ + 'bottle (>=0.9)' + ], + classifiers = [ + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + 'Topic :: Software Development :: Libraries :: Python Modules' + ], + cmdclass = {'build_py': build_py} +) diff --git a/plugins/sqlite/test.py b/plugins/sqlite/test.py new file mode 100644 index 0000000..d4024ec --- /dev/null +++ b/plugins/sqlite/test.py @@ -0,0 +1,33 @@ +import unittest +import os +import bottle +from bottle.ext import sqlite +import sqlite3 + +class SQLiteTest(unittest.TestCase): + def setUp(self): + self.app = bottle.Bottle(catchall=False) + + def test_with_keyword(self): + self.plugin = self.app.install(sqlite.Plugin()) + + @self.app.get('/') + def test(db): + self.assertEqual(type(db), type(sqlite3.connect(':memory:'))) + self.app({'PATH_INFO':'/', 'REQUEST_METHOD':'GET'}, lambda x, y: None) + + def test_without_keyword(self): + self.plugin = self.app.install(sqlite.Plugin()) + + @self.app.get('/') + def test(): + pass + self.app({'PATH_INFO':'/', 'REQUEST_METHOD':'GET'}, lambda x, y: None) + + @self.app.get('/2') + def test(**kw): + self.assertFalse('db' in kw) + self.app({'PATH_INFO':'/2', 'REQUEST_METHOD':'GET'}, lambda x, y: None) + +if __name__ == '__main__': + unittest.main() |