Klein is a micro-framework for developing production ready web services with python. It is 'micro' in that it has an incredibly small API similar to bottle and flask. It is not 'micro' in that it depends on things outside the standard library. This is primarily because it is built on widely used and well tested components like werkzeug and Twisted.
A Klein bottle is an example of a non-orientable surface, and a glass Klein bottle looks like a twisted bottle or twisted flask. This, of course, made it too good of a pun to pass up.
Here are some basic klein handler functions that return some strings.
from klein import run, route
@route('/')
def home(request):
return 'Hello, world!'
run("localhost", 8080)
Helpfully you can also return a twisted.web.resource.IResource
such as
static.File
. If the branch=True
is passed to route
the
returned IResource
will also be allowed to handle all children path
segments. So http://localhost:8080/static/img.gif
should return an
image and http://localhost:8080/static/
should return a directory
listing.
from twisted.web.static import File
from klein import run, route
@route('/static/', branch=True)
def static(request):
return File("./static")
@route('/')
def home(request):
return '<img src="/static/img.gif">'
run("localhost", 8080)
You can also make easy use of twisted.web.templates
by returning anything
that implements twisted.web.template.IRenderable
such as
twisted.web.template.Element
in which case the template will be rendered
and the result will be sent as the response body.
from twisted.web.template import Element, XMLString, renderer
from klein import run, route
class HelloElement(Element):
loader = XMLString((
'<h1 '
'xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"'
'>Hello, <span t:render="name"></span>!</h1>'))
def __init__(self, name):
self._name = name
@renderer
def name(self, request, tag):
return self._name
@route('/hello/<string:name>')
def home(request, name='world'):
return HelloElement(name)
run("localhost", 8080)
And of course, this is twisted. So there is a wealth of APIs that return a
twisted.internet.defer.Deferred
. A Deferred
may also be returned from
handler functions and their result will be used as the response body.
Here is a simple Google proxy.
from twisted.web.client import getPage
from klein import run, route
@route('/')
def google(request):
return getPage('https://www.google.com' + request.uri)
run("localhost", 8080)
Another very important integration point with Twisted is the twistd
application runner. It provides rich logging support, daemonization, reactor
selection, profiler integration, and many more incredibly useful features.
To provide access to these features (and others like HTTPS) klein provides the
resource
function which returns a valid twisted.web.resource.IResource
for your application.
Here is our "Hello, World!" application again in a form that can be launched
by twistd
.
from klein import resource, route
@route('/')
def hello(request):
return "Hello, world!"
To run the above application we can save it as helloworld.py
and use the
twistd web
plugin.
twistd -n web --class=helloworld.resource
The route
decorator supports a methods
keyword which is the list of
HTTP methods as strings. For example methods=['POST']
will cause the
handler to be invoked when an POST
request is received. If a handler
can support multiple methods the current method can be distinguished with
request.method
.
Here is our "Hello, world!"
example extended to support setting the
name we are saying Hello to via a POST
request with a name
argument.
This also demonstrates the use of the redirect method of the request to
redirect back to '/'
after handling the POST
.
The most specific handler should be defined first. So the POST
handler
must be defined before the handler with no methods
.
from twisted.internet.defer import succeed
from klein import run, route
name='world'
@route('/', methods=['POST'])
def setname(request):
global name
name = request.args.get('name', ['world'])[0]
request.redirect('/')
return succeed(None)
@route('/')
def hello(request):
return "Hello, {0}!".format(name)
run("localhost", 8080)
The following curl command can be used to test this behaviour:
curl -v -L -d name='bob' http://localhost:8080/
For obvious reasons it may be desirable for your application to have some non-global state that is used by the your route handlers.
Below we have created a simple ItemStore
class that has an instance of
Klein
as a class variable app
. We can now use @app.route
to
decorate the methods of the class.
import json
from klein import Klein
class ItemStore(object):
app = Klein()
def __init__(self):
self._items = {}
@app.route('/')
def items(self, request):
request.setHeader('Content-Type', 'application/json')
return json.dumps(self._items)
@app.route('/<string:name>', methods=['PUT'])
def save_item(self, request, name):
request.setHeader('Content-Type', 'application/json')
body = json.loads(request.content.read())
self._items[name] = body
return json.dumps({'success': True})
@app.route('/<string:name>', methods=['GET'])
def get_item(self, request, name):
request.setHeader('Content-Type', 'application/json')
return json.dumps(self._items.get(name))
if __name__ == '__main__':
store = ItemStore()
store.app.run('localhost', 8080)