Waffle - A Dependency-Injection-based Python Application Framework
Waffle provides common functionality for bootstrapping an application using Injector.
The general approach used by the Injector modules in Waffle is to use flags to configure the behaviour of objects exported by the modules. The flag defaults can (and sometimes must) be overridden by the @main
decorator or run()
function.
For example, waffle.redis.RedisModule
relies on the flag --redis_server
to connect to a Redis server, which defaults to localhost:6379:0
. To configure and use the Redis module you might do something like this:
from waffle import main
@main(redis_server='redis.domain.com:6379:1')
def main(injector):
redis = injector.get(Redis)
Waffle provides:
- Command line flag parsing via argh.
- Injection of flags.
- Construction of the injector.
- A bunch of modules that provide different integration: Redis, SQLAlchemy, Flask, logging, etc.
Examples
A bare bones application
from waffle import main
@main
def main():
print 'Hello world'
Injecting modules
A slightly more complex example injecting the logging module:
from waffle import LoggingModule, main
@main(debug=True)
@modules(LoggingModule)
def main():
...
@main
should be the last statements in the module.
A full example web server with database
This example illustrates several aspects of Waffle:
-
How to set defaults for flags by passing the flags as keyword arguments to either
main()
orrun()
. -
The use of the convenience Injector modules
AppModules
andWebModules
, which install commonly used modules for applications and web applications, respectively. See below for details. -
Use of the
@transaction
decorator to wrap requests in an SQLAlchemy transaction.
from injector import inject
from sqlalchemy import Column, String
from waffle import AppModules, WebModules, \
Model, WebApplication, routes, main, modules, route, \
transaction, csrf_exempt
class KeyValue(Model):
key = Column(String, primary_key=True)
value = Column(String)
@route('/')
@transaction
def index():
return [(kv.key, kv.value) for kv in KeyValue.query.all()]
@route('/<key>')
@transaction
@csrf_exempt
def get_or_create(request, key):
kv = KeyValue.query.filter_by(key=key).all()
if kv:
kv = kv[0]
if request.method == 'POST':
if kv:
kv.value = request.data
else:
kv = KeyValue(key=key, value=request.data)
kv.save()
return {'status': 'OK'}
return {'key': kv.key, 'value': kv.value}
@main(database_uri='sqlite:///:memory:', static_root='./static/',
template_root='./templates/')
@modules(AppModules, WebModules)
@inject(app=WebApplication)
def main(app):
app.serve(port=8081, use_reloader=False)
Available modules
waffle.common.AppModules (composite)
Installs waffle.db.DatabaseModule
and waffle.db.LoggingModule
.
waffle.web.common.WebModules (composite)
Installs waffle.web.clastic.WebModule
, waffle.web.db.DatabaseSessionModule
, waffle.web.template.TemplateModule
and waffle.web.csrf.CsrfModule
.
waffle.db.DatabaseModule
Configure SQLAlchemy to work with injection.
Binds a configured SQLAlchemy Session
to the injector.
from sqlalchemy.orm import String
from sqlalchemy.orm.session import Session
from waffle import DatabaseModule, Base, main, modules
class KeyValue(Base):
key = String()
value = String()
@main(database_uri='sqlite:///tmp/test.db')
@modules(DatabaseModule)
def main(injector):
session = injector.get(Session)
...
waffle.log.LoggingModule
Configures some default basic logging.
Binds the key LogLevel
to the specified log level.
waffle.devel.DevelModule
A module that binds the gevent BackdoorServer
, allowing the developer to attach to a Python shell in the application.
Use like so:
injector.get(BackdoorServer).start()
waffle.web.clastic.WebModule
Integrates Clastic through an injector module. This is the core module for providing web application support.
waffle.web.db.DatabaseSessionModule
A module that manages DB session lifecycle in HTTP requests. This basically resets the session at the end of each request.
waffle.web.template.TemplateModule
A module that provides template loading and the ability for separate modules to contribute to the global template rendering context. Useful for eg. adding global debug variables, etc.
To contribute to global template context:
binder.multibind(TemplateContext, to={
'debug': debug,
})
waffle.web.csrf.CsrfModule
Enable CSRF support in templates.
waffle.redis.RedisModule
Provides a Redis client configured by flags:
from redis import Redis
@inject(redis=Redis)
def get(redis):
return redis.get('some_key')