Python For Programmers - A Project-Based Tutorial
Python For Programmers - A Project-Based Tutorial
A Project-Based Tutorial
PyCon 2013
https://us.pycon.org/2013/community/tutorials/5/
Introduction to Workshop
Who are we?
http://bit.ly/YXKWic
● Whitespace enforcement
● 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!!
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:
● %hist
● %pdb
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
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'
# increment
counter += 1
# decrement
counter -= 1
Introduction to Python Loops, Code
Blocks, and Boolean Logic
● for
● while
http://www.secnetix.de/olli/Python/block_indentation.hawk
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
● if
● else
● elif
def function(*args):
# Expects something like:
# ['he', 'she', 'it']
def function(**kwargs):
# Expects something like:
#{'me': 'you', 'we': they'}
class MyCar:
''' This class defines a car object '''
# member variables go here
make = "Honda"
model = "Civic"
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.
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.
http://www.codinghorror.com/blog/2008/05/understanding-model-view-controller.html
Real world example
Model: User
View: Profile page
Controller: "Change password" functionality
mkdir my-cherrypy-poll
Grab the app code from Github, you'll
need it later:
If you have git installed:
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:
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
my-favorite-food-is-oranges-what-is-yours
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.
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?
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
... 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
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
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.
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.
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)
@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)
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)
</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.
{% 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:
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
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.
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 my_subscriptions(self):
return self._subs(self._blogin.subs(self._blogin.login)
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!
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.
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:
IRC:
○ Freenode #python channel
Cool stuff:
○ Github
Questions?