CherryPy

CherryPy version 3.2 is the first major web application server to be made available on the Python 3 platform. It can be downloaded from http://cherrypy.org/. It is not a full-stack framework like the very popular Django, TurboGears, or Zope libraries. These frameworks provide extra support for data storage, templating, authentication, and other common web operations. Such features are not impossible in CherryPy, you're just responsible for finding or implementing them yourself.

CherryPy is a very powerful web server that uses a simple design for building web applications. Let's jump in head-first with a simple example that serves the HTML file we developed in the previous section:

	import cherrypy

	class SimplePage:
		@cherrypy.expose
		def index(self):
			with open("html_document.html") as file:
				return file.read()
		
	cherrypy.quickstart(SimplePage())

If we run this program, we can visit http://localhost:8080/ in a web browser to see the web page in action. All we've done here is create a class to pass to the quickstart function. That function starts a web server and serves pages from that class. Any methods of the class we created that have been marked as exposed will be made available via HTTP at a URL with the same name as the method. Any method not explicitly marked as exposed can be used internally as a helper method, but cannot be accessed at any URL.

The method itself simply opens a file and returns the contents of that file. Ultimately, we've written a web application that serves a single HTML 5 web page.

Of course, having a site that serves only one web page is pretty boring. Let's look at an example that is just a touch more exciting:

	import cherrypy

	template = """<!DOCTYPE html>
	<html>
		<body>
			{message}
		</body>
	</html>"""
	class AboutPage:
		@cherrypy.expose
		def index(self):
			return template.format(message="""
			I'm not a very interesting person...""")
			
	@cherrypy.expose
	def contactPage(self):
		print(self)
		return template.format(
				message="I can't be contacted anywhere.")
	
	class MainPage:
		about = AboutPage()
		contact = contactPage
		@cherrypy.expose
		def index(self):
			return template.format(message="""
			This is the main page.
			<a href="/about/">About Me</a>
			<a href="http:///contact/">Contact Me</a>
			<a href="/links/">Some Links</a>
			""")

		@cherrypy.expose
		def links(self):
			return template.format(
				message="No Links Yet")
				
	cherrypy.quickstart(MainPage())

This example shows three ways that pages can be added to a site. The obvious one is to add an exposed method, such as the links method above, to the class. But we can add exposed objects in other ways too:

  • By defining a separate function and including the attribute in the class definition as we did with contactPage
  • By defining a separate class and including an instance of it in the class definition, as we did with aboutPage
  • By adding the exposed method to the object after the class has been instantiated using code such as app.some_page = AnExposedClass()

You've probably figured out already that the index method is a special method. It doesn't map to the /index URL; instead, it is the method called if no path is added after the ending slash.

We can also accept HTML form arguments. Let's create a real contact page:

	import cherrypy
	
	class ContactPage:
		@cherrypy.expose
		def index(self, message=None):
			if message:
				print("The user submitted:
{0}".format(
					message))
				return "Thank you!"
			return """<form>
				<textarea name="message"></textarea>
				<input type="submit" />
				</form>"""
			
	cherrypy.quickstart(ContactPage())

This page displays a different result depending on the presence of a message variable in the keyword arguments. If no such argument is supplied, the visitor is presented with a form to enter a message in. If the argument is supplied, the value of the message is printed to the console (normally we'd do something useful with the value, such as e-mailing it somewhere or storing it in a database or file for later retrieval). Then a thank you message is returned to the client.

So how did that message parameter get set? Basically, any named inputs in a form (in this case, the message textarea) are mapped to keyword arguments when the page is submitted. It's that simple!

A full web stack?

As we discussed, CherryPy is just a web application server; it is not a web framework. It provides a complete web server and the basic features to map HTTP requests to code that must be executed when those requests are made. It also provides, with a bit of configuration, complete SSL support, the ability to set and retrieve cookies, caching support, HTTP authentication, and sessions. However, it is missing two key features that many other frameworks supply: templating and data storage.

Many websites use databases for data storage, but CherryPy does not supply this ability. Do we really need it to? We really just need database connectivity; it doesn't have to be built into the web framework. Indeed, why don't we just use SQLAlchemy, which we discussed earlier in the chapter? In fact, this is what the TurboGears framework uses for its database access.

This then, still leaves us to solve the templating problem, another framework feature that CherryPy lacks. Templating is the process of taking static strings or files, and replacing certain substrings in those files with new strings, based on some kind of context. The str.format function we covered in Chapter 10 is a basic example of templating. It allows us to replace modifiers with variables passed into the function. Indeed, this was the template method we used in the example of a simple CherryPy application earlier.

Most template languages go beyond this ability to allow things like conditionals (including data in the template only if a certain condition is met, such as two variables being equal, or a user being logged in), and looping (including data in a template repeatedly, such as creating a table or unordered list containing multiple items from a Python list). Some even go so far as to allow arbitrary Python code to be executed within the template.

There are a myriad opinions on what a template language should be, which is why, for Python 2, there have been an immeasurable number of different template languages devised. This diversity hasn't spread to Python 3 yet, but one of the most powerful templating languages, Jinja2 is already available on the Python 3 platform. It can be downloaded from http://jinja.pocoo.org/.

As a sort of case study, let's take these three tools―CherryPy, SQLAlchemy, and Jinja―and create a quick and dirty blogging engine! We'll start with the SQLAlchemy models; these define the data that will be stored in the database:

	import datetime
	import sqlalchemy as sqa
	from sqlalchemy.ext.declarative import declarative_base
	
	Base = declarative_base()
	
	class Article(Base):
		__tablename__ = "article"
		rowid = sqa.Column(sqa.Integer, primary_key=True)
		title = sqa.Column(sqa.String)
		message = sqa.Column(sqa.String)
		pub_date = sqa.Column(sqa.DateTime)
		
		def __init__(self, title, message):
			self.title = title
			self.message = message
			self.pub_date=datetime.datetime.now()

	class Comment(Base):
		__tablename__ = "comment"
		rowid = sqa.Column(sqa.Integer, primary_key=True)
		article_id = sqa.Column(sqa.Integer,
				sqa.ForeignKey('article.rowid'))
		article = sqa.orm.relationship(Article, backref="comments")
		name = sqa.Column(sqa.String)
		message = sqa.Column(sqa.String)
	
		def __init__(self, article_id, name, message):
			self.article_id = article_id
			self.name = name
			self.message = message
		
	engine = sqa.create_engine('sqlite:///blog.db')
	Base.metadata.create_all(engine)
	Session = sqa.orm.sessionmaker(bind=engine)

We create two models with some fields. The two models are associated with a ForeignKey relationship on the Comment class.

The rowid field is a special one; in SQLite, every model is automatically given a unique integer rowid. We don't have to do anything to populate this number, it's simply available from the database. This wouldn't work with PostgreSQL or another engine; we'd have to set up a sequence or autoincrement field instead.

We add an __init__ method to each class to make it easier to construct new instances. Then we associate the engine, create the tables, and create a Session class to interact with the database later.

Jinja Templating

Now, we can set up Jinja to serve some templates from a folder for us:

	import jinja2
	templates = jinja2.Environment(loader=jinja2.FileSystemLoader(
		'blog_templates'))

Well that was easy. This gives us a templates variable that we can use to load templates based on filename from the given folder. Before we create the CherryPy app server, let's have a look at the templates. Let's scrutinize the simple template for adding a blog article first:


	{% extends "base.html" %}
	
	{% block title %}New Entry{% endblock %}
	{% block content %}
	<form method="POST" action="/process_add/">
		Title: <input name="title" type="text" size="40" /><br />
		<textarea name="message" rows="10" cols="40">
		</textarea><br />
		<input type="submit" value="Publish" />
	</form>
	{% endblock %}

This sort of resembles normal HTML, but all those {% things are new. That's Jinja markup (it's also very similar to Django markup, if you have used or are interested in using Django's template system) for a template tag. Template tags are instructions to the templating system to do something special here. There are two types of template tags in use: extends, and block. The extends tag essentially tells the template system to start with the base.html, but replace any named blocks with the named blocks in this file. And that's what the block and endblock tags are; named blocks to override whatever is specified in the parent template, base.html. This may be a bit clearer if we know what base.html looks like:

	<!DOCTYPE html>
		<html>
			<head><title>{% block title %}{% endblock %}</title></head>
			<body>
				<h1>My Blog</h1>
				<ul>
					<li><a href="http:///>Main</a></li>
					<li><a href="/add/">Add Entry</a></li>
				</ul>
				<hr />
				{% block content %}
				{% endblock %}
			<body>
		<html>

This looks even more like a normal HTML page; it shows where the two named blocks should go in the context of a larger page.

Extending base.html in other templates allows us to ignore the parts of every page that stay the same. Further, if we want to add a link to the menu or otherwise modify the overall site, we only have to do it in this single template file.

The other template, index.html is substantially more complex:


	{% extends "base.html" %}

	{% block title %}My Blog{% endblock %}
	{% block content %}
		{% for article in articles %}
			<h2>{{article.title}}</h2>
			<em>{{article.pub_date.strftime('%b %d %Y')}}</em>
			<p>{{article.message}}</p>
			<div style="margin-left: 6em">
				<h3>Comments</h3>
				{% for comment in article.comments %}
					<em>{{comment.name}} wrote:</em>
					<p>
					{{comment.message}}
					</p>
				{% endfor %}
				{% include "comment_form.html" %}
			</div>
			<hr />
		{% endfor %}
	{% endblock %}

It includes the same extends and block tags as the earlier template. In addition, it introduces us to the for template tag, which loops over all the articles (or all the comments in an article) and renders slightly different HTML for each of them. It also renders a bunch of variables using the {{<variable_name>}} syntax. The variable names are passed into the template from our CherryPy application or are assigned within the context, as is done inside the for loops.

The rendering of the pub_date variable on the article is particularly interesting, as the item is a datetime.datetime object, and we can see that Jinja allows us to call the strftime method on this object directly.

Finally, the include tag allows us to render part of the template in a separate file, comment_form.html, which looks like this:

			<form method="POST"
					action="/process_comment/{{article.rowid}}/">
				Name: <input name="name" type="text" size="30" /><br />
				<textarea name="message" rows="5" cols="30">
				</textarea><br />
				<input type="submit" value="Comment" />
			</form>

That's basic Jinja syntax in a nutshell; there's a lot more that can be done with it, of course, but these basics are enough to get you interested. They're also enough for our simple blog engine!

The CherryPy blog web application

In the interest of understanding how web applications are designed, note that I didn't write those templates before I wrote the CherryPy application we're about to see. Instead, I developed iteratively, creating both the code and templates to add an article, followed by the code and templates to display an article, and finally, setting up the comment system. I grouped all the resulting templates together in the above section so we could focus on Jinja template syntax. Now, let's focus on CherryPy and how those templates are called!

First, here's our blog engine with the index method:

	import cherrypy
	class Blog:
		@cherrypy.expose
		def index(self):
			session = Session()
			articles = session.query(Article).all()
			template = templates.get_template("index.html")
			content = template.render(articles=articles)
			session.close()
			return content
	cherrypy.quickstart(Blog())

Here's where we start to see our three puzzle pieces merging together. CherryPy, of course, is serving the page. Jinja is creating the page using our templates. And SQLAlchemy is giving Jinja the data it needs to display

First we construct a session and use it to search for all the available articles. We then get a template by name; this comes from the templates object we set up earlier in the module. Then we render the template, passing one keyword argument into it. Keyword arguments map to variables inside the template context; the template we defined earlier will loop over the articles we passed in this function. Then we return the rendered content to let CherryPy display it.

The code to display the form for adding a new article is even simpler; we just render the template, since it doesn't need any variables:

		@cherrypy.expose
		def add(self):
			template = templates.get_template("add.html")
			return template.render()

You may have noticed in our templates that the forms for adding articles and comments have action attributes pointing to process_add and process_comment URLs. The process_add URL simply constructs a new article from the form parameters (title and name), which come to us from CherryPy as keyword arguments. Then it raises an exception to redirect the client to the main view, which will display the new article:

		@cherrypy.expose
		def process_add(self, title=None, message=None):
			session = Session()
			article = Article(title, message)
			session.add(article)
			session.commit()
			session.close()
			raise cherrypy.HTTPRedirect("/")

The process_comment method is much the same, except it also accepts a positional argument. Positional arguments come between forward slash characters in the URL, so the following method signature would actually map to /process_comment/3/ if an article_id of 3 is passed:

		@cherrypy.expose
		def process_comment(self, article_id, name=None,
				message=None):
			session = Session()
			comment = Comment(article_id, name, message)
			session.add(comment)
			session.commit()
			session.close()
			raise cherrypy.HTTPRedirect("/")

And there we have it, a complete, simple blog engine with absolutely no authentication and which will fill up with spam in a matter of minutes. But it works! And we wrote it all using Python 3 objects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.152.136