Moved plugins from main bottle repository to bottle-extras.

This commit is contained in:
Marcel Hellkamp 2011-09-01 19:21:53 +02:00
commit 7c6f7b3abb
4 changed files with 264 additions and 0 deletions

87
README Normal file
View File

@ -0,0 +1,87 @@
=====================
Bottle-SQLite
=====================
SQLite is a self-contained SQL database engine that runs locally and does not
require any additional server software or setup. The sqlite3 module is part of the
Python standard library and already installed on most systems. It it very useful
for prototyping database-driven applications that are later ported to larger
databases such as PostgreSQL or MySQL.
This plugin simplifies the use of sqlite databases in your Bottle applications.
Once installed, all you have to do is to add a ``db`` keyword argument
(configurable) to route callbacks that need a database connection.
Installation
===============
Install with one of the following commands::
$ pip install bottle-sqlite
$ easy_install bottle-sqlite
or download the latest version from github::
$ git clone git://github.com/defnull/bottle.git
$ cd bottle/plugins/sqlite
$ python setup.py install
Usage
===============
Once installed to an application, the plugin passes an open
:class:`sqlite3.Connection` instance to all routes that require a ``db`` keyword
argument::
import bottle
app = bottle.Bottle()
plugin = bottle.ext.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")
Routes that do not expect a ``db`` keyword argument are not affected.
The connection handle is configured so that :class:`sqlite3.Row` objects can be
accessed both by index (like tuples) and case-insensitively by name. At the end of
the request cycle, outstanding transactions are committed and the connection is
closed automatically. If an error occurs, any changes to the database since the
last commit are rolled back to keep the database in a consistent state.
Configuration
=============
The following configuration options exist for the plugin class:
* **dbfile**: Database filename (default: in-memory database).
* **keyword**: The keyword argument name that triggers the plugin (default: 'db').
* **autocommit**: Whether or not to commit outstanding transactions at the end of the request cycle (default: True).
* **dictrows**: Whether or not to support dict-like access to row objects (default: True).
You can override each of these values on a per-route basis::
@app.route('/cache/:item', sqlite={'dbfile': ':memory:'})
def cache(item, db):
...
or install two plugins with different ``keyword`` settings to the same application::
app = bottle.Bottle()
test_db = bottle.ext.sqlite.Plugin(dbfile='/tmp/test.db')
cache_db = bottle.ext.sqlite.Plugin(dbfile=':memory:', keyword='cache')
app.install(test_db)
app.install(cache_db)
@app.route('/show/:item')
def show(item, db):
...
@app.route('/cache/:item')
def cache(item, cache):
...

99
bottle_sqlite.py Normal file
View File

@ -0,0 +1,99 @@
'''
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.1'
__license__ = 'MIT'
### CUT HERE (see setup.py)
import sqlite3
import inspect
from bottle import HTTPError
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

45
setup.py Normal file
View File

@ -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/plugins/sqlite.html',
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}
)

33
test.py Normal file
View File

@ -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()