2011-09-01 17:21:53 +00:00
|
|
|
'''
|
|
|
|
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"
|
2012-02-07 18:07:27 +00:00
|
|
|
__version__ = '0.1.2'
|
2011-09-01 17:21:53 +00:00
|
|
|
__license__ = 'MIT'
|
|
|
|
|
|
|
|
### CUT HERE (see setup.py)
|
|
|
|
|
|
|
|
import sqlite3
|
|
|
|
import inspect
|
2014-03-27 13:40:11 +00:00
|
|
|
import bottle
|
|
|
|
|
|
|
|
# PluginError is defined to bottle >= 0.10
|
|
|
|
if not hasattr(bottle, 'PluginError'):
|
|
|
|
class PluginError(bottle.BottleException):
|
|
|
|
pass
|
|
|
|
bottle.PluginError = PluginError
|
2011-09-01 17:21:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
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'
|
2014-03-29 13:29:40 +00:00
|
|
|
api = 2
|
2011-09-01 17:21:53 +00:00
|
|
|
|
|
|
|
def __init__(self, dbfile=':memory:', autocommit=True, dictrows=True,
|
2014-12-15 20:00:00 +00:00
|
|
|
keyword='db', text_factory=unicode):
|
2014-03-29 14:04:08 +00:00
|
|
|
self.dbfile = dbfile
|
|
|
|
self.autocommit = autocommit
|
|
|
|
self.dictrows = dictrows
|
|
|
|
self.keyword = keyword
|
2014-12-15 20:00:00 +00:00
|
|
|
self.text_factory = text_factory
|
2011-09-01 17:21:53 +00:00
|
|
|
|
|
|
|
def setup(self, app):
|
|
|
|
''' Make sure that other installed plugins don't affect the same
|
|
|
|
keyword argument.'''
|
|
|
|
for other in app.plugins:
|
2014-03-29 14:04:08 +00:00
|
|
|
if not isinstance(other, SQLitePlugin):
|
|
|
|
continue
|
2011-09-01 17:21:53 +00:00
|
|
|
if other.keyword == self.keyword:
|
2014-03-29 14:04:08 +00:00
|
|
|
raise PluginError("Found another sqlite plugin with "
|
|
|
|
"conflicting settings (non-unique keyword).")
|
2014-03-29 13:57:02 +00:00
|
|
|
elif other.name == self.name:
|
|
|
|
self.name += '_%s' % self.keyword
|
2011-09-01 17:21:53 +00:00
|
|
|
|
2014-03-27 13:40:11 +00:00
|
|
|
def apply(self, callback, route):
|
|
|
|
# hack to support bottle v0.9.x
|
|
|
|
if bottle.__version__.startswith('0.9'):
|
|
|
|
config = route['config']
|
|
|
|
_callback = route['callback']
|
|
|
|
else:
|
|
|
|
config = route.config
|
|
|
|
_callback = route.callback
|
2014-03-29 14:04:08 +00:00
|
|
|
|
2011-09-01 17:21:53 +00:00
|
|
|
# Override global configuration with route-specific values.
|
2014-03-29 14:04:08 +00:00
|
|
|
if "sqlite" in config:
|
|
|
|
# support for configuration before `ConfigDict` namespaces
|
2014-03-27 14:07:01 +00:00
|
|
|
g = lambda key, default: config.get('sqlite', {}).get(key, default)
|
2014-03-27 13:40:11 +00:00
|
|
|
else:
|
2014-03-27 14:07:01 +00:00
|
|
|
g = lambda key, default: config.get('sqlite.' + key, default)
|
2014-03-29 14:04:08 +00:00
|
|
|
|
2014-03-27 13:40:11 +00:00
|
|
|
dbfile = g('dbfile', self.dbfile)
|
|
|
|
autocommit = g('autocommit', self.autocommit)
|
|
|
|
dictrows = g('dictrows', self.dictrows)
|
|
|
|
keyword = g('keyword', self.keyword)
|
2014-12-15 20:00:00 +00:00
|
|
|
text_factory = g('keyword', self.text_factory)
|
2011-09-01 17:21:53 +00:00
|
|
|
|
|
|
|
# Test if the original callback accepts a 'db' keyword.
|
|
|
|
# Ignore it if it does not need a database handle.
|
2014-03-27 13:40:11 +00:00
|
|
|
argspec = inspect.getargspec(_callback)
|
2014-03-27 14:10:24 +00:00
|
|
|
if keyword not in argspec.args:
|
2011-09-01 17:21:53 +00:00
|
|
|
return callback
|
2014-03-29 14:04:08 +00:00
|
|
|
|
2011-09-01 17:21:53 +00:00
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
# Connect to the database
|
|
|
|
db = sqlite3.connect(dbfile)
|
2014-12-15 20:00:00 +00:00
|
|
|
# set text factory
|
|
|
|
db.text_factory = text_factory
|
2011-09-01 17:21:53 +00:00
|
|
|
# This enables column access by name: row['column_name']
|
2014-03-29 14:04:08 +00:00
|
|
|
if dictrows:
|
|
|
|
db.row_factory = sqlite3.Row
|
2011-09-01 17:21:53 +00:00
|
|
|
# Add the connection handle as a keyword argument.
|
|
|
|
kwargs[keyword] = db
|
|
|
|
|
|
|
|
try:
|
|
|
|
rv = callback(*args, **kwargs)
|
2014-03-29 14:04:08 +00:00
|
|
|
if autocommit:
|
|
|
|
db.commit()
|
2014-03-27 13:40:11 +00:00
|
|
|
except sqlite3.IntegrityError as e:
|
2011-09-01 17:21:53 +00:00
|
|
|
db.rollback()
|
2014-03-29 14:04:08 +00:00
|
|
|
raise bottle.HTTPError(500, "Database Error", e)
|
2014-03-27 13:40:11 +00:00
|
|
|
except bottle.HTTPError as e:
|
2012-02-07 18:07:27 +00:00
|
|
|
raise
|
2014-03-27 13:40:11 +00:00
|
|
|
except bottle.HTTPResponse as e:
|
2014-03-29 14:04:08 +00:00
|
|
|
if autocommit:
|
|
|
|
db.commit()
|
2012-02-07 18:07:27 +00:00
|
|
|
raise
|
2011-09-01 17:21:53 +00:00
|
|
|
finally:
|
|
|
|
db.close()
|
|
|
|
return rv
|
|
|
|
|
|
|
|
# Replace the route callback with the wrapped one.
|
|
|
|
return wrapper
|
|
|
|
|
2014-03-27 13:40:11 +00:00
|
|
|
Plugin = SQLitePlugin
|