summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hellkamp <marc@gsites.de>2011-05-02 21:29:05 +0200
committerMarcel Hellkamp <marc@gsites.de>2011-05-02 21:29:05 +0200
commite82f43583044f192dd7ba681bb624d93a8d203d8 (patch)
treed2f3fa64d129ddc6917940cb94c8aab46a901287
parent287b12a0140d039720933823853e59053b285217 (diff)
downloadbottle-e82f43583044f192dd7ba681bb624d93a8d203d8.tar.gz
Added sqlite plugin (experimental).
-rw-r--r--plugins/sqlite/bottle_sqlite.py98
-rw-r--r--plugins/sqlite/setup.py45
-rw-r--r--plugins/sqlite/test.py33
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()