0% found this document useful (0 votes)
563 views

Python For Programmers - A Project-Based Tutorial

This document provides an introduction and overview of the Python for Programmers tutorial being presented at PyCon 2013. It begins with introducing the presenters, Sandy Strong and Katharine Jarmul, and the intended audience of computer programmers with some programming experience. It then gives a brief history of Python and why it was chosen for the tutorial. The rest of the document provides an overview of the topics that will be covered, including Python libraries like CherryPy and Jinja2, data types like integers, strings, lists, tuples and dictionaries, control structures like for and while loops, and boolean logic. The goal is to quickly tour Python fundamentals and complete an entire web app project in the 3 hour tutorial.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
563 views

Python For Programmers - A Project-Based Tutorial

This document provides an introduction and overview of the Python for Programmers tutorial being presented at PyCon 2013. It begins with introducing the presenters, Sandy Strong and Katharine Jarmul, and the intended audience of computer programmers with some programming experience. It then gives a brief history of Python and why it was chosen for the tutorial. The rest of the document provides an overview of the topics that will be covered, including Python libraries like CherryPy and Jinja2, data types like integers, strings, lists, tuples and dictionaries, control structures like for and while loops, and boolean logic. The goal is to quickly tour Python fundamentals and complete an entire web app project in the 3 hour tutorial.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 131

Python for Programmers:

A Project-Based Tutorial
PyCon 2013

Sandy Strong & Katharine Jarmul


Please visit this page and follow the
instructions:

https://us.pycon.org/2013/community/tutorials/5/
Introduction to Workshop
Who are we?

Alexandra (Sandy) Strong (@sandymahalo):


Systems Engineer at Dreamhost, Python
Education, PyLadies

Katharine Jarmul (@kjam):


Director of Technology at HYFN, Pythonista,
PyLadies
Who are you?
Py-curious computer programmers, with
beginning to intermediate level of experience in
your language of choice, and a rudimentary
understanding of your operating system's
command line.

If you have never programmed before, this


tutorial is probably not for you.

We will be moving through material extremely


quickly in order to complete an entire web app
in 3 hours.
View the tutorial slides here:

http://bit.ly/YXKWic

I recommend downloading and


saving the slides as a PDF now!
A little Python history
● Created by Guido van Rossum in late 80s
○ “Benevolent Dictator for Life”
○ Now works at Google

● Name is based off Monty Python


Why Python?
● Simple to get started, easy for beginners, powerful
enough for professionals

● Code is clean, modular, and elegant, making it easy to


read and edit

● Whitespace enforcement

● Extensive standard library, many modules to import


from

● Cross platform - Windows, Mac, Linux

● Supportive, large, and helpful community


Who is using Python?
● Google
● Reddit
● Twitter
● Pinterest/Instragram
● DreamHost
● YouTube
● BitTorrent
● DropBox
● ...and countless more!
Why a project?
Getting your hands dirty is the best way to
develop a new skill!

Your efforts in this tutorial will produce a


meaningful project, and give you something to
show for your work.
Questions so far?
Introduction to Project and
a Quick Tour of Python
Libraries: CherryPy and Jinja2
Why did we choose these?

● CherryPy:
○ Lightweight and easy to use microframework
○ Has direct interface with WSGI
○ It's fun to learn new things and we know you've all
used django or flask ;)

● Jinja2
○ Commonly used python templating language
○ Easy to grok syntax
○ Integrates with many python libraries
Initial environment setup
(you should already have this done)
pip install ipython
pip install jinja2
pip install cherrypy
There are a few important additions!!

We're going to use memcache as our storage


backend for this app. This requires that we install
memcached, and a Python library called pylibmc:

pip install pylibmc

Linux:
apt-get install libmemcached-dev
apt-get install memcached
Mac:
brew install memcached
Important note to Mac users:
If you receive an error referencing a failure in
the llvm-gcc-4.2 library when attempting to
install pylibmc, please try the following:

brew link libevent


brew install libmemcached
sudo pip install pylibmc
Raise your hand if you were not able
to install memcached and/or
pylibmc successfully!
IPython: Initial debugging
my-computer:~ sandy$ ipython

In [1]: import urllib4


--------------------------------------------------------
ImportError Traceback (most recent call last)
<ipython-input-1-87716ee6ccca> in <module>()
----> 1 import urllib4

ImportError: No module named urllib4


IPython
● ? operator

● %who and %whos

● %hist

● %pdb

● More details: http://pages.physics.cornell.


edu/~myers/teaching/ComputationalMethods/python/ipy
So what is IPython, anyway?
It's a gussied up version of the standard Python
interpreter.

Python is an interpreted language. This


means that the interpreter executes the
program source code directly, statement by
statement.

In comparison, compiled languages (like C)


must be explicitly translated into a lower-level
machine language executable.
Brief Intro to Python Data Types
● Integers
● Strings
● Lists
● Tuples
● Dictionaries
Integers and basic math
In [6]: 2 + 2
Out[6]: 4

In [7]: 3 * 3
Out[7]: 9

In [8]: 2**3
Out[8]: 8

In [9]: 4/2
Out[9]: 2

In [11]: 2%4
Strings
In [1]: my_name = "Sandy Strong"
In [2]: print my_name
Sandy Strong

Now type, "my_name." and hit the tab key:

In [3]: my_name.
my_name.capitalize my_name.center
my_name.count my_name.decode
my_name.encode <snip>
Useful string methods
● strip
○ strips leading/trailing whitespace
● upper
○ makes all characters upper case
● lower
○ makes all characters lower case
● split
○ splits string into a list, whitespace delimited
● find
○ search for a string within your string
● startswith
○ test for what your string starts with
String formatting
You can pass variables into strings to format
them in a specific way, for example:

In [14]: age = 28
In [15]: name = 'Sandy'

In [16]: print "Hello, my name is %s." % name


Hello, my name is Sandy.

In [17]: print "Hello, my name is %s, and I'm


%s years old." % (name, age)
Hello, my name is Sandy, and I'm 28 years
old.
Lists
[2]: items = ['bacon', 3.14, ['bread',
'milk'] ]

In [3]: print items


['bacon', 3.14, ['bread', 'milk']]

You can put lists (and other Python data types)


inside of lists.
Useful list methods
● insert
○ provide index position and item to be inserted into
the list
● append
○ append an item to the end of your list
● pop
○ provide index position to "pop" an item out of your
list
Tuples
In [12]: colors = ('red', 'blue', 'green')

In [13]: print colors


('red', 'blue', 'green')

Tuples are immutable. Once they're created,


they cannot be changed. Because they are
immutable, they are hashable.
What does "hashble" mean?
An object is hashable if it has a hash value
which never changes during its lifetime.

If we run a hashing algorithm on a tuple, it will


return to us the hash value for that object. If we
ran that same algorithm again on the same
tuple, it would be identical-- because a tuple is
immutable (it's values cannot change after it is
defined).
Useful tuple methods
● index
○ provide an item in your tuple, and it returns the index
position for that item
Dictionaries
In [1]: favorite_sports = {'John':
'Football', 'Sally': 'Soccer'}

In [2]: print favorite_sports


{'John': 'Football', 'Sally': 'Soccer'}

The first parameter is the "key" and the second


parameter is the "value". A dictionary can have
as many key/value pairs as you wish, and keys
are immutable.
Useful dictionary methods
● get
○ retrieves the value of the given key or returns None
if the key doesn't exist in the dictionary
● values
○ all values in your dictionary
● keys
○ all keys in your dictionary
● items
○ list of 2-element tuples that correspond to the
key/value pairs in your dictionary
● update
○ add a new key/value pair to your dictionary
Wait, what is None?
It's a Python built-in constant that is frequently
used to represent the absence of a value.

None is Python's version of NULL.

In [1]: None == None


Out[1]: True
Incrementing and Decrementing Values

# increment
counter += 1

# decrement
counter -= 1
Introduction to Python Loops, Code
Blocks, and Boolean Logic

● for
● while

BUT before we can jump into loops....


... we have to talk about whitespace
awareness
Python is a "whitespace aware" programming
language.

This simply means that, within a code block,


Python expects uniform indentation, the
standard is 4 spaces.

What defines a code block? Let's try out some


loops and find out...
For loops: "for each item in x, do y"
In [1]: items = ['socks', 'shoes', 'shirt',
'pants']

In [2]: for x in items:


...: print x
...:
socks
shoes
shirt
pants

The green highlighted code represents a code block. See


how the line "print x" is indented in 4 spaces from the line
"for x in items:"? IPython does this for you, when coding
What happens if I don't indent?
In [1]: items = ['socks', 'shoes', 'shirt', 'pants']

In [2]: for x in items:


...: print x
...:
IndentationError: expected an indented block

I highly recommend this article explaining


whitespace further:

http://www.secnetix.de/olli/Python/block_indentation.hawk

Now that that's out of the way, let's move on!


Enough boolean logic for this tutorial:
In [3]: True == True
Out[3]: True

In [4]: False == True


Out[4]: False

In [5]: False == False


Out[5]: True

In [6]: not False == True


Out[6]: True

In [7]: not True == False


Out[7]: True

For more practice with booleans in Python, I highly recommend the Boolean
Practice chapter from Zed Shaw's Learn Python the Hard Way.
While loops: "while x is true, do y"
In [4]: counter = 3

In [5]: while counter >= 0:


...: print counter
...: # decrement the counter
...: counter -= 1
...:
3
2
1
0

This while-loop will continue to execute until the condition


"counter >= 0" no longer evaluates to True.
Watch out for infinite loops!
In programming, an infinite loop is a loop of
code which your program enters, but never
returns from.

While-loops can be tricky, because they


continue running while a condition remains
True. The slightest logic error can lead to an
infinite while-loop.

For-loops are less prone to infinity because


they operate on iterable objects (lists, strings,
dictionaries), which are finite by nature.
Basic Control Flow Tools
These are the keywords you need to know:

● if
● else
● elif

In Python we use the above keywords to control


flows within our program.

Depending on which condition is met (evaluates to


True) within a block of control flow code, we may
Comparison operators
Equal to: ==
Less than: <
Greater than: >
Less than or equal to: <=
Greater than or equal to: >=

Let's try it out...


if and else
In [70]: age = 20
In [71]: if age <= 20:
....: print "You're old enough!"
....:
You're old enough!

In [72]: if age > 20:


....: print "You're too old!"
....: else:
....: print "You're the right age."
....:
You're the right age.
Using elif
The elif keyword allows you to expand your
control flow block, and perform additional
comparisons within an if-else statement.

In [74]: if age < 20:


....: print "You're too young."
....: elif age >= 20:
....: print "You're the right age!"
....:
You're the right age!
List comprehensions: magical!

In [75]: user_pets = {'Sandy': 'Dog',


'Katharine': 'Snake', 'Bob': 'Mouse',
'Sally': 'Fish'}

In [76]: pet_list = [ v for k,v in


user_pets.iteritems()]

In [77]: print pet_list


Out[77]: ['Snake', 'Mouse', 'Fish', 'Dog']

Python programmers pride themselves on writing tight,


legible, efficient code. List comprehensions are a great
example of that.
Using control flow and conditionals
in list comprehensions
In [77]: user_pets = {'Sandy': 'Dog',
'Katharine': 'Snake', 'Bob': 'Mouse',
'Sally': 'Fish'}

In [78]: pet_list = [ v for k,v in


user_pets.iteritems() if k != 'Bob']

In [79]: print pet_list


['Snake', 'Fish', 'Dog']

We will only add the pet to pet_list if the


user is not Bob.
Writing Python functions

In [6]: def hello_world(user='John'):


...: """ This is a docstring """
...: message = "Hello, %s!" % user
...: return message
...:

In [7]: my_message = hello_world(user='Sandy')

In [8]: print my_message


Hello, Sandy!
*args and **kwargs
In addition to single or comma-separated
arguments, you can also pass lists or
dictionaries of parameters into Python functions

def function(*args):
# Expects something like:
# ['he', 'she', 'it']

def function(**kwargs):
# Expects something like:
#{'me': 'you', 'we': they'}

We'll revisit this later when we build our app!


A word about import statements
Python has an extensive standard library, filled
with very useful modules that will prevent you
from having to write boilerplate code.

# import the random library


In [60]: import random

# Print a random intiger between 1 and 5:


In [61]: print random.randint(1,5)
4

Put import statements at the top of your file.


Intro to Python classes
# import the imaginary 'cars' module
import cars

class MyCar:
''' This class defines a car object '''
# member variables go here
make = "Honda"
model = "Civic"

# class methods go here


def car_color(self, clr="black"):
self.color = cars.paint(clr)
return True

def tire_type(self, type="standard"):


self.tires = cars.tire(type)
return True
Wait-- what exactly is a class in Python?

In object-oriented programming, a class is a construct that


is used to create instances of itself, called class objects.

A class defines constituent members which enable its


instances to have state and behavior. Data field members
(member variables) enable a class instance to maintain
state.

Other kinds of members, especially methods, enable the


behavior of class instances.

http://en.wikipedia.org/wiki/Class_(computer_programming)
Ok, so how do I use the MyCar class?
>>> import MyCar
>>> car = MyCar()
>>> print car.make
Honda
>>> print car.model
Civic
>>> car.car_color()
>>> print car.color
black
>>> car.tire_type(type="sport")
>>> print car.tires
sport
Be aware of the __init__ method
There's a special method, named __init__ , that is
implicitly defined when you create a new class. By default,
__init__ is empty and does nothing special.

In some cases, you will want to override this method to


have it do something in particular, you can do that by
simply defining it at the top of your class:

class MyCar:
def __init__():
# do stuff here!
Methods vs. Functions
In Python, what we call a "method" is actually a
member function of a class.

A standalone function that belongs to no class,


is simply referred to as a "function".
Web Coding in Python
(it's the best, we promise!)
Intro to MVC (Model View Controller)
This is a design pattern followed by most modern web
frameworks.

Models: Models represent knowledge-- they can can represent a single


object, or a structure of objects.

Views: A view is a visual representation of its model. A view typically


highlights certain attributes of the model, and suppresses others-- it is a
presentation filter.

Controllers: A controller is the link between a user and the system. It


provides the user with input by arranging for relevant views to present
themselves in appropriate places on the screen, and provides means
for user output by presenting the user with menus or other means of
giving commands and data.

http://www.codinghorror.com/blog/2008/05/understanding-model-view-controller.html
Real world example
Model: User
View: Profile page
Controller: "Change password" functionality

● user is represented by a model


● specific user model data is presented by a
view on the profile page for that user
● functionality allowing a user to change his
password-- interact with the model-- is
handled by a controller
What is WSGI (whiskey)?
The Web Server Gateway Interface defines a
simple and universal interface between web
servers and web applications or frameworks for
the Python programming language.

WSGI is what lets your Python web application


talk to web servers like Apache and Nginx.
ORM - Object Relational Mapper
ORM's are libraries that allow your application
code to interact with objects in the datastore.

CherryPy doesn't have a native ORM, so we


will be writing our own simple ORM that will
allow us to perform CRUD (Create, Read,
Update, and Delete) operations.

We will be using memcache, a volatile in-


memory datastore, as our backend.
Are there any questions before we
start building our project?
CherryPy Setup
You should already have all libraries installed
and working on your computer.

We will be creating a Poll web application. You


will be able to create and edit polls, add and
edit choices, and vote on the poll.

The first step is to create a directory on your


computer named my-cherrypy-poll:

mkdir my-cherrypy-poll
Grab the app code from Github, you'll
need it later:
If you have git installed:

git clone git://github.com/kjam/cherrypy-poll.git

If you do not have git installed, please review the


instructions from our tutorial wiki:

https://us.pycon.org/2013/community/tutorials/5/
db_tools.py: Our ORM
import pylibmc

def get_mc():
''' Get a memcache connection object '''
mc = pylibmc.Client(['127.0.0.1:11211'])
return mc

def get_value(key):
''' Retrieve value for the given key '''
mc = get_mc()
val = mc.get(key)
# delete the mc connection object
del mc
return val
db_tools.py (continued)
def set_value(key,value):
''' Set a given key/value pair in mc '''
mc = get_mc()
mc.set(key,value)
del mc
return True

def del_value(key):
''' Delete a key/value pair from mc '''
mc = get_mc()
mc.delete(key)
del mc
return True
What do we have so far?
We've got a simple ORM module, composed of
functions that allow us to:

● connect to our locally-running memcache instance


● retrieve values from memcache based on their key
● set key/value pairs in memcache
● delete key/value pairs from memcache

We leveraged methods from pylibmc to build our


ORM. This library allows Python to easily talk to
memcache.
Note our use of pylibmc functions:
def get_value(key):
# get value from mc using supplied key
val = mc.get(key)

def set_value(key,value):
# set supplied key/value into mc
mc.set(key,value)

def del_value(key):
# del key/val from mc using supplied key
mc.delete(key)
Questions so far?
utils.py: Our "helper function"
import re
from unicodedata import normalize

_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')

def slugify(text, delim=u'-'):


"""Generates an ASCII-only slug."""
result = []
for word in _punct_re.split(text.lower()):
word = normalize('NFKD', word).encode('ascii', 'ignore')
if word:
result.append(word)
return str(delim.join(result))

Notice our usage of string functions (lower, split)!


What is "slugify"?
Suppose a user created a poll named:

"My favorite food is oranges, what is yours? ◕‿◕"

This slugify function converts this poll name to:

my-favorite-food-is-oranges-what-is-yours

Why? Prettier URLs, more SEO-friendly, handy


unique identifier for the record in the datastore.
Notes on the slugify function
The variable _punct_re:

Python does not formally enforce


public/private variables or functions.

An underscore preceding an object name


indicates that the programmer should
respect this object and treat it as private.
Normalizing unicode
Unicode is great-- it represents a much larger character set than
ASCII. However, it can be challenging to work with.

For the purpose of storing slugs in our datastore, it's simpler if


we normalize our slug string to only ASCII characters.

This code normalizes the string, replacing unicode characters


with their ASCII equivalent, and then encodes the string in
ASCII format, ignoring any errors raised during the encoding
process:

normalize('NFKD', word).encode('ascii', 'ignore')

For a production web application, you would want to use a more


robust method for slugifying your Poll names.
How might we organize our data?
Open up initial_json.py:

poll_dict = { 'my-question-slug': {
'question': 'My question is le awesome, no?',
'slug':'my-question-is-le-awesome-no',
'choices': [
{
'id':1,
'text':'yep',
'value':0,
}, { 'id':2,
'text':'nerp',
'value':0,
},
'publish': True,
}
models.py
The models.py file in a Python application is
often used to define class types and create and
inherit model instances.

In our application, we are essentially using the


theory of this to help manipulate data within our
memcache datastore.

We will use our models to edit and create "poll"


instances which we update in our memcache
storage.
We grab the poll from
def add_poll(**kwargs):
kwargs, and then if
choices_arr = [] choices are present,
count = 1 we build out a
poll_dict = {} choice_dict of the
choices associated
poll_dict['question'] = kwargs.get('question') with that poll.
for k,v in kwargs.items():
if 'choice' not in k: continue The poll question gets
slugified, and added to
choice_dict = {
the poll_dict, and
'id': count, then a list containing
'text': v, the choices dictionary
is added. to
'value': 0
poll_dict.
}
choices_arr.append(choice_dict) We call set_value
count += 1 and pass in the slug
(unique key for the
slug = slugify(kwargs.get('question')) poll), and the contents
poll_dict['slug'] = slug of poll_dict.
poll_dict['choices'] = choices_arr
Finally, if kwargs
set_value(slug,poll_dict)
contains a key named
if kwargs.get('publish'): "publish" we publish
publish_poll(slug) the poll.
Now for edit_poll...
This is a big one!
def edit_poll(**kwargs):
choices_arr = []
poll = get_poll(str(kwargs.get('slug')))
poll_dict = {}
poll_dict['question'] = kwargs.get('question')
for k,v in kwargs.items():
if 'choice' not in k: continue
this_choice = [
c for c in poll.get('choices')
if int(k.strip('choice')) == c.get('id')
]
if not len(this_choice):
return False
else:
this_choice = this_choice[0]
choice_dict = {
'id': this_choice.get('id'),
'text': v,
'value': this_choice.get('value'),
}
choices_arr.append(choice_dict)
edit_poll (continued)
slug = str(kwargs.get('slug'))
poll_dict['slug'] = slug
poll_dict['choices'] = choices_arr
set_value(slug,poll_dict)
if kwargs.get('publish'):
publish_poll(slug)
else:
unpublish_poll(slug)
return poll_dict

The edit_poll function and add_poll functions have some similarities.


They start to differ with the list comprehension used to assign this_choice.

We build out this_choice using a conditional to validate that the id for the
choice passed in is equivalent to the choice id on the poll object from
memcache.
From there, we build out the choice_dict , get slug from **kwargs , add
slug and choices to the poll_dict , and set the value in memcache.
What did we just do?

We created a models file with two functions that


allow us to: create and edit poll objects, as well as
publish them if they are publishable (according to
the editor).

Remember, poll objects will be stored in


memcache.

We used our ORM functions from db_tools.py,


and our slugify function from utils.py to help
with this.
Any questions about slugs?

We're about to dive into views.

There are a lot of views, and I'll explain each as


we go along.
views.py: Presenting our model data
from db_tools import get_value, set_value, del_value
from utils import slugify

def cast_vote(poll_key,choice):
poll = get_value(poll_key)
for c in poll['choices']:
if c['id'] == int(choice):
c['value'] += 1
set_value(poll_key,poll)
return poll

This is the view that allows visitors to vote on a poll. When


executed, it accepts the poll_key (its unique identifier),
and the choice that the user selected.

The poll is retrieved from memcached, validated, and it's


views.py (continued)

def get_global_links(): This is essentially our


return [{ site navigation. We
'name': 'Add a poll', return a list of
'url': '/polls/add', dictionaries, each one
}, containing a pretty
{ name for the view,
'name': 'Home', along with the url path
'url': '/' where the view is
}, located within our
{ application.
'name': 'Poll list',
'url': '/polls', The view to "Add a
}] poll" is mapped to the
"/polls/add" url.
views.py (continued)
def get_poll(key):
poll = get_value(key)
return poll

Retrieve a specific poll from memcache, based


on the key-- the slug-- for the poll.
views.py (continued)
def get_polls():
poll_list = []
published_polls = get_value('published_polls')
if not published_polls:
set_value('published_polls',[])
for p in published_polls:
poll = get_value(p)
poll_list.append(poll)
return poll_list

Return the value from memcache with the key


'published_polls', loop through each poll id (slug), and
retrieve the poll object for that id. Build a list of poll objects, and
return them.
views.py (continued)
def publish_poll(key):
published_polls = get_value('published_polls')
if not published_polls:
set_value('published_polls',[])
if key not in published_polls:
published_polls.append(key)
set_value('published_polls',published_polls)

For a given poll id (key), add it to the list of


published_polls in memcache if it is not already there.

Notice how we're using if-statements to perform validation


on published_polls and the individual key we're
attempting to add to the published_polls list.
views.py (continued)
def unpublish_polls(key):
published_polls = get_value('published_polls')
if not published_polls:
set_value('published_polls',[])
if key in published_polls:
published_polls.remove(key)
set_value('published_polls',published_polls)

For a given poll id (key), remove it from list of


published_polls in memcache, if it is currently
published.

Note again how we perform validation.


Whew! That was a lot of coding...

So, who has questions?

...I know someone must have a question!

... c'mon...
app.conf: Important app instructions
These configuration settings tell our app how and
where we want it to run its server at.

[global]
server.socket_host = "127.0.0.1"
server.socket_port = 8888
server.thread_pool = 10

When our app is complete and we fire it up, we'll


be running on http://127.0.0.1:8888 with
10 worker threads.
app.py: Our main application code
This is another big file. Let's start with imports
and environment setup, these go at the top:

import cherrypy
from jinja2 import Environment,FileSystemLoader
from db_tools import get_value,set_value,del_value
from views import get_global_links,get_polls,get_poll,\
add_poll,edit_poll,cast_vote
import os.path

conf = os.path.join(os.path.dirname(__file__), 'app.conf')


env = Environment(loader=FileSystemLoader('templates'))

env tells our app where to find templates at, and conf tells
our app where to find the app.conf file.
app.py class 1: the PollViews class
class PollViews:

@cherrypy.expose
def index(self):
data_dict = {
'title': 'Poll List',
'links': get_global_links(),
'polls': get_polls(),
}
templ = env.get_template('polls.html')
return templ.render(data_dict)

First, we define a class named PollViews. Then we write our index function,
the default page for our app.

There are some interesting things here, @cherrypy.expose, env.


What are these things??
@cherrypy.expose
The "@" indicates that this is a decorator. Decorators are commonly-used
"syntactic sugar" in Python. They allow you to execute the function that
@cherrypy.expose is decorating-- in this case, index--, to be passed into
the cherrypy.expose function, where additional work is done. This
decorator function is a standard part of the CherryPy microframework, and it
allows objects passed into index to be traversable by the URL mapping
routine.

env.get_template('polls.html'),
We get the polls.html template from our environment. Remember, we set
up env at the top of our app.py file?

templ.render(data_dict).
We pass our data dictionary-- the attributes of our poll model that will be
exposed in our view-- into the render method of our template object, templ.

You will observe these same patterns in other methods within


app.py class 1 cont: the poll method
@cherrypy.expose
def poll(self,key,choice=None):
method = cherrypy.request.method.upper()
poll = False
data_dict = {
'title': 'Poll',
'links': get_global_links(),
}
if method == 'POST':
data_dict['poll'] = cast_vote(key,choice)
data_dict['success'] = True
else:
data_dict['poll'] = get_poll(key)
templ = env.get_template('poll.html')
return templ.render(data_dict)

A little more CherryPy internal magic. Then, if the request is POST, we submit a
vote to cast_vote. otherwise, we retrieve the poll to display for the user,
app.py class 1 cont: the add method
@cherrypy.expose
def add(self,**kwargs):
method = cherrypy.request.method.upper()
poll = False
data_dict = {
'title': 'Add a poll',
'links': get_global_links(),
}
if method == 'POST':
add_poll(**kwargs)
data_dict['success'] = True
templ = env.get_template('add_poll.html')
return templ.render(data_dict)

This lets us add a poll to our datastore. If method is POST,


we pass in **kwargs to add_poll, then we render the
contents of our data_dict out to the add_poll.html
app.py: class 1 cont: the edit method
@cherrypy.expose
def edit(self,key,**kwargs):
method = cherrypy.request.method.upper()
If method is POST, we
poll = False pass **kwargs into
data_dict = { edit_poll, then we
'title': 'Edit your poll', add poll to our
data_dict.
'links': get_global_links(),
} Otherwise, we call
if method == 'POST': get_poll with the key
poll = edit_poll(**kwargs) passed in to edit, and
add that to our
data_dict['poll'] = poll data_dict.
data_dict['success'] = True
else: Finally, we render our
data_dict to
data_dict['poll'] = get_poll(key)
edit_poll.html.
templ = env.get_template('edit_poll.html')
return templ.render(data_dict)
app.py class 2: the PollApp class
class PollApp:
polls = PollViews()

@cherrypy.expose
def index(self):
data_dict = {
'title': 'Welcome to the poll application!',
'links': get_global_links(),
}
templ = env.get_template('index.html')
return templ.render(data_dict)

polls is a member variable of the PollApp class, and it is an instance of the


PollViews class.

The PollApp class has one method, index. This method has a simple
data_dict, which is rendered out to index.html.
app.py: The last bit! Let's make our app "go"!
This goes at the very bottom of app.py:

if __name__ == '__main__':
cherrypy.quickstart(PollApp(),
config=conf)
else:
cherrypy.tree.mount(PollApp(),
config=conf)

When we run our CherryPy app, Python sees this as the


"__main__" function.

Congratulations! You've completed writing your


App Homework!
● Raise an error if a user enters bad data into
the form
● Use the unpublish_polls function in the
application to properly unpublish polls
● Use CherryPy Sessions to validate that
each user can only vote once http://docs.cherrypy.
org/dev/refman/lib/sessions.html
● Allow users to add more than 4 choices to a
poll using a more dynamically generated
form
Don't forget the templates!
Within your my-cherrypy-poll directory,
create another directory named templates.

In the interest of time, we won't be typing out all


of the HTML, however, we will go over the
highlights and important template syntax.

Remember, we're using the jinja2 library to


drive templates in our app.
add_poll.html
<html>
<head>

</head>
<body>
<h1>{{title}}</h1>

<h2>Add a poll</h2>
<form action= "/polls/add" method='POST'>
Question: <input type="text" name="question" value=""><br>
Choice 1: <input type="text" name="choice1" value=""><br>
Choice 2: <input type="text" name="choice2" value=""><br>
Choice 3: <input type="text" name="choice3" value=""><br>
Choice 4: <input type="text" name="choice4" value=""><br>
Publish? <input type="checkbox" name="publish" value="checked"><br>
<input type="submit" value="Add poll">
</form>

{% for l in links %}
<span><a href=" {{l.url}}">{{l.name}}</a></span> |
{% endfor %}
Some things to note
We use pairs of double braces-- {{ }} -- to surround a
variable passed into the template from our view.

When calling a method that posts data to our server, we


specify POST as the method inside our form.

We are able to use for-loops, and reference attributes on


our objects (each link in links, has both a url and a
name attribute):

{% for l in links %}
<span><a href=" {{l.url}}">{{l.name}}</a></span> |
{% endfor %}
edit_poll.html
<html>
<head></head>
<body>
<h1>{{title}}</h1>
<h2>Edit your poll</h2>
<form action=" /polls/edit/{{poll.slug}}" method='POST'>
Question: <input type="text" name="question" value=" {{poll.question}}"
><br>
{% for c in poll.choices %}
Choice {{c.id}}: <input type="text" name="choice {{c.id}}" value="{{c.
text}}"><br>
{% endfor %}
Publish? <input type="checkbox" name="publish" checked=" {% if poll.
published %}checked{% endif %}"><br>
<input type="hidden" name="slug" value=" {{poll.slug}}">
<input type="submit" value="Edit poll">
</form>

{% for l in links %}
<span><a href=" {{l.url}}">{{l.name}}</a></span> |
{% endfor %}
</body>
index.html
<html>
<head>

</head>
<body>
<h1>{{title}}</h1>

<p>Welcome to the polls app. Feel free to add a poll or answer some
questions! :)</p>

{% for l in links %}
<span><a href=" {{l.url}}">{{l.name}}</a></span> |
{% endfor %}

</body>
</html>
poll.html
<html>
<head></head>
<body>
<h1>{{title}}</h1>
<h2>Poll: {{poll.question}}</h2>
{% if success %}
<p>Thanks for voting!</p>
<h3>Results</h3>
{% for c in poll.choices %}
<p>{{c.text}}: {{c.value}}</p>
{% endfor %}
{% else %}
<form action=" /polls/poll/{{poll.slug}}" method='POST'>
{% for c in poll.choices %}
<input type="radio" name="choice" value=" {{c.id}}">{{c.text}}<br>
{% endfor %}
<input type="submit" value="Vote">
</form>
{% endif %}
{% for l in links %}
<span><a href=" {{l.url}}">{{l.name}}</a></span> |
{% endfor %} </body></html>
polls.html
<html>
<head></head>
<body>
<h1>{{title}}</h1>

<h2>Polls</h2>
<ul>
{% for p in polls %}
<li><a href="/polls/poll/{{p.slug}}">{{p.question}}
</a></li>
{% endfor %}
</ul>

{% for l in links %}
<span><a href="{{l.url}}">{{l.name}}</a></span> |
{% endfor %}
</body>
What we've learned about templates:
jinja2 allows us to do some powerful things:

● Reference variables passed into our template with


double-braces: Ex. {{ title }}
● Reference attributes on variables passed in to the
template: Ex. poll.choices
● Use for-loops: for ... endfor
● Use control flow structures: if ... endif

Pretty cool, right?


Now, bring in the templates!
Copy the contents of the templates directory
from the cherrypy-polls code you
downloaded earlier, into the templates
directory that you just created in your own
version of the app, my-cherrypy-polls.
Fire up the app and play with it
To start your polls app, execute the following
command in your terminal, from within the my-
cherrypy-polls directory:

python app.py

NOTE:
We are running a development server. This
method is not suitable for a production
environment.
Testing
(/me facepalms)
Testing: What it is and why we do it
"Code not tested is broken by design" - JKM

We test our code to determine whether or not it is fit for


use.

Why, specifically, do we do it?


● Saves money
● Saves development time
● Happier developers
● Saves on QA time
● Confidence
Writing Tests
● Unit Tests
● Mocking Request/Response
● Mocking Objects
Unit Tests vs. Mock Tests
Units:
A method by which individual units of source code are
tested. A unit is the smallest testable part of an
application, like an individual function.

Mocks:
You can mock the creation, updating, and deletion of
request and response objects-- and much more. This type
of testing is good for making sure your app behaves in a
sane fashion for your users. The mock library is a great tool
for doing this.
Unfortunately...
... we don't have time to go into testing in-depth
during this tutorial.

We want you guys to come away with:

● Understanding of why writing tests is so important


● A basic understanding of how tests might be written
● Resources that inspire you to write your own tests!
Using the Python unittest library
import random
import unittest

class TestSequenceFunctions(unittest.TestCase):

def setUp(self):
self.sequence = range(10)

def test_choice(self):
element = random.choice(self.sequence)
self.assertTrue(element in self.sequence)

if __name__ == '__main__':
unittest.main()
Using the mock library
But first, some sample application code...
class BlogLogin(object):

def __init__(self, login, password):


self._blogin = Blog(login, password)

def my_subscriptions(self):
return self._subs(self._blogin.subs(self._blogin.login)

def _subs(self, call):


crs = -1
results = []
while crs != 0:
page = call(crs)
subscriptions = page['subscriptions']
crs = page['next_cursor']
results.extend(subscriptions)
return results
Mock class to test the sample code...
import BlogLogin
import mock

class BlogLoginTest(unittest.TestCase):

def test_all_subs(self):
"""Verify that my subscriptions load"""
bl = BlogLogin('username', 'password')
bl._blogin = Mock()
bl._subs.blogin.subs.return_value = \
{subs:[{'a':1}], 'next_cursor':0}
result = bl.my_subscriptions()
bl._blogin.subs.my_subscriptions.assert_called_with(
cursor = -1)
self.assertEquals([{'a':1}], result)
Testing homework:
Your app should be well-tested!

After PyCon, go home and write a test suite


using unittest and mock, you can find great
documentation here:

○ Python unittest Documentation


○ Mock Library Documentation
Errors
(FML)
Python Errors and Exception class
● Syntax errors
○ whitespace violations or other parsing errors
● Exceptions:
○ Python has built-in exceptions that can be caught or
raised
○ Alternatively, you can write your own exception
classes specific to your application
Catching exceptions
try:
# Try to get "my-first-poll" from memcache
return get_value('my-first-poll')
except KeyError:
# If the key is not found in memcache,
# return an empty dictionary instead
return {}

Catching exceptions is useful when you can anticipate what


exception might occur, and have the application do something
else instead of raising the exception.

In this case, if our key is not found, we want our app to return
an empty dictionary.
Raising exceptions
If you do not attempt to catch an exception,
then Python will raise it automatically.

There are many cases where you can catch an


exception and mitigate the negative impact for
users.

However, there are some exceptions that you


can never programmatically predict-- and those
bubble up into your application error log.
Exception homework:

Go through your completed my-cherrypy-poll


app and find places that are missing exceptions.

Put in your own try/catch blocks, and then try


writing your own custom exception classes:

○ Python Built-In Exceptions


○ Creating User-Defined Exception Classes
While we're talking about errors...
I want to introduce you to a valuable debugging tool that's part of Python's
standard library, pdb. Let's try it out really quickly in IPython:

In [8]: def my_fnc(val1, val2, val3):


...: import pdb; pdb.set_trace()
...: if val1 > val2:
...: print val1
...: elif val3 < val2:
...: print val2
...: else:
...: print val3

In [10]: my_fnc(1, 2, 3)
> <ipython-input-8-1f89e51121c6>(3)my_fnc()
-> if val1 > val2:
(Pdb) n
Basic pdb commands
A few options to enter at the pdb prompt:

○ l = print out lines above/below your cursor


○ n = continue to next line
○ s = step into a function

You can find the full pdb documentation here.

Have fun, and happy bug hunting!


Briefly, on server errors
● Server Errors: What are they?
○ http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
○ Use them to handle exceptions or proper situation
handling (sorry, I can't find what you're looking for:
404. sorry, you can't see that: 403)

● Throwing Server Errors


○ Each framework has their own way of throwing
server errors
○ http://docs.cherrypy.org/stable/refman/_cperror.html#classes
A few more goodies from Python's stdlib
Date object creation and manipulation
import datetime
Hashing algorithm toolbox
import hashlib
Encoding and decoding JSON objects
import json
More advanced math
import math
Web requests made easy (check out the Missing Manual)
import urllib2
Accepting user input to a script
import sys
Conclusions
(what did we learn?)
Whew!
Today we gave you a whirlwind tour of basic
Python, and we're hoping it's enough to get you
guys hooked!

We covered data types, loops, control flow


statements, conditionals, functions, classes,
testing, error handling, debugging, and more!
A few more resources for devs:
Docs:
○ Official Python Documentation
○ Stack Overflow

IRC:
○ Freenode #python channel

Cool stuff:
○ Github
Questions?

Thank you so much for attending


our tutorial, we hope you enjoyed it!

You might also like