web micro-framework battle!

95
web Monday, 22 August 2011

Upload: richard-jones

Post on 08-Sep-2014

98.756 views

Category:

Technology


1 download

DESCRIPTION

The source code for the reviews in this presentation are at https://bitbucket.org/r1chardj0n3s/web-micro-battle

TRANSCRIPT

Page 1: Web micro-framework BATTLE!

web

Monday, 22 August 2011

Page 2: Web micro-framework BATTLE!

webmicro-framework

Monday, 22 August 2011

Page 3: Web micro-framework BATTLE!

webmicro-framework

Ba!le!Monday, 22 August 2011

Page 4: Web micro-framework BATTLE!

aka

Monday, 22 August 2011

Page 5: Web micro-framework BATTLE!

akaRichard surveys the landscape

so you (probably) don’t have to

Monday, 22 August 2011

Page 6: Web micro-framework BATTLE!

Introduction

Monday, 22 August 2011

I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, but I’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.

Page 7: Web micro-framework BATTLE!

Introduction

• I write code for a telco

Monday, 22 August 2011

I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, but I’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.

Page 8: Web micro-framework BATTLE!

Introduction

• I write code for a telco

• Lots of discrete system applications

Monday, 22 August 2011

I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, but I’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.

Page 9: Web micro-framework BATTLE!

Introduction

• I write code for a telco

• Lots of discrete system applications

• Connected through HTTP

Monday, 22 August 2011

I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, but I’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.

Page 10: Web micro-framework BATTLE!

Introduction

• I write code for a telco

• Lots of discrete system applications

• Connected through HTTP

• I write little HTTP servers all the time

Monday, 22 August 2011

I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, but I’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.

Page 11: Web micro-framework BATTLE!

Introduction

• I write code for a telco

• Lots of discrete system applications

• Connected through HTTP

• I write little HTTP servers all the time

• I’ve written half a dozen micro-frameworks

Monday, 22 August 2011

I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, but I’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.

Page 12: Web micro-framework BATTLE!

What’s IN?

Monday, 22 August 2011

“standard lib”, access to form vars, redirection (with raise), OO/REST-ish app with GET/POST, WSGI so it’s easy to serve and get middleware. Downloadable docs for offline development are a real bonus.

Page 13: Web micro-framework BATTLE!

What’s IN?

• easy to understand and use(docs, minimal magic, no surprises, terse)

Monday, 22 August 2011

“standard lib”, access to form vars, redirection (with raise), OO/REST-ish app with GET/POST, WSGI so it’s easy to serve and get middleware. Downloadable docs for offline development are a real bonus.

Page 14: Web micro-framework BATTLE!

What’s IN?

• easy to understand and use(docs, minimal magic, no surprises, terse)

• HTTP request/response

Monday, 22 August 2011

“standard lib”, access to form vars, redirection (with raise), OO/REST-ish app with GET/POST, WSGI so it’s easy to serve and get middleware. Downloadable docs for offline development are a real bonus.

Page 15: Web micro-framework BATTLE!

What’s IN?

• easy to understand and use(docs, minimal magic, no surprises, terse)

• HTTP request/response

• URL routing (“RESTful”)

Monday, 22 August 2011

“standard lib”, access to form vars, redirection (with raise), OO/REST-ish app with GET/POST, WSGI so it’s easy to serve and get middleware. Downloadable docs for offline development are a real bonus.

Page 16: Web micro-framework BATTLE!

What’s IN?

• easy to understand and use(docs, minimal magic, no surprises, terse)

• HTTP request/response

• URL routing (“RESTful”)

• WSGI

Monday, 22 August 2011

“standard lib”, access to form vars, redirection (with raise), OO/REST-ish app with GET/POST, WSGI so it’s easy to serve and get middleware. Downloadable docs for offline development are a real bonus.

Page 17: Web micro-framework BATTLE!

What’s IN?

• easy to understand and use(docs, minimal magic, no surprises, terse)

• HTTP request/response

• URL routing (“RESTful”)

• WSGI

• PyPy and Python 3...

Monday, 22 August 2011

“standard lib”, access to form vars, redirection (with raise), OO/REST-ish app with GET/POST, WSGI so it’s easy to serve and get middleware. Downloadable docs for offline development are a real bonus.

Page 18: Web micro-framework BATTLE!

What’s OUT?

Monday, 22 August 2011

I like dbapiext / explicitness; SQLAlchemy works plenty well; templating is handled by other things (though some frameworks have something built-in)

Page 19: Web micro-framework BATTLE!

What’s OUT?

• Object-Relational Manager(actually, any sort of DB wrapper)

Monday, 22 August 2011

I like dbapiext / explicitness; SQLAlchemy works plenty well; templating is handled by other things (though some frameworks have something built-in)

Page 20: Web micro-framework BATTLE!

What’s OUT?

• Object-Relational Manager(actually, any sort of DB wrapper)

• Template engine

Monday, 22 August 2011

I like dbapiext / explicitness; SQLAlchemy works plenty well; templating is handled by other things (though some frameworks have something built-in)

Page 21: Web micro-framework BATTLE!

Who’s out?

• Mega Frameworks:django, grok, pyramid, web2py, zope

• CubicWeb (never heard of it)

• milla (too young / incomplete)

• pump (too late to include)

• routes+webob (too complicated)

Monday, 22 August 2011

Page 22: Web micro-framework BATTLE!

WIKI

Monday, 22 August 2011

In the following there's some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps I also implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests of brevity.

Page 23: Web micro-framework BATTLE!

WIKI

• page view (GET /PageName)

Monday, 22 August 2011

In the following there's some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps I also implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests of brevity.

Page 24: Web micro-framework BATTLE!

WIKI

• page view (GET /PageName)

• creation, editing (GET & POST /edit)

Monday, 22 August 2011

In the following there's some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps I also implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests of brevity.

Page 25: Web micro-framework BATTLE!

WIKI

• page view (GET /PageName)

• creation, editing (GET & POST /edit)

• redirect (GET /)

Monday, 22 August 2011

In the following there's some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps I also implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests of brevity.

Page 26: Web micro-framework BATTLE!

WIKI

• page view (GET /PageName)

• creation, editing (GET & POST /edit)

• redirect (GET /)

• not found (GET /favicon.ico)

Monday, 22 August 2011

In the following there's some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps I also implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests of brevity.

Page 27: Web micro-framework BATTLE!

The Contenders

• cgi+wsgiref• aspen.io• bobo• bottle• cherrypy• flask• itty• pesto• web.py• werkzeug

Monday, 22 August 2011

10 frameworks, cgi+wsgiref is my baseline

Page 28: Web micro-framework BATTLE!

The Criteria

• documentation• ease of use• magic• RESTful• WSGI• Python 3• PyPy• amount of typing• size of the framework

Monday, 22 August 2011

Just to recap, this is what I’m looking for.

Page 29: Web micro-framework BATTLE!

cgi&wsgiref

1.

Monday, 22 August 2011

DOCUMENTATION: just the standard library, EASE OF USE: a bit of work but not hard, EXTRAS: none, MAGIC: no, RESTful: no, WSGI: yes

Page 30: Web micro-framework BATTLE!

cgi+wsgirefsimple WSGI appclass NotFound(Exception): passclass Redirect(Exception): def __init__(self, location): self.location = location

def app(environ, start_response): headers = [('Content-type', 'text/html')] status = '200 OK' try: for p, m, c in urls: if m != environ['REQUEST_METHOD']: continue m = re.match(p, environ['PATH_INFO']) if m is None: continue response = c(environ, **m.groupdict()) raise NotFound except NotFound: status = '404 Not Found' response = 'Not found: %s' % environ['PATH_INFO'] except Redirect, e: status = '301 Moved Permanently' headers.append(('Location', e.location)) response = '' start_response(status, headers) return response

Monday, 22 August 2011

No top-level app in wsgiref, so I had to write my own. This, by the way, is why I've written a half a dozen web frameworks.

Page 31: Web micro-framework BATTLE!

def app(environ, start_response): headers = [('Content-type', 'text/html')] status = '200 OK' try: for p, m, c in urls: if m != environ['REQUEST_METHOD']: continue m = re.match(p, environ['PATH_INFO']) if m is None: continue response = c(environ, **m.groupdict()) raise NotFound except NotFound: status = '404 Not Found' response = 'Not found: %s' % environ['PATH_INFO'] except Redirect, e: status = '301 Moved Permanently' headers.append(('Location', e.location)) response = '' start_response(status, headers) return response

cgi+wsgirefsimple WSGI app

Monday, 22 August 2011

These are the WSGI bits.

Page 32: Web micro-framework BATTLE!

def app(environ, start_response): headers = [('Content-type', 'text/html')] status = '200 OK' try: for p, m, c in urls: if m != environ['REQUEST_METHOD']: continue m = re.match(p, environ['PATH_INFO']) if m is None: continue response = c(environ, **m.groupdict()) raise NotFound except NotFound: status = '404 Not Found' response = 'Not found: %s' % environ['PATH_INFO'] except Redirect, e: status = '301 Moved Permanently' headers.append(('Location', e.location)) response = '' start_response(status, headers) return response

cgi+wsgirefsimple WSGI app

Monday, 22 August 2011

Given a list of potential actions containing a URL pattern to match, a request method and a callable, we see which one to call, if any.

Page 33: Web micro-framework BATTLE!

cgi+wsgirefactions

def index(environ): raise Redirect('/FrontPage')

def page(environ, name): if not storage.wikiname_re.match(name): raise NotFound return wiki.render_page(name)

def edit_form(environ, name): return wiki.render_edit_form(name)

def edit(environ, name): fields = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ) if 'submit' in fields: wiki.store_page(name, fields.getfirst('content')) raise Redirect("/%s" % name)

Monday, 22 August 2011

Handling forms is manual, of course.

Page 34: Web micro-framework BATTLE!

cgi+wsgirefurl mapping

urls = [ ('^/$', 'GET', index), ('^/(?P<name>\w+)/?$', 'GET', page), ('^/(?P<name>\w+)/edit$', 'GET', edit_form), ('^/(?P<name>\w+)/edit$', 'POST', edit),]

Monday, 22 August 2011

Finally we construct our list of potential handlers.

Page 35: Web micro-framework BATTLE!

cgi+wsgirefserving HTTP

server = wsgiref.simple_server.make_server('127.0.0.1', 8080, app)server.serve_forever()

Monday, 22 August 2011

Page 36: Web micro-framework BATTLE!

bobo

2.

Monday, 22 August 2011

bobo started out life as an awesome object publisher (get attributes and items) and then it became Zope. sadly as Zope grew, so bobo was tainted and now it’s not the simple publisher it once was. DOCUMENTATION: limited and focused on The Bobo Way - EASE OF USE: it's just not straight-forward, can't just create an app and publish it - I must publish a module file!?!

Page 37: Web micro-framework BATTLE!

boboindex

@bobo.query('/')def index(): return bobo.redirect("/FrontPage")

Monday, 22 August 2011

Simple enough. MAGIC: bobo ... that’s the module. Decorating for scanning. I did have some transient, unexplained trouble with this redirect ending up at “/FrontPage/”. There also doesn’t appear to be a redirect exception.

Page 38: Web micro-framework BATTLE!

bobo/PageName

@bobo.subroute('/:name?', scan=True)class Page(object): def __init__(self, request, name=None): if not storage.wikiname_re.match(name): raise bobo.NotFound('/%s' % name) self.name = name

@bobo.query('') def index(self): return wiki.render_page(name)

Monday, 22 August 2011

RESTful: quite confusing bobo.route()/bobo.subroute() request method in decorator. So here we’re setting up a “sub”route directing the URLs starting with that pattern to the decorated class (any callable will do). Instances of the class will be created being passed the name from the URL pattern. the docs seemed to imply that @bobo.query with no args would expose the function named - but that didn’t work. scan? wha? well, without it the decorations in the class aren’t ... known.

Page 39: Web micro-framework BATTLE!

boboindex

AttributeError: Page instance has no attribute 'bobo_response'

Monday, 22 August 2011

without scan I get this bizarro error

Page 40: Web micro-framework BATTLE!

bobo/PageName/edit

@bobo.query('/edit', 'GET') def edit(self): return wiki.render_edit_form(name)

@bobo.post('/edit', 'POST') def do_edit(self, content, submit=False): if submit: wiki.store_page(self.name, content) return bobo.redirect("/%s" % self.name)

Monday, 22 August 2011

Again the docs seemed to lie. @post needs that “POST” argument or it gets both GET AND POST. PRO: uses WebOb request/response.

Page 41: Web micro-framework BATTLE!

boborunning the app

if __name__ == "__main__": import boboserver boboserver.server(['-f', __file__])

Monday, 22 August 2011

And then we serve the application by pointing boboserver at the what now? We publish the module file? This is pretty wacky. WSGI: kinda (paste deploy) - no idea how to plug into my own WSGI server. I poked and poked at the source and could not figure how to get a WSGI application.

Page 42: Web micro-framework BATTLE!

cherrypy

3.

Monday, 22 August 2011

OK, so from the first example this looks to be a little different to others - kinda close to bobo by publishing objects and methods. But it’s not RESTish. DOCS: pro: uses Sphinx, con: a bit light-on in some places. The default dispatcher doesn't make RESTish control easy so I'm using a RESTish MethodDispatcher, but the only documentation for it is a simple example which doesn't really explain things in much detail. I guessed at some of the semantics, and it worked!

Page 43: Web micro-framework BATTLE!

cherrypyindex

@cherrypy.popargs('name')class Wiki(object): exposed = True

def GET(self, name=None): if not name: raise cherrypy.HTTPRedirect("/FrontPage") if not storage.wikiname_re.match(name): raise cherrypy.NotFound('/%s' % name) return wiki.render_page(name)

edit = WikiEdit()

Monday, 22 August 2011

Docs search found me the HTTPRedirect and NotFound which is nice. Yay Sphinx! “exposed” may be done in a bunch of different ways, not explained clearly in one place AFAICT.

Page 44: Web micro-framework BATTLE!

cherrypyedit

class WikiEdit(object): exposed = True

def GET(self, name): return wiki.render_edit_form(name)

def POST(self, name, content='', submit=None, cancel=None): if submit: wiki.store_page(name, content) raise cherrypy.HTTPRedirect("/%s" % name)

Monday, 22 August 2011

Page 45: Web micro-framework BATTLE!

cherrypy

the funky bit

conf = { '/': { 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), }}

if __name__ == "__main__": cherrypy.quickstart(Wiki(), '/', conf)

Monday, 22 August 2011

EASE OF USE: simple, though the config bit is a bit strange; ooh, automatic code reloading.; WSGI: I had to google and *then* look at the source to find out how to get a WSGI application: cherrypy.Application, cherrypy.request.wsgi_environ; MAGIC: minimal - just the cherrypy.request module global

Page 46: Web micro-framework BATTLE!

web.py

4.

Monday, 22 August 2011

DOCUMENTATION: great website, easy to get started

Page 47: Web micro-framework BATTLE!

web.pyindex and /PageName

class index: def GET(self): raise web.SeeOther("/FrontPage")

class page: def GET(self, name): if not storage.wikiname_re.match(name): raise web.NotFound('/%s' % name) return wiki.render_page(name)

Monday, 22 August 2011

RESTful: url mapping through ugly URLs tuple (url, name, url, name, ...) and handler named is class with request method methods GET(), POST(), ...

Page 48: Web micro-framework BATTLE!

web.py/PageName/edit

class edit: def GET(self, name): return wiki.render_edit_form(name)

def POST(self, name): f = web.input('content', submit=False, cancel=False) if f.submit: wiki.store_page(name, f.content) raise web.seeother("/%s" % name)

Monday, 22 August 2011

DOCS: was hard to find how web.input() worked; EASE OF USE: simple enough class-per-URL-match; Form doesn't support submit buttons?! Forms are a bit of a fiddly way to get to simple form submitted data. For this simple case (very much like most of my uses) it's actually easier to access the submitted information directly without involving the Form class through web.input().

Page 49: Web micro-framework BATTLE!

web.pyserving

urls = ( '/', 'index', '/(.+)/edit', 'edit', '/(.+)', 'page',)app = web.application(urls, globals())

if __name__ == "__main__": app.run()

Monday, 22 August 2011

MAGIC: url mapping through names looked up in globals? Ugh. module-level form input() global; EXTRAS: session, openid, utilities, templating, form and db abstraction; WSGI: hard-to-find web.application(...).wsgifunc(*middleware)

Page 50: Web micro-framework BATTLE!

bo!le

5.

Monday, 22 August 2011

Page 51: Web micro-framework BATTLE!

bottleindex and /PageName

@get('/')def index(): redirect("/FrontPage")

@get('/:name')def display(name): if not storage.wikiname_re.match(name): abort(404, 'Not Found :/%s' % name) return wiki.render_page(name)

Monday, 22 August 2011

PRO: SINGLE FILE! bottle.py; EASE OF USE: very simple; CON: MAGIC module global magic for request, response :-( PRO: Has first-class get(), post() as well as generic route() which configure a default app (you can also create other apps). DOCUMENTATION: very good, downloadable

Page 52: Web micro-framework BATTLE!

bottle/PageName/edit

@get('/:name/edit')def edit_form(name): return wiki.render_edit_form(name)

@post('/:name/edit')def edit(name): if request.POST.get('submit'): wiki.store_page(name, request.POST['content']) redirect("/%s" % name)

Monday, 22 August 2011

RESTful: get(), post() decorators which take URL. request.POST has the posted form vars (there’s also request.params which combines GET and POST vars).

Page 53: Web micro-framework BATTLE!

bottleserving HTTP

if __name__ == "__main__": run(host='127.0.0.1', port=8080)

Monday, 22 August 2011

WSGI: Had to look in the source but bottle.default_app.wsgi is WSGI. Bottle also has the concept of an app stack, but I didn’t look into that and am not sure what the point is. EXTRAS: static files, adapters (eg werkzeug), SimpleTemplate, lots of convenience utilities

Page 54: Web micro-framework BATTLE!

from bottle import get, post, request, run, abort, redirectimport storage

wiki = storage.Storage('contents')

@get('/')def index(): redirect("/FrontPage")

@get('/:name')def display(name): if not storage.wikiname_re.match(name): abort(404, 'Not Found :/%s' % name) return wiki.render_page(name)

@get('/:name/edit')def edit_form(name): return wiki.render_edit_form(name)

@post('/:name/edit')def edit(name): if request.POST.get('submit'): wiki.store_page(name, request.POST['content']) redirect("/%s" % name)

if __name__ == "__main__": run(host='127.0.0.1', port=8080)

Monday, 22 August 2011

Seriously, that’s it. That’s all the overhead that bottle imposes when you write a web app with it!

Page 55: Web micro-framework BATTLE!

i!y

6.

Monday, 22 August 2011

itty is really similar to bottle. They both aspire to the same simple API.

Page 56: Web micro-framework BATTLE!

ittyindex and /PageName

@get('/')def index(request): raise Redirect("/FrontPage")

@get('/(?P<name>\w+)')def display(request, name): if not storage.wikiname_re.match(name): raise NotFound('/%s' % name) return wiki.render_page(name)

Monday, 22 August 2011

get, Redirect and NotFound imported from itty (from itty import *) MAGIC module globals; DOCS: very little but straightforward and good examples; EASE OF USE: simple; RESTful: get(), post() decorators which take URL;

Page 57: Web micro-framework BATTLE!

itty/PageName/edit

@get('/(?P<name>\w+)/edit')def edit_form(request, name): return wiki.render_edit_form(name)

@post('/(?P<name>\w+)/edit')def edit(request, name): if request.POST.get('submit'): wiki.store_page(name, request.POST['content']) raise Redirect("/%s" % name)

Monday, 22 August 2011

request.POST simple enough; CON: Redirects appear in the log with tracebacks??!?

Page 58: Web micro-framework BATTLE!

ittyserving

run_itty()

Monday, 22 August 2011

WSGI: itty.handle_request (the docs don't make this clear ... and *why* request._environ ??)

Page 59: Web micro-framework BATTLE!

flask

7.

Monday, 22 August 2011

Con: depends on two external libraries: the Jinja2 template engine and the Werkzeug WSGI toolkit. DOCS: very good, and downloadable, though werkzeug references were tricky

Page 60: Web micro-framework BATTLE!

flaskindex and /PageName

app = Flask(__name__)

@app.route("/")def index(): return redirect("/FrontPage")

@app.route('/<name>')def display(name): if not storage.wikiname_re.match(name): raise NotFound('/%s' % name) return wiki.render_page(name)

Monday, 22 August 2011

Where itty and bottle had a single default app that was being configured, here we explicitly create our app. CON: There appears to be no way to raise a redirect exception. NotFound needs to be imported from werkzeug directly. No way to return a NotFound result? EASE OF USE: requirement to fall back to werkzeug sometimes makes things tricky

Page 61: Web micro-framework BATTLE!

flask/PageName/edit

@app.route('/<name>/edit', methods=['GET'])def edit_form(name): return wiki.render_edit_form(name)

@app.route('/<name>/edit', methods=['POST'])def edit(name): if request.form.get('submit'): wiki.store_page(name, request.form['content']) return redirect("/%s" % name)

Monday, 22 August 2011

Con: in the quickstart, it wasn't clear where request came from. MAGIC: request is module-level global from “flask”; RESTful: request method in app.route() decorator which also takes URL

Page 62: Web micro-framework BATTLE!

flaskserving

app.run(debug=True)

Monday, 22 August 2011

Pro: debugger works really well; WSGI: the Flask object is a WSGI app; EXTRAS: werkzeug, test support, debugger, secure cookies, Jinja2, shell

Page 63: Web micro-framework BATTLE!

pesto

8.

Monday, 22 August 2011

Page 64: Web micro-framework BATTLE!

pestoindex and /PageNameapp = dispatcher_app()

@app.match('/', 'GET')def index(request): return Response.redirect("/FrontPage")

@app.match('/<name:unicode>', 'GET')def display(request, name): if not storage.wikiname_re.match(name): return Response.not_found('/%s' % name) page = wiki.render_page(name) return Response(page)

Monday, 22 August 2011

explicit app creation - nice. DOCUMENTATION: good; EASE OF USE: simple; MAGIC: no magic whatsoever; RESTful: request method in app.match() decorator which also takes URL; unfortunately can’t just return the page contents from the function, have to wrap it in a Response.

Page 65: Web micro-framework BATTLE!

pesto/PageName/edit

@app.match('/<name:unicode>/edit', 'GET')def edit_form(request, name): page = wiki.render_edit_form(name) return Response(page)

@app.match('/<name:unicode>/edit', 'POST')def edit(request, name): if request.form.get('submit'): wiki.store_page(name, request.form['content']) return Response.redirect("/%s" % name)

Monday, 22 August 2011

Very, very similar to itty and bottle... the type is not optional - I just assumed unicode would be the default.

Page 66: Web micro-framework BATTLE!

pestoserving

httpd = simple_server.make_server('127.0.0.1', 8080, app)httpd.serve_forever()

Monday, 22 August 2011

WSGI: app is a WSGI app; EXTRAS: utilities for testing, sessions, caching, wsgi

Page 67: Web micro-framework BATTLE!

werkzeug

9.

Monday, 22 August 2011

DOCUMENTATION: very good, downloadable: The docs are a bit interestingly laid-out, with the sample program being overly complex to start with, so I had to delve into the actual API docs to figure things out. Having said that, the example program does talk about structure, ORM layers, templating, URLs and other handy things.

Page 68: Web micro-framework BATTLE!

werkzeugindex and /PageName

def index(request): raise RequestRedirect("/FrontPage")

def page(request, name): if not storage.wikiname_re.match(name): raise NotFound('/%s' % name) return wiki.render_page(name)

Monday, 22 August 2011

MAGIC: no magic

Page 69: Web micro-framework BATTLE!

werkzeug/PageName/edit

def edit_form(request, name): return wiki.render_edit_form(name)

def edit(request, name): if request.form.get('submit'): wiki.store_page(name, request.form['content']) raise RequestRedirect("/%s" % name)

Monday, 22 August 2011

Page 70: Web micro-framework BATTLE!

werkzeugURL mapping

url_map = Map([ Rule('/', endpoint=index, methods=['GET']), Rule('/<name>/edit', endpoint=edit_form, methods=['GET']), Rule('/<name>/edit', endpoint=edit, methods=['POST']), Rule('/<name>', endpoint=page, methods=['GET']),])

Monday, 22 August 2011

RESTful: Map with Rules having URL and request method type mapping to endpoint callable

Page 71: Web micro-framework BATTLE!

werkzeugapplication and serving

def application(environ, start_response): request = Request(environ) urls = url_map.bind_to_environ(environ) try: m, args = urls.match() r = m(request, **args) start_response('200 OK', [('Content-Type', 'text/html')]) return r except HTTPException, e: return e(environ, start_response)

if __name__ == "__main__": from werkzeug.serving import run_simple run_simple('localhost', 8080, application)

Monday, 22 August 2011

EASE OF USE: simple except writing my own WSGI app; EXTRAS: contributed code for sessions, ATOM, secure cookies and caching; WSGI: had to write my own, but it was short

Page 72: Web micro-framework BATTLE!

a#en.io

10.

Monday, 22 August 2011

Page 73: Web micro-framework BATTLE!

Monday, 22 August 2011

Quite a different approach to things. Even though this is going against my "no templates" rule there is no way to use this framework *without* using its templating...

Page 74: Web micro-framework BATTLE!

aspen.ioindex.html

from aspen import Response^Lraise Response(301, headers={'Location': '/FrontPage'})^L

Monday, 22 August 2011

EASE OF USE: wildly different and some things are inexplicably hard; EXTRAS: static file serving; MAGIC: the page break stuff (initialisation, action, template) hurts understandability. Oh, aspen, "raise Response(301, headers={'Location': path+'/'})" really??!?

Page 75: Web micro-framework BATTLE!

aspen.iodirectory structure

.aspenindex.html%name/index.html%name/edit

Monday, 22 August 2011

Aspen sees the %name directory and assigns the first part of the URL to the path variable “name”. Extra modules (storage, html, ...) have to be placed in the hidden ".aspen" folder.

Page 76: Web micro-framework BATTLE!

aspen.io%name/index.htmlfrom aspen import Responseimport storage

wiki = storage.Storage('contents')^Lname = request.path['name']if not storage.wikiname_re.match(name): raise Response(404, '/%s' % name)page = wiki.render_page(name)^L{{ page }}

Monday, 22 August 2011

I had to go off to the Tornado docs to figure how to HTML escaping was handled (though I probably could've just tried and discovered that it's not escaped by default). Not escaped HTML by default? RESTful: URLs map to filesystem paths

Page 77: Web micro-framework BATTLE!

aspen.io%name/edit

from aspen import Responseimport storage

wiki = storage.Storage('contents')^Lname = request.path['name']if request.method == 'POST': if 'submit' in request.body: wiki.store_page(name, request.body.one('content')) raise Response(301, headers={'Location': '/' + name})page = wiki.render_edit_form(name)response.headers.set('Content-Type', 'text/html')^L{{ page }}

Monday, 22 August 2011

The documentation is *seriously* thin. I found myself having to read the source to: 1. figure out what the request.body thing was and how to use it, and 2. figure out how to get a redirect back to the client. Oh, text/html isn’t the DEFAULT? RESTful: manually test for request method type in handler

Page 78: Web micro-framework BATTLE!

aspen.ioserving HTTP

from aspen import serverserver.main()

Monday, 22 August 2011

Put this in a file in the aspen site’s directory. Or use the aspen command-line program. WSGI: appears to be no way to get a WSGI app. I’ve given aspen a rough time here because it’s not really suitable for my purposes, but I can see how it’d actually be really nice to develop a simple website with.

Page 79: Web micro-framework BATTLE!

So, how do they rank?

• Let’s score them

• +1 or -1 on each of:Documentation, Ease of Use, Avoiding Magic, RESTful, WSGI, Python 3, PyPy and SLOC

cgi+wsgiref:0, aspen.io:0, bobo:0, bottle:0, cherrypy:0, flask:0, itty:0, pesto:0, web.py:0, werkzeug:0

Monday, 22 August 2011

Page 80: Web micro-framework BATTLE!

Documentation

• good, downloadable: cgi+wsgiref, flask, werkzeug, bottle

• good: pesto

• could be better: web.py, itty, cherrypy

• bad: bobo, aspen.io

cgi+wsgiref:1, aspen.io:-1, bobo:-1, bottle:1, cherrypy:0,flask:1, itty:0, pesto:1, web.py:0, werkzeug:1

Monday, 22 August 2011

No “hello world” ... ? Hard to find Redirect/NotFound, RESTful routing, form access, wsgi application?

Page 81: Web micro-framework BATTLE!

Ease of Use

• SIMPLE: pesto, itty, flask, web.py, bottle

• Could get complicated: werkzeug, cherrypy

• Complicated: aspen.io, bobo

• Just Plain Difficult: cgi+wsgiref

cgi+wsgiref:0, aspen.io:-2, bobo:-2, bottle:2, cherrypy:0,flask:2, itty:1, pesto:2, web.py:1, werkzeug:1

Monday, 22 August 2011

Page 82: Web micro-framework BATTLE!

Avoiding Magic

• none: cgi+wsgiref, werkzeug, pesto

• WSGI not apparent: itty

• module-level: bottle, flask, web.py, cherrypy

• just plain odd: aspen.io, bobo

cgi+wsgiref:1, aspen.io:-2, bobo:-3, bottle:1, cherrypy:-1,flask:1, itty:1, pesto:3, web.py:0, werkzeug:2

Monday, 22 August 2011

Page 83: Web micro-framework BATTLE!

RESTful

• bottle, flask, itty, pesto, web.py, werkzeug

• not obvious: bobo, cherrypy

• manual test method: aspen.io

• NO: cgi+wsgiref

cgi+wsgiref:0, aspen.io:-3, bobo:-3, bottle:2, cherrypy:-1,flask:2, itty:2, pesto:4, web.py:1, werkzeug:3

Monday, 22 August 2011

This actually dictated the structure of my wiki code more than anything else.

Page 84: Web micro-framework BATTLE!

RESTful

• Ignoring aspen.io there was 2 approaches:

• decorate callable

• listing maps directly to callable

Monday, 22 August 2011

Page 85: Web micro-framework BATTLE!

WSGI

• bottle, flask, pesto, cgi+wsgiref

• not obvious: itty, web.py, cherrypy

• manual: werkzeug

• unknown: bobo

• NO: aspen.iocgi+wsgiref:1, aspen.io:-4, bobo:-4, bottle:3, cherrypy: -1,

flask:3, itty:2, pesto:5, web.py:1, werkzeug:2

Monday, 22 August 2011

Page 86: Web micro-framework BATTLE!

Python 3

• yes: cgi+wsgiref, bottle, cherrypy

• no: bobo, itty, web.py, pesto, werkzeug, flask, aspen.io

cgi+wsgiref:2, aspen.io:-5, bobo:-5, bottle:4, cherrypy:0,flask:2, itty:1, pesto:4, web.py:0, werkzeug:1

Monday, 22 August 2011

Page 87: Web micro-framework BATTLE!

PyPy

cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,flask:3, itty:2, pesto:5, web.py:1, werkzeug:2

Monday, 22 August 2011

PyPy 1.5.0-alpha0 with GCC 4.0.1 on OS X

Page 88: Web micro-framework BATTLE!

PyPy

THEY

cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,flask:3, itty:2, pesto:5, web.py:1, werkzeug:2

Monday, 22 August 2011

PyPy 1.5.0-alpha0 with GCC 4.0.1 on OS X

Page 89: Web micro-framework BATTLE!

PyPy

THEYALL

cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,flask:3, itty:2, pesto:5, web.py:1, werkzeug:2

Monday, 22 August 2011

PyPy 1.5.0-alpha0 with GCC 4.0.1 on OS X

Page 90: Web micro-framework BATTLE!

PyPy

THEYALLRUN

cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,flask:3, itty:2, pesto:5, web.py:1, werkzeug:2

Monday, 22 August 2011

PyPy 1.5.0-alpha0 with GCC 4.0.1 on OS X

Page 91: Web micro-framework BATTLE!

wiki.py line/byte count 38/905 bottle 38/982 itty 41/1061 flask 43/1148 bobo 45/1071 web.py 45/1301 pesto 51/1267 cherrypy 55/1730 werkzeug 77/2126 aspen.io 78/2142 cgi+wsgiref

cgi+wsgiref:2, aspen.io:-5, bobo:-2, bottle:6, cherrypy:1,flask:4, itty:3, pesto:6, web.py:1, werkzeug:2

Monday, 22 August 2011

Page 92: Web micro-framework BATTLE!

framework line count 593 itty 2441 cgi+wsgiref 2697 bottle 5600 aspen.io 6649 pesto 8044 bobo 10198 web.py 16563 werkzeug 27768 cherrypy 32245 flask

cgi+wsgiref:3, aspen.io:-5, bobo:-2, bottle:7, cherrypy:0,flask:3, itty:4, pesto:6, web.py:1, werkzeug:2

Monday, 22 August 2011

Page 93: Web micro-framework BATTLE!

The Official* ScoresRank Framework Score

1 bottle 7

2 pesto 6

3 itty 4

4 flask, cgi+wsgiref 3

5 werkzeug 2

6 web.py 1

7 cherrypy 0

8 bobo -2

9 aspen.io -5Monday, 22 August 2011

The scores, as determined solely by my own criteria and weighting.

Page 94: Web micro-framework BATTLE!

OMGz0R a winner?

Bo!le!

Monday, 22 August 2011

Simple to use, great docs, RESTful and WSGI, Python 3. A single file and compact application code. Awesome!

Page 95: Web micro-framework BATTLE!

ExtrasFramework Sessions Caching Othercgi+wsgiref ✗ ✗

aspen.io ✗ ✗ static file servingbobo ✗ ✗

bottle ✗ ✗ templating, adapters, utilitiescherrypy ✓ ✓ static file serving, plugins

flask ✓ ✓ debugger, shell, werkzeugitty ✗ ✗

pesto ✓ ✓ utilities for testingweb.py ✓ ✗ openid, templating, form & db

werkzeug ✓ ✓ ATOM, secure cookies

Monday, 22 August 2011