In a web service, you are free to return whatever set of responses formats you so choose; however, plain text, JSON, and HTML are probably the most common
| Format | Human-friendly | Computer-friendly |
| HTML | Yes | Yes, if structured |
| JSON | No | Yes |
| text/plain | Yes | Maybe |
Often, a web service may convey information in more than one format. (Example: /portfolio/process.gv.txt, /portfolio/process.gv.txt?format=raw, /portfolio/process.gv.txt?format=svg; you could also use the Accept: header, but you can't make a permalink this way ) (Another example: /my-service/foo.json, /my-service/foo.txt, /my-service/foo.html, etc.)
JSON == JavaScript Object Notation
Returning JSON in python is easy:
>>> import json
>>> foo = { 'hello': 1, 'foobar': 2, 'fleem': {'nested': True, 'error': None}}
>>> json.dumps(foo)
'{"fleem": {"error": null, "nested": true}, "foobar": 2, "hello": 1}'
In a web service, you would just return this as the response.
In recent versions of python, the json module is part of the standard library. In older versions, you will have to easy_install simplejson
How to support both json and simplejson:
try: import json except ImportError: import simplejson as json
You should also add simplejson to the install_requires section of your setup.py
Of course, you can only use json.dumps() to serialize objects that are supported in JSON, such as strings, numbers, dictionaries, arrays, True, False, and None. Try to do this with other objects will result in an error.
>>> import sys
>>> json.dumps(sys.stdin)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.6/json/__init__.py", line 230, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python2.6/json/encoder.py", line 367, in encode
chunks = list(self.iterencode(o))
File "/usr/lib/python2.6/json/encoder.py", line 317, in _iterencode
for chunk in self._iterencode_default(o, markers):
File "/usr/lib/python2.6/json/encoder.py", line 323, in _iterencode_default
newobj = self.default(o)
File "/usr/lib/python2.6/json/encoder.py", line 344, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <open file '<stdin>', mode 'r' at 0xb78c2020> is not JSON serializable
Loading JSON is doable with json.loads, but it is picky
A template is a prototype of a document that will be rendered with variables (and possibly logic) to create a final document.
Let's say you have a python file, that you want to package. We'll use a8e.py from last class. But before we start ...
Why bother creating a python package?
If you're just going to run a script from the command line, then you might not want to package it. However, making a real package has certain advantages:
So how do I create one?
PasteScript comes with a basic_package template out of the box. Once you easy_install PasteScript, it should be available to you.
Making a8e.py into a package:
>>> from a8e import a8e
>>> a8e.__file__
'/home/jhammel/stage/src/a8e/a8e/a8e.py'
>>> dir(a8e)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a8e', 'main', 'sys', 'urllib2']
>>> a8e.a8e('hello')
'h3o'
>>>
The filesystem layout looks something like this:
a8e/ |-- a8e | |-- a8e.py | `-- __init__.py |-- setup.cfg `-- setup.py
(and the setup.cfg is pretty optional)
It is easier to start a new project from an example.
Installing webob_view:
# create a virtualenv if you haven't cd webob_view python setup.py develop
Listing available PasteScript templates:
(stage)> paster create --list-templates Available templates: basic_package: A basic setuptools-enabled package command_script: pastescript template for creating command line applications console_script: pastescript template for creating command line applications genshi_view: a simple view with webob + genshi paste_deploy: A web application deployed through paste.deploy webob_view: a simple view with webob
Creating a new project with webob_view :
(stage)> paster create -t webob_view hello Selected and implied templates: webob-view#webob_view a simple view with webob Variables: egg: hello package: hello project: hello Enter description (One-line description of the package) ['']: a description Enter author (Author name) ['']: Jeff Hammel Enter author_email (Author email) ['']: [email protected] Enter url (URL of homepage) ['']: http://k0s.org/ Enter port (port to serve paste) ['']: 7654 Creating template webob_view Creating directory ./hello Recursing into +package+ Creating ./hello/hello/ Copying __init__.py to ./hello/hello/__init__.py Copying dispatcher.py to ./hello/hello/dispatcher.py Copying factory.py_tmpl to ./hello/hello/factory.py Copying handlers.py to ./hello/hello/handlers.py Copying +package+.ini_tmpl to ./hello/hello.ini Copying README.txt_tmpl to ./hello/README.txt Copying setup.py_tmpl to ./hello/setup.py Running /home/jhammel/stage/bin/python setup.py egg_info
Serving the created project:
cd hello python setup.py develop (stage)> paster serve hello.ini Starting server in PID 2050. serving on 0.0.0.0:7654 view at http://127.0.0.1:7654
Viewing the page:
> curl http://127.0.0.1:7654 <html><body><form method="post">Hello, <input type="text" name="name" value="world"/></form></body></html>
Or use your browser!
When you render the webob_view template, what do you get out of it?
Being a template, you are allowed -- nay, encouraged! -- to alter any of the resultant code (pylons has a similar philosophy)
Why web templates?
Python web templates:
Templates are typically used by passing in variables. In python, this is usually a dict.
Most template flavors allow simple display-oriented logic (django's does not)
If you don't use templates, your code ends up looking like this:
def application(environ, start_response):
"""a simple application to alphabetize words"""
request = Request(environ)
words = sorted(request.GET.keys())
variables = { 'title': request.GET.get('title', 'Hello World'),
'body': '<li>' + '</li><li>'.join(words) + '</li>'
}
template = """<html><head><title>%(title)s</title><body><h1>%(title)s</h1><div><ul>%(body)s</ul></div></body></html>""" % variables
response = Response(content_type='text/html',
template)
return response(environ, start_response)
Genshi is an XML and text templating language that focuses on robustness and streams
The previous example:
from genshi.template import TemplateLoader
loader = TemplateLoader('/path/to/directory')
def application(environ, start_response):
"""a simple application to alphabetize words"""
request = Request(environ)
words = sorted(request.GET.keys())
variables = { 'title': request.GET.get('title', 'Hello World'),
'words': words
}
template = self.loader.load('alphabetize.html')
content = template.genereate(**variables).render()
response = Response(content_type='text/html',
content)
return response(environ, start_response)
The template:
${file('/home/jhammel/mozilla/web/craft/alphabetize.html')}
alphabetize.html
Remember the crazy URL that made the page black and blink weird?
http://k0s.org/mozilla/craft/?show_header=Accept,Host,Unicorn&blink=true&black#end
This is all done with a genshi template. The WebOb Request object is passed in. From there, the template does the rest.
${file('/home/jhammel/mozilla/web/craft/index.html')}
genshi_view extends webob_view with genshi templates
Other features over webob_view:
You may not need these for every project, but they're easy to delete and they might be useful
Yes, this is a system of (web) templates in a (file) template
(stage)> paster create -t genshi_view SimpleWiki Selected and implied templates: genshi-view#genshi_view a simple view with webob + genshi Variables: egg: SimpleWiki package: simplewiki project: SimpleWiki Enter description (One-line description of the package) ['']: an example wiki with genshi Enter author (Author name) ['']: Jeff Hammel Enter author_email (Author email) ['']: [email protected] Enter url (URL of homepage) ['']: http://k0s.org/mozilla/craft/ Enter port (port to serve paste) ['']: 12345 Creating template genshi_view Creating directory ./SimpleWiki Recursing into +package+ Creating ./SimpleWiki/simplewiki/ Copying __init__.py to ./SimpleWiki/simplewiki/__init__.py Copying dispatcher.py to ./SimpleWiki/simplewiki/dispatcher.py Copying factory.py_tmpl to ./SimpleWiki/simplewiki/factory.py Copying handlers.py to ./SimpleWiki/simplewiki/handlers.py Recursing into static Creating ./SimpleWiki/simplewiki/static/ Copying jquery.js to ./SimpleWiki/simplewiki/static/jquery.js Recursing into templates Creating ./SimpleWiki/simplewiki/templates/ Copying index.html to ./SimpleWiki/simplewiki/templates/index.html Copying navigation.html to ./SimpleWiki/simplewiki/templates/navigation.html Copying +package+.ini_tmpl to ./SimpleWiki/simplewiki.ini Copying README.txt_tmpl to ./SimpleWiki/README.txt Copying setup.py_tmpl to ./SimpleWiki/setup.py Running /home/jhammel/stage/bin/python setup.py egg_info (stage)> cd SimpleWiki/ (stage)> python setup.py develop running develop running egg_info writing requirements to SimpleWiki.egg-info/requires.txt writing SimpleWiki.egg-info/PKG-INFO writing top-level names to SimpleWiki.egg-info/top_level.txt writing dependency_links to SimpleWiki.egg-info/dependency_links.txt writing entry points to SimpleWiki.egg-info/entry_points.txt writing requirements to SimpleWiki.egg-info/requires.txt writing SimpleWiki.egg-info/PKG-INFO writing top-level names to SimpleWiki.egg-info/top_level.txt writing dependency_links to SimpleWiki.egg-info/dependency_links.txt writing entry points to SimpleWiki.egg-info/entry_points.txt reading manifest file 'SimpleWiki.egg-info/SOURCES.txt' writing manifest file 'SimpleWiki.egg-info/SOURCES.txt' running build_ext Creating /home/jhammel/stage/lib/python2.6/site-packages/SimpleWiki.egg-link (link to .) Adding SimpleWiki 0.0 to easy-install.pth file Installed /home/jhammel/stage/src/SimpleWiki Processing dependencies for SimpleWiki==0.0 Searching for Genshi==0.6 Best match: Genshi 0.6 Processing Genshi-0.6-py2.6.egg Genshi 0.6 is already the active version in easy-install.pth Using /home/jhammel/stage/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg Searching for PasteScript==1.7.3 Best match: PasteScript 1.7.3 Processing PasteScript-1.7.3-py2.6.egg PasteScript 1.7.3 is already the active version in easy-install.pth Installing paster script to /home/jhammel/stage/bin Installing paster script to /home/jhammel/stage/bin Using /home/jhammel/stage/lib/python2.6/site-packages/PasteScript-1.7.3-py2.6.egg Searching for Paste==1.7.3.1 Best match: Paste 1.7.3.1 Processing Paste-1.7.3.1-py2.6.egg Paste 1.7.3.1 is already the active version in easy-install.pth Using /home/jhammel/stage/lib/python2.6/site-packages/Paste-1.7.3.1-py2.6.egg Searching for WebOb==0.9.8 Best match: WebOb 0.9.8 Processing WebOb-0.9.8-py2.6.egg WebOb 0.9.8 is already the active version in easy-install.pth Using /home/jhammel/stage/lib/python2.6/site-packages/WebOb-0.9.8-py2.6.egg Searching for PasteDeploy==1.3.3 Best match: PasteDeploy 1.3.3 Processing PasteDeploy-1.3.3-py2.6.egg PasteDeploy 1.3.3 is already the active version in easy-install.pth Using /home/jhammel/stage/lib/python2.6/site-packages/PasteDeploy-1.3.3-py2.6.egg Finished processing dependencies for SimpleWiki==0.0 (stage)>
${file('/home/jhammel/mozilla/web/craft/simplewiki-patches/renderer')}
changes to add renderer
diff format
${file('/home/jhammel/mozilla/web/craft/simplewiki-patches/index')}
changes to add index handler
${file('/home/jhammel/mozilla/web/craft/simplewiki-patches/post')}
changes to add POST handler
Add a FileApp handler for other content
${file('/home/jhammel/mozilla/web/craft/simplewiki-patches/fileserver')}
changes to add file server
${file('/home/jhammel/mozilla/web/craft/simplewiki-patches/edit')}
changes to add edit view
The code lives here: /hg/SimpleWiki
Defects:
Features:
The point is, its not hard to build an app this way. SimpleWiki is an example application an something to build on, not a comprehensive solution as-is.
See also cousin decoupage
In order to make a meaningful web service, it must be independent of other web services
Note that each of the handlers is a distinct web service. They don't talk to each other and have standalone functionality.
How you would do this in pylons:
How we could improve this in SimpleWiki: