citizenwatt-refactor/base/bottle_sqlalchemy.py

164 lines
5.6 KiB
Python

'''
This bottle-sqlalchemy plugin integrates SQLAlchemy with your Bottle
application. It connects to a database at the beginning of a request,
passes the database handle to the route callback and closes the connection
afterwards.
The plugin inject an argument to all route callbacks that require a `db`
keyword.
Usage Example::
import bottle
from bottle import HTTPError
from bottle.ext import sqlalchemy
from sqlalchemy import create_engine, Column, Integer, Sequence, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
engine = create_engine('sqlite:///:memory:', echo=True)
app = bottle.Bottle()
plugin = sqlalchemy.Plugin(engine, Base.metadata, create=True)
app.install(plugin)
class Entity(Base):
__tablename__ = 'entity'
id = Column(Integer, Sequence('id_seq'), primary_key=True)
name = Column(String(50))
def __init__(self, name):
self.name = name
def __repr__(self):
return "<Entity('%d', '%s')>" % (self.id, self.name)
@app.get('/:name')
def show(name, db):
entity = db.query(Entity).filter_by(name=name).first()
if entity:
return {'id': entity.id, 'name': entity.name}
return HTTPError(404, 'Entity not found.')
@app.put('/:name')
def put_name(name, db):
entity = Entity(name)
db.add(entity)
It is up to you create engine and metadata, because SQLAlchemy has
a lot of options to do it. The plugin just handles the SQLAlchemy
session.
Copyright (c) 2011-2012, Iuri de Silvio
License: MIT (see LICENSE for details)
'''
import inspect
import bottle
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.scoping import ScopedSession
# PluginError is defined to bottle >= 0.10
if not hasattr(bottle, 'PluginError'):
class PluginError(bottle.BottleException):
pass
bottle.PluginError = PluginError
class SQLAlchemyPlugin(object):
name = 'sqlalchemy'
api = 2
def __init__(self, engine, metadata=None,
keyword='db', commit=True, create=False, use_kwargs=False, create_session=None):
'''
:param engine: SQLAlchemy engine created with `create_engine` function
:param metadata: SQLAlchemy metadata. It is required only if `create=True`
:param keyword: Keyword used to inject session database in a route
:param create: If it is true, execute `metadata.create_all(engine)`
when plugin is applied
:param commit: If it is true, commit changes after route is executed.
:param use_kwargs: plugin inject session database even if it is not
explicitly defined, using **kwargs argument if defined.
:param create_session: SQLAlchemy session maker created with the
'sessionmaker' function. Will create its own if undefined.
'''
self.engine = engine
if create_session is None:
create_session = sessionmaker()
self.create_session = create_session
self.metadata = metadata
self.keyword = keyword
self.create = create
self.commit = commit
self.use_kwargs = use_kwargs
def setup(self, app):
''' Make sure that other installed plugins don't affect the same
keyword argument and check if metadata is available.'''
for other in app.plugins:
if not isinstance(other, SQLAlchemyPlugin):
continue
if other.keyword == self.keyword:
raise bottle.PluginError("Found another SQLAlchemy plugin with "\
"conflicting settings (non-unique keyword).")
elif other.name == self.name:
self.name += '_%s' % self.keyword
if self.create and not self.metadata:
raise bottle.PluginError('Define metadata value to create database.')
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
if "sqlalchemy" in config: # support for configuration before `ConfigDict` namespaces
g = lambda key, default: config.get('sqlalchemy', {}).get(key, default)
else:
g = lambda key, default: config.get('sqlalchemy.' + key, default)
keyword = g('keyword', self.keyword)
create = g('create', self.create)
commit = g('commit', self.commit)
use_kwargs = g('use_kwargs', self.use_kwargs)
argspec = inspect.getargspec(_callback)
if not ((use_kwargs and argspec.keywords) or keyword in argspec.args):
return callback
if create:
self.metadata.create_all(self.engine)
def wrapper(*args, **kwargs):
kwargs[keyword] = session = self.create_session(bind=self.engine)
try:
rv = callback(*args, **kwargs)
if commit:
session.commit()
except (SQLAlchemyError, bottle.HTTPError):
session.rollback()
raise
except bottle.HTTPResponse:
if commit:
session.commit()
raise
finally:
if isinstance(self.create_session, ScopedSession):
self.create_session.remove()
else:
session.close()
return rv
return wrapper
Plugin = SQLAlchemyPlugin