webMonday, 22 August 2011
web                         micro-frameworkMonday, 22 August 2011
web                         micro-framework                           Ba!le!Monday, 22 August 2011
akaMonday, 22 August 2011
aka        Richard surveys the landscape       so you (probably) don’t have toMonday, 22 August 2011
IntroductionMonday, 22 August 2011I primarily develop systems for routing calls and texts, and provisioning phone numbers ...
Introduction                   • I write code for a telcoMonday, 22 August 2011I primarily develop systems for routing cal...
Introduction                   • I write code for a telco                   • Lots of discrete system applicationsMonday, ...
Introduction                   • I write code for a telco                   • Lots of discrete system applications        ...
Introduction                   • I write code for a telco                   • Lots of discrete system applications        ...
Introduction                   • I write code for a telco                   • Lots of discrete system applications        ...
What’s IN?Monday, 22 August 2011“standard lib”, access to form vars, redirection (with raise), OO/REST-ish app with GET/PO...
What’s IN?                   • easy to understand and use                         (docs, minimal magic, no surprises, ters...
What’s IN?                   • easy to understand and use                         (docs, minimal magic, no surprises, ters...
What’s IN?                   • easy to understand and use                         (docs, minimal magic, no surprises, ters...
What’s IN?                   • easy to understand and use                         (docs, minimal magic, no surprises, ters...
What’s IN?                   • easy to understand and use                         (docs, minimal magic, no surprises, ters...
What’s OUT?Monday, 22 August 2011I like dbapiext / explicitness; SQLAlchemy works plenty well; templating is handled by ot...
What’s OUT?                   • Object-Relational Manager                         (actually, any sort of DB wrapper)Monday...
What’s OUT?                   • Object-Relational Manager                         (actually, any sort of DB wrapper)      ...
Who’s out?                   • Mega Frameworks:                         django, grok, pyramid, web2py, zope               ...
WIKIMonday, 22 August 2011In the following theres some core wiki code which is used to manipulate the wiki storage and ren...
WIKI                   • page view (GET /PageName)Monday, 22 August 2011In the following theres some core wiki code which ...
WIKI                   • page view (GET /PageName)                   • creation, editing (GET & POST /edit)Monday, 22 Augu...
WIKI                   • page view (GET /PageName)                   • creation, editing (GET & POST /edit)               ...
WIKI                   • page view (GET /PageName)                   • creation, editing (GET & POST /edit)               ...
The Contenders                                            •   cgi+wsgiref                                            •   a...
The Criteria                                               • documentation                                               •...
1.                         cgi&wsgirefMonday, 22 August 2011DOCUMENTATION: just the standard library, EASE OF USE: a bit o...
cgi+wsgiref                             simple WSGI app                              class NotFound(Exception):           ...
cgi+wsgiref                              simple WSGI app                         def app(environ, start_response):        ...
cgi+wsgiref                             simple WSGI app                         def app(environ, start_response):         ...
cgi+wsgiref                                        actions              def index(environ):                  raise Redirec...
cgi+wsgiref                                           url mapping      urls = [          (^/$, GET, index),          (^/(?...
cgi+wsgiref                         serving HTTP   server = wsgiref.simple_server.make_server(127.0.0.1, 8080, app)   serv...
2.                                                     boboMonday, 22 August 2011bobo started out life as an awesome objec...
bobo                                                       index                              @bobo.query(/)              ...
bobo                                           /PageName                         @bobo.subroute(/:name?, scan=True)       ...
bobo                                        index    AttributeError: Page instance has no attribute bobo_responseMonday, 2...
bobo                             /PageName/edit                         @bobo.query(/edit, GET)                         de...
bobo                               running the app                           if __name__ == "__main__":                   ...
3.                                            cherrypyMonday, 22 August 2011OK, so from the first example this looks to be ...
cherrypy                                                     index                  @cherrypy.popargs(name)               ...
cherrypy                                       edit       class WikiEdit(object):           exposed = True                ...
cherrypy                                      the funky bit                         conf = {                             /...
4.                                         web.pyMonday, 22 August 2011DOCUMENTATION: great website, easy to get started
web.py                         index and /PageName                         class index:                             def GE...
web.py                                /PageName/edit          class edit:              def GET(self, name):               ...
web.py                                                    serving                            urls = (                     ...
5.                         bo!leMonday, 22 August 2011
bottle                         index and /PageName                     @get(/)                     def index():           ...
bottle                               /PageName/edit                @get(/:name/edit)                def edit_form(name):  ...
bottle                                    serving HTTP                         if __name__ == "__main__":                 ...
from bottle import get, post, request, run, abort, redirect                         import storage                        ...
6.                                                            i!yMonday, 22 August 2011itty is really similar to bottle. T...
itty                         index and /PageName                          @get(/)                          def index(reque...
itty                              /PageName/edit             @get(/(?P<name>w+)/edit)             def edit_form(request, n...
itty                                                    serving                                                         ru...
7.                                                  flaskMonday, 22 August 2011Con: depends on two external libraries: the ...
flask                         index and /PageName                          app = Flask(__name__)                          @...
flask                               /PageName/edit                 @app.route(/<name>/edit, methods=[GET])                 ...
flask                                                    serving                                                    app.run...
8.                         pestoMonday, 22 August 2011
pesto                         index and /PageName                         app = dispatcher_app()                         @...
pesto                                /PageName/edit                 @app.match(/<name:unicode>/edit, GET)                 ...
pesto                                                     serving          httpd = simple_server.make_server(127.0.0.1, 80...
9.                                        werkzeugMonday, 22 August 2011DOCUMENTATION: very good, downloadable: The docs a...
werkzeug                         index and /PageName                          def index(request):                         ...
werkzeug                         /PageName/edit                def edit_form(request, name):                    return wik...
werkzeug                                   URL mapping       url_map = Map([           Rule(/, endpoint=index, methods=[GE...
werkzeug                   application and serving    def application(environ, start_response):        request = Request(e...
10.                         a#en.ioMonday, 22 August 2011
Monday, 22 August 2011Quite a different approach to things. Even though this is going against my "no templates" rule there...
aspen.io                                               index.html                from aspen import Response               ...
aspen.io                         directory structure                                                   .aspen             ...
aspen.io                         %name/index.html                           from aspen import Response                    ...
aspen.io                                         %name/edit             from aspen import Response             import stor...
aspen.io                                      serving HTTP                                              from aspen import ...
So, how do they rank?                   • Let’s score them                   • +1 or -1 on each of:                       ...
Documentation                   • good, downloadable: cgi+wsgiref, flask,                         werkzeug, bottle         ...
Ease of Use                   • SIMPLE: pesto, itty, flask, web.py, bottle                   • Could get complicated: werkz...
Avoiding Magic                   • none: cgi+wsgiref, werkzeug, pesto                   • WSGI not apparent: itty         ...
RESTful                   • bottle, flask, itty, pesto, web.py, werkzeug                   • not obvious: bobo, cherrypy   ...
RESTful                   • Ignoring aspen.io there was 2 approaches:                    • decorate callable              ...
WSGI               • bottle, flask, pesto, cgi+wsgiref               • not obvious: itty, web.py, cherrypy               • ...
Python 3                   • yes: cgi+wsgiref, bottle, cherrypy                   • no: bobo, itty, web.py, pesto, werkzeu...
PyPy                          cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1,                               flas...
PyPy                                 THEY                          cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy...
PyPy                                 THEY                                  ALL                          cgi+wsgiref:3, asp...
PyPy                                 THEY                                  ALL                                  RUN       ...
wiki.py line/byte count                                        38/905 bottle                                        38/982...
framework line count                                         593 itty                                         2441 cgi+wsg...
The Official* Scores                             Rank                     Framework        Score                           ...
OMGz0R a winner?                                      Bo!le!Monday, 22 August 2011Simple to use, great docs, RESTful and W...
Extras            Framework     Sessions Caching                Other            cgi+wsgiref      ✗       ✗              a...
Upcoming SlideShare
Loading in...5
×

Web micro-framework BATTLE!

116,301

Published on

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

Published in: Technology, Business
13 Comments
148 Likes
Statistics
Notes
No Downloads
Views
Total Views
116,301
On Slideshare
0
From Embeds
0
Number of Embeds
30
Actions
Shares
0
Downloads
886
Comments
13
Likes
148
Embeds 0
No embeds

No notes for slide

Web micro-framework BATTLE!

  1. 1. webMonday, 22 August 2011
  2. 2. web micro-frameworkMonday, 22 August 2011
  3. 3. web micro-framework Ba!le!Monday, 22 August 2011
  4. 4. akaMonday, 22 August 2011
  5. 5. aka Richard surveys the landscape so you (probably) don’t have toMonday, 22 August 2011
  6. 6. IntroductionMonday, 22 August 2011I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, butI’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.
  7. 7. Introduction • I write code for a telcoMonday, 22 August 2011I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, butI’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.
  8. 8. Introduction • I write code for a telco • Lots of discrete system applicationsMonday, 22 August 2011I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, butI’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.
  9. 9. Introduction • I write code for a telco • Lots of discrete system applications • Connected through HTTPMonday, 22 August 2011I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, butI’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.
  10. 10. Introduction • I write code for a telco • Lots of discrete system applications • Connected through HTTP • I write little HTTP servers all the timeMonday, 22 August 2011I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, butI’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.
  11. 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-frameworksMonday, 22 August 2011I primarily develop systems for routing calls and texts, and provisioning phone numbers and things like that. I also maintain websites, butI’m not talking about those today. I’m talking about HTTP as a layer for connecting my systems.
  12. 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 getmiddleware. Downloadable docs for offline development are a real bonus.
  13. 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 getmiddleware. Downloadable docs for offline development are a real bonus.
  14. 14. What’s IN? • easy to understand and use (docs, minimal magic, no surprises, terse) • HTTP request/responseMonday, 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 getmiddleware. Downloadable docs for offline development are a real bonus.
  15. 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 getmiddleware. Downloadable docs for offline development are a real bonus.
  16. 16. What’s IN? • easy to understand and use (docs, minimal magic, no surprises, terse) • HTTP request/response • URL routing (“RESTful”) • WSGIMonday, 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 getmiddleware. Downloadable docs for offline development are a real bonus.
  17. 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 getmiddleware. Downloadable docs for offline development are a real bonus.
  18. 18. What’s OUT?Monday, 22 August 2011I like dbapiext / explicitness; SQLAlchemy works plenty well; templating is handled by other things (though some frameworks havesomething built-in)
  19. 19. What’s OUT? • Object-Relational Manager (actually, any sort of DB wrapper)Monday, 22 August 2011I like dbapiext / explicitness; SQLAlchemy works plenty well; templating is handled by other things (though some frameworks havesomething built-in)
  20. 20. What’s OUT? • Object-Relational Manager (actually, any sort of DB wrapper) • Template engineMonday, 22 August 2011I like dbapiext / explicitness; SQLAlchemy works plenty well; templating is handled by other things (though some frameworks havesomething built-in)
  21. 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. 22. WIKIMonday, 22 August 2011In the following theres some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps Ialso implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests ofbrevity.
  23. 23. WIKI • page view (GET /PageName)Monday, 22 August 2011In the following theres some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps Ialso implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests ofbrevity.
  24. 24. WIKI • page view (GET /PageName) • creation, editing (GET & POST /edit)Monday, 22 August 2011In the following theres some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps Ialso implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests ofbrevity.
  25. 25. WIKI • page view (GET /PageName) • creation, editing (GET & POST /edit) • redirect (GET /)Monday, 22 August 2011In the following theres some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps Ialso implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests ofbrevity.
  26. 26. WIKI • page view (GET /PageName) • creation, editing (GET & POST /edit) • redirect (GET /) • not found (GET /favicon.ico)Monday, 22 August 2011In the following theres some core wiki code which is used to manipulate the wiki storage and render pages. When I wrote the wiki apps Ialso implemented history and reversion to reinforce each framework’s approach, but I’ve left that out of these slides in the interests ofbrevity.
  27. 27. The Contenders • cgi+wsgiref • aspen.io • bobo • bottle • cherrypy • flask • itty • pesto • web.py • werkzeugMonday, 22 August 201110 frameworks, cgi+wsgiref is my baseline
  28. 28. The Criteria • documentation • ease of use • magic • RESTful • WSGI • Python 3 • PyPy • amount of typing • size of the frameworkMonday, 22 August 2011Just to recap, this is what I’m looking for.
  29. 29. 1. cgi&wsgirefMonday, 22 August 2011DOCUMENTATION: just the standard library, EASE OF USE: a bit of work but not hard, EXTRAS: none, MAGIC: no, RESTful: no, WSGI:yes
  30. 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 responseMonday, 22 August 2011No top-level app in wsgiref, so I had to write my own. This, by the way, is why Ive written a half a dozen web frameworks.
  31. 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 responseMonday, 22 August 2011These are the WSGI bits.
  32. 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 responseMonday, 22 August 2011Given 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. 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 2011Handling forms is manual, of course.
  34. 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 2011Finally we construct our list of potential handlers.
  35. 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. 36. 2. boboMonday, 22 August 2011bobo started out life as an awesome object publisher (get attributes and items) and then it became Zope. sadly as Zope grew, so bobo wastainted and now it’s not the simple publisher it once was. DOCUMENTATION: limited and focused on The Bobo Way - EASE OF USE: itsjust not straight-forward, cant just create an app and publish it - I must publish a module file!?!
  37. 37. bobo index @bobo.query(/) def index(): return bobo.redirect("/FrontPage")Monday, 22 August 2011Simple enough. MAGIC: bobo ... that’s the module. Decorating for scanning. I did have some transient, unexplained trouble with thisredirect ending up at “/FrontPage/”. There also doesn’t appear to be a redirect exception.
  38. 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 2011RESTful: quite confusing bobo.route()/bobo.subroute() request method in decorator. So here we’re setting up a “sub”route directing theURLs starting with that pattern to the decorated class (any callable will do). Instances of the class will be created being passed the namefrom the URL pattern. the docs seemed to imply that @bobo.query with no args would expose the function named - but that didn’twork. scan? wha? well, without it the decorations in the class aren’t ... known.
  39. 39. bobo index AttributeError: Page instance has no attribute bobo_responseMonday, 22 August 2011without scan I get this bizarro error
  40. 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 2011Again the docs seemed to lie. @post needs that “POST” argument or it gets both GET AND POST. PRO: uses WebOb request/response.
  41. 41. bobo running the app if __name__ == "__main__": import boboserver boboserver.server([-f, __file__])Monday, 22 August 2011And 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 aWSGI application.
  42. 42. 3. cherrypyMonday, 22 August 2011OK, 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’snot RESTish. DOCS: pro: uses Sphinx, con: a bit light-on in some places. The default dispatcher doesnt make RESTish control easy so Imusing a RESTish MethodDispatcher, but the only documentation for it is a simple example which doesnt really explain things in muchdetail. I guessed at some of the semantics, and it worked!
  43. 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 2011Docs 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.
  44. 44. cherrypy edit 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
  45. 45. cherrypy the funky bit conf = { /: { request.dispatch: cherrypy.dispatch.MethodDispatcher(), } } if __name__ == "__main__": cherrypy.quickstart(Wiki(), /, conf)Monday, 22 August 2011EASE OF USE: simple, though the config bit is a bit strange; ooh, automatic code reloading.; WSGI: I had to google and *then* look at thesource to find out how to get a WSGI application: cherrypy.Application, cherrypy.request.wsgi_environ; MAGIC: minimal - just thecherrypy.request module global
  46. 46. 4. web.pyMonday, 22 August 2011DOCUMENTATION: great website, easy to get started
  47. 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 2011RESTful: url mapping through ugly URLs tuple (url, name, url, name, ...) and handler named is class with request method methods GET(),POST(), ...
  48. 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 2011DOCS: was hard to find how web.input() worked; EASE OF USE: simple enough class-per-URL-match; Form doesnt support submitbuttons?! 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) itsactually easier to access the submitted information directly without involving the Form class through web.input().
  49. 49. web.py serving urls = ( /, index, /(.+)/edit, edit, /(.+), page, ) app = web.application(urls, globals()) if __name__ == "__main__": app.run()Monday, 22 August 2011MAGIC: 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)
  50. 50. 5. bo!leMonday, 22 August 2011
  51. 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 2011PRO: SINGLE FILE! bottle.py; EASE OF USE: very simple; CON: MAGIC module global magic for request, response :-( PRO: Has first-classget(), post() as well as generic route() which configure a default app (you can also create other apps). DOCUMENTATION: very good,downloadable
  52. 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 2011RESTful: get(), post() decorators which take URL. request.POST has the posted form vars (there’s also request.params which combinesGET and POST vars).
  53. 53. bottle serving HTTP if __name__ == "__main__": run(host=127.0.0.1, port=8080)Monday, 22 August 2011WSGI: 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 intothat and am not sure what the point is. EXTRAS: static files, adapters (eg werkzeug), SimpleTemplate, lots of convenience utilities
  54. 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 2011Seriously, that’s it. That’s all the overhead that bottle imposes when you write a web app with it!
  55. 55. 6. i!yMonday, 22 August 2011itty is really similar to bottle. They both aspire to the same simple API.
  56. 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 2011get, Redirect and NotFound imported from itty (from itty import *) MAGIC module globals; DOCS: very little but straightforward andgood examples; EASE OF USE: simple; RESTful: get(), post() decorators which take URL;
  57. 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 2011request.POST simple enough; CON: Redirects appear in the log with tracebacks??!?
  58. 58. itty serving run_itty()Monday, 22 August 2011WSGI: itty.handle_request (the docs dont make this clear ... and *why* request._environ ??)
  59. 59. 7. flaskMonday, 22 August 2011Con: 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. 60. flask 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 2011Where itty and bottle had a single default app that was being configured, here we explicitly create our app. CON: There appears to be noway to raise a redirect exception. NotFound needs to be imported from werkzeug directly. No way to return a NotFound result? EASEOF USE: requirement to fall back to werkzeug sometimes makes things tricky
  61. 61. 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 2011Con: in the quickstart, it wasnt clear where request came from. MAGIC: request is module-level global from “flask”; RESTful: requestmethod in app.route() decorator which also takes URL
  62. 62. flask serving app.run(debug=True)Monday, 22 August 2011Pro: debugger works really well; WSGI: the Flask object is a WSGI app; EXTRAS: werkzeug, test support, debugger, secure cookies, Jinja2,shell
  63. 63. 8. pestoMonday, 22 August 2011
  64. 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 2011explicit app creation - nice. DOCUMENTATION: good; EASE OF USE: simple; MAGIC: no magic whatsoever; RESTful: request method inapp.match() decorator which also takes URL; unfortunately can’t just return the page contents from the function, have to wrap it in aResponse.
  65. 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 2011Very, very similar to itty and bottle... the type is not optional - I just assumed unicode would be the default.
  66. 66. pesto serving httpd = simple_server.make_server(127.0.0.1, 8080, app) httpd.serve_forever()Monday, 22 August 2011WSGI: app is a WSGI app; EXTRAS: utilities for testing, sessions, caching, wsgi
  67. 67. 9. werkzeugMonday, 22 August 2011DOCUMENTATION: very good, downloadable: The docs are a bit interestingly laid-out, with the sample program being overly complex tostart 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.
  68. 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 2011MAGIC: no magic
  69. 69. 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
  70. 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 2011RESTful: Map with Rules having URL and request method type mapping to endpoint callable
  71. 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 2011EASE 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
  72. 72. 10. a#en.ioMonday, 22 August 2011
  73. 73. Monday, 22 August 2011Quite 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. 74. aspen.io index.html from aspen import Response ^L raise Response(301, headers={Location: /FrontPage}) ^LMonday, 22 August 2011EASE 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??!?
  75. 75. aspen.io directory structure .aspen index.html %name/index.html %name/editMonday, 22 August 2011Aspen sees the %name directory and assigns the first part of the URL to the path variable “name”. Extra modules (storage, html, ...) haveto be placed in the hidden ".aspen" folder.
  76. 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 2011I had to go off to the Tornado docs to figure how to HTML escaping was handled (though I probably couldve just tried and discoveredthat its not escaped by default). Not escaped HTML by default? RESTful: URLs map to filesystem paths
  77. 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 2011The documentation is *seriously* thin. I found myself having to read the source to: 1. figure out what the request.body thing was and howto 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 requestmethod type in handler
  78. 78. aspen.io serving HTTP from aspen import server server.main()Monday, 22 August 2011Put 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 developa simple website with.
  79. 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, flask:0, itty:0, pesto:0, web.py:0, werkzeug:0Monday, 22 August 2011
  80. 80. 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:1Monday, 22 August 2011No “hello world” ... ? Hard to find Redirect/NotFound, RESTful routing, form access, wsgi application?
  81. 81. 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:1Monday, 22 August 2011
  82. 82. 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:2Monday, 22 August 2011
  83. 83. 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:3Monday, 22 August 2011This actually dictated the structure of my wiki code more than anything else.
  84. 84. RESTful • Ignoring aspen.io there was 2 approaches: • decorate callable • listing maps directly to callableMonday, 22 August 2011
  85. 85. WSGI • bottle, flask, pesto, cgi+wsgiref • not obvious: itty, web.py, cherrypy • manual: werkzeug • unknown: bobo • NO: aspen.io cgi+wsgiref:1, aspen.io:-4, bobo:-4, bottle:3, cherrypy: -1, flask:3, itty:2, pesto:5, web.py:1, werkzeug:2Monday, 22 August 2011
  86. 86. 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:1Monday, 22 August 2011
  87. 87. PyPy cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1, flask:3, itty:2, pesto:5, web.py:1, werkzeug:2Monday, 22 August 2011PyPy 1.5.0-alpha0 with GCC 4.0.1 on OS X
  88. 88. PyPy THEY cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1, flask:3, itty:2, pesto:5, web.py:1, werkzeug:2Monday, 22 August 2011PyPy 1.5.0-alpha0 with GCC 4.0.1 on OS X
  89. 89. PyPy THEY ALL cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1, flask:3, itty:2, pesto:5, web.py:1, werkzeug:2Monday, 22 August 2011PyPy 1.5.0-alpha0 with GCC 4.0.1 on OS X
  90. 90. PyPy THEY ALL RUN cgi+wsgiref:3, aspen.io:-4, bobo:-4, bottle:5, cherrypy:1, flask:3, itty:2, pesto:5, web.py:1, werkzeug:2Monday, 22 August 2011PyPy 1.5.0-alpha0 with GCC 4.0.1 on OS X
  91. 91. 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:2Monday, 22 August 2011
  92. 92. 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:2Monday, 22 August 2011
  93. 93. The Official* Scores Rank 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 2011The scores, as determined solely by my own criteria and weighting.
  94. 94. OMGz0R a winner? Bo!le!Monday, 22 August 2011Simple to use, great docs, RESTful and WSGI, Python 3. A single file and compact application code. Awesome!
  95. 95. Extras Framework Sessions Caching Other cgi+wsgiref ✗ ✗ aspen.io ✗ ✗ static file serving bobo ✗ ✗ bottle ✗ ✗ templating, adapters, utilities cherrypy ✓ ✓ static file serving, plugins flask ✓ ✓ debugger, shell, werkzeug itty ✗ ✗ pesto ✓ ✓ utilities for testing web.py ✓ ✗ openid, templating, form & db werkzeug ✓ ✓ ATOM, secure cookiesMonday, 22 August 2011
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×