+ All Categories
Home > Documents > Python for Programmers- A Project-Based Tutorial

Python for Programmers- A Project-Based Tutorial

Date post: 24-Sep-2015
Category:
Upload: dobrivoje-todorovic
View: 53 times
Download: 0 times
Share this document with a friend
Description:
python
131
Python for Programmers: A Project-Based Tutorial PyCon 2013 Sandy Strong & Katharine Jarmul
Transcript
  • 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 Jinja2Why 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 ipythonpip install jinja2pip 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-devapt-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 libeventbrew install libmemcachedsudo pip install pylibmc

  • Raise your hand if you were not able to install memcached and/or pylibmc successfully!

  • IPython: Initial debuggingmy-computer:~ sandy$ ipython

    In [1]: import urllib4

    --------------------------------------------------------

    ImportError Traceback (most recent call last)

    in ()

    ----> 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/ipython.html

  • 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 mathIn [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

    Out[11]: 2

  • 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

  • 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 formattingYou 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." % nameHello, 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 == NoneOut[1]: True

  • Incrementing and Decrementing Values

    # incrementcounter += 1

    # decrementcounter -= 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 outside of IPython, you must make your own indentations.

  • 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 ToolsThese 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 want our application to behave differently.

  • Comparison operators

    Equal to: ==Less than: Less than or equal to: =

    Let's try it out...

  • if and elseIn [70]: age = 20

    In [71]: if 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 **kwargsIn 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 statementsPython 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: UserView: Profile pageController: "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 ORMimport 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 unicodeUnicode 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.

  • def add_poll(**kwargs):

    choices_arr = []

    count = 1

    poll_dict = {}

    poll_dict['question'] = kwargs.get('question')

    for k,v in kwargs.items():

    if 'choice' not in k: continue

    choice_dict = {

    'id': count,

    'text': v,

    'value': 0

    }

    choices_arr.append(choice_dict)

    count += 1

    slug = slugify(kwargs.get('question'))

    poll_dict['slug'] = slug

    poll_dict['choices'] = choices_arr

    set_value(slug,poll_dict)

    if kwargs.get('publish'):

    publish_poll(slug)

    We grab the poll from kwargs, and then if choices are present, we build out a choice_dict of the choices associated with that poll.

    The poll question gets slugified, and added to the poll_dict, and then a list containing the choices dictionary is added. to poll_dict.

    We call set_value and pass in the slug (unique key for the poll), and the contents of poll_dict.

    Finally, if kwargs contains a key named "publish" we publish 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 datafrom 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 vote count is incremented in memcache.

  • views.py (continued)

    def get_global_links():

    return [{

    'name': 'Add a poll',

    'url': '/polls/add',

    },

    {

    'name': 'Home',

    'url': '/'

    },

    {

    'name': 'Poll list',

    'url': '/polls',

    }]

    This is essentially our site navigation. We return a list of dictionaries, each one containing a pretty name for the view, along with the url path where the view is located within our application.

    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 instructionsThese 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 = 8888server.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 codeThis 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 classclass 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.get_template, and templ.render. Everything else is pretty standard.

  • What are these [email protected]

    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 the PollViews class.

  • 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, get_poll. We then render the data_dict to our poll.html template.

  • 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 template.

  • app.py: class 1 cont: the edit method @cherrypy.expose

    def edit(self,key,**kwargs):

    method = cherrypy.request.method.upper()

    poll = False

    data_dict = {

    'title': 'Edit your poll',

    'links': get_global_links(),

    }

    if method == 'POST':

    poll = edit_poll(**kwargs)

    data_dict['poll'] = poll

    data_dict['success'] = True

    else:

    data_dict['poll'] = get_poll(key)

    templ = env.get_template('edit_poll.html')

    return templ.render(data_dict)

    If method is POST, we pass **kwargs into edit_poll, then we add poll to our data_dict.

    Otherwise, we call get_poll with the key passed in to edit, and add that to our data_dict.

    Finally, we render our data_dict to edit_poll.html.

  • app.py class 2: the PollApp classclass 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 Python code for this app!

  • 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

    {{title}}

    Add a poll

    Question:

    Choice 1:

    Choice 2:

    Choice 3:

    Choice 4:

    Publish?

    {% for l in links %}

    {{l.name}} |

    {% endfor %}

  • Some things to noteWe 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 %}

    {{l.name}} |

    {% endfor %}

  • edit_poll.html

    {{title}}

    Edit your poll

    Question:

    {% for c in poll.choices %}

    Choice {{c.id}}:

    {% endfor %}

    Publish?

    {% for l in links %}

    {{l.name}} |

    {% endfor %}

  • index.html

    {{title}}

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

    {% for l in links %}

    {{l.name}} |

    {% endfor %}

  • poll.html

    {{title}}

    Poll: {{poll.question}}

    {% if success %}

    Thanks for voting!

    Results

    {% for c in poll.choices %}

    {{c.text}}: {{c.value}}

    {% endfor %}

    {% else %}

    {% for c in poll.choices %}

    {{c.text}}

    {% endfor %}

    {% endif %}

    {% for l in links %}

    {{l.name}} |

    {% endfor %}

  • polls.html

    {{title}}

    Polls

    {% for p in polls %}

    {{p.question}}

    {% endfor %}

    {% for l in links %}

    {{l.name}} |

    {% endfor %}

  • 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 libraryimport 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 exceptionstry:

    # 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)

    > (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 stdlibDate object creation and manipulation

    import datetime

    Hashing algorithm toolboximport hashlib

    Encoding and decoding JSON objectsimport json

    More advanced mathimport math

    Web requests made easy (check out the Missing Manual)import urllib2

    Accepting user input to a scriptimport 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!


Recommended