5. aka
Richard surveys the landscape
so you (probably) donāt have to
Monday, 22 August 2011
6. 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.
7. 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.
8. 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.
9. 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.
10. 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.
11. 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.
12. 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 ofļ¬ine development are a real bonus.
13. 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 ofļ¬ine development are a real bonus.
14. 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 ofļ¬ine development are a real bonus.
15. 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 ofļ¬ine development are a real bonus.
16. 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 ofļ¬ine development are a real bonus.
17. 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 ofļ¬ine development are a real bonus.
18. 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)
19. 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)
20. 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)
21. 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
22. 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.
23. 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.
24. 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.
25. 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.
26. 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.
27. The Contenders
ā¢ cgi+wsgiref
ā¢ aspen.io
ā¢ bobo
ā¢ bottle
ā¢ cherrypy
ā¢ ļ¬ask
ā¢ itty
ā¢ pesto
ā¢ web.py
ā¢ werkzeug
Monday, 22 August 2011
10 frameworks, cgi+wsgiref is my baseline
28. 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.
29. 1.
cgi&wsgiref
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
30. cgi+wsgiref
simple WSGI app
class NotFound(Exception):
pass
class 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.
31. cgi+wsgiref
simple WSGI app
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
These are the WSGI bits.
32. cgi+wsgiref
simple WSGI app
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
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.
33. cgi+wsgiref
actions
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.
34. cgi+wsgiref
url 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.
35. cgi+wsgiref
serving HTTP
server = wsgiref.simple_server.make_server('127.0.0.1', 8080, app)
server.serve_forever()
Monday, 22 August 2011
36. 2.
bobo
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 ļ¬le!?!
37. bobo
index
@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.
38. 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.
39. bobo
index
AttributeError: Page instance has no attribute 'bobo_response'
Monday, 22 August 2011
without scan I get this bizarro error
40. 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.
41. bobo
running 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 ļ¬le? 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 ļ¬gure how to get a
WSGI application.
42. 3.
cherrypy
Monday, 22 August 2011
OK, so from the ļ¬rst 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!
43. cherrypy
index
@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.
45. 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 conļ¬g bit is a bit strange; ooh, automatic code reloading.; WSGI: I had to google and *then* look at the
source to ļ¬nd out how to get a WSGI application: cherrypy.Application, cherrypy.request.wsgi_environ; MAGIC: minimal - just the
cherrypy.request module global
46. 4.
web.py
Monday, 22 August 2011
DOCUMENTATION: great website, easy to get started
47. web.py
index 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(), ...
48. 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 ļ¬nd 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 ļ¬ddly 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().
49. web.py
serving
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-ļ¬nd web.application(...).wsgifunc(*middleware)
51. bottle
index 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 ļ¬rst-class
get(), post() as well as generic route() which conļ¬gure a default app (you can also create other apps). DOCUMENTATION: very good,
downloadable
52. 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).
53. bottle
serving 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 ļ¬les, adapters (eg werkzeug), SimpleTemplate, lots of convenience utilities
54. from bottle import get, post, request, run, abort, redirect
import 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!
55. 6.
i!y
Monday, 22 August 2011
itty is really similar to bottle. They both aspire to the same simple API.
56. itty
index 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;
57. 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??!?
58. itty
serving
run_itty()
Monday, 22 August 2011
WSGI: itty.handle_request (the docs don't make this clear ... and *why* request._environ ??)
59. 7.
ļ¬ask
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
60. ļ¬ask
index 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 conļ¬gured, 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
61. ļ¬ask
/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 āļ¬askā; RESTful: request
method in app.route() decorator which also takes URL
62. ļ¬ask
serving
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
64. pesto
index and /PageName
app = 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.
65. 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.
66. pesto
serving
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
67. 9.
werkzeug
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 ļ¬gure things out. Having said that, the example program does talk about structure,
ORM layers, templating, URLs and other handy things.
68. werkzeug
index 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
70. werkzeug
URL 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
71. werkzeug
application 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
73. 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...
74. aspen.io
index.html
from aspen import Response
^L
raise Response(301, headers={'Location': '/FrontPage'})
^L
Monday, 22 August 2011
EASE OF USE: wildly different and some things are inexplicably hard; EXTRAS: static ļ¬le serving; MAGIC: the page break stuff
(initialisation, action, template) hurts understandability. Oh, aspen, "raise Response(301, headers={'Location': path+'/'})" really??!?
75. aspen.io
directory structure
.aspen
index.html
%name/index.html
%name/edit
Monday, 22 August 2011
Aspen sees the %name directory and assigns the ļ¬rst part of the URL to the path variable ānameā. Extra modules (storage, html, ...) have
to be placed in the hidden ".aspen" folder.
76. aspen.io
%name/index.html
from aspen import Response
import storage
wiki = storage.Storage('contents')
^L
name = 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 ļ¬gure 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 ļ¬lesystem paths
77. aspen.io
%name/edit
from aspen import Response
import storage
wiki = storage.Storage('contents')
^L
name = 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. ļ¬gure out what the request.body thing was and how
to use it, and 2. ļ¬gure 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
78. aspen.io
serving HTTP
from aspen import server
server.main()
Monday, 22 August 2011
Put this in a ļ¬le 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.
79. 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,
ļ¬ask:0, itty:0, pesto:0, web.py:0, werkzeug:0
Monday, 22 August 2011
80. Documentation
ā¢ good, downloadable: cgi+wsgiref, ļ¬ask,
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,
ļ¬ask:1, itty:0, pesto:1, web.py:0, werkzeug:1
Monday, 22 August 2011
No āhello worldā ... ? Hard to ļ¬nd Redirect/NotFound, RESTful routing, form access, wsgi application?
81. Ease of Use
ā¢ SIMPLE: pesto, itty, ļ¬ask, web.py, bottle
ā¢ Could get complicated: werkzeug, cherrypy
ā¢ Complicated: aspen.io, bobo
ā¢ Just Plain Difļ¬cult: cgi+wsgiref
cgi+wsgiref:0, aspen.io:-2, bobo:-2, bottle:2, cherrypy:0,
ļ¬ask:2, itty:1, pesto:2, web.py:1, werkzeug:1
Monday, 22 August 2011
83. RESTful
ā¢ bottle, ļ¬ask, 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,
ļ¬ask: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.
84. RESTful
ā¢ Ignoring aspen.io there was 2 approaches:
ā¢ decorate callable
ā¢ listing maps directly to callable
Monday, 22 August 2011
87. PyPy
cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,
ļ¬ask: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
88. PyPy
THEY
cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,
ļ¬ask: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
89. PyPy
THEY
ALL
cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,
ļ¬ask: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
90. PyPy
THEY
ALL
RUN
cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,
ļ¬ask: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
93. The Ofļ¬cial* Scores
Rank Framework Score
1 bottle 7
2 pesto 6
3 itty 4
4 ļ¬ask, cgi+wsgiref 3
5 werkzeug 2
6 web.py 1
7 cherrypy 0
8 bobo -2
9 aspen.io -5
Monday, 22 August 2011
The scores, as determined solely by my own criteria and weighting.
94. OMGz0R a winner?
Bo!le!
Monday, 22 August 2011
Simple to use, great docs, RESTful and WSGI, Python 3. A single ļ¬le and compact application code. Awesome!