Moved plugins from main bottle repository to bottle-extras.
This commit is contained in:
commit
7c6f7b3abb
87
README
Normal file
87
README
Normal 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
99
bottle_sqlite.py
Normal 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
45
setup.py
Normal 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
33
test.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user