Pyramid, the Spot Between Flask and Django

Who is this jerk?

Tod Hansmann,
Problem Solver

Otherwise very boring.

Why Pyramid? Who would Use this Stuff?

Zope, and its ilk

  • Zope (2) started it all
  • Nobody liked Zope 3 (some exceptions)
  • Pylons and repoze.bfg (the real pyramid?)
  • So many modules. Like, so many
  • Yet another thingamabob for web?
  • Python way back when

http://grokcode.com/864/snakefooding-python-code-for-complexity-visualization/

Compromise

  • Modules are the new thing, right?
  • Sometimes you think big.
  • Schadenfreude
  • Architected
  • Oh, and it's Python

WSGI

We learn nothing from ourselves.

HTTP, real quick:

GET /someresource HTTP/1.1
Host: omgwtfbbq.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95
Referer: https://www.google.com/
Accept-Language: en-US,en;q=0.8

Dictionaries?

http://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html

php:
$_SERVER['HTTP_REFERER']

pyramid:
request.referer  # or referrer, sigh

The Config

Simple. The hard bits are behind it.

A quick start: scaffolds

$ pcreate -s alchemy demoapp
Creating directory /home/tod/venvs/pyratest/demoapp
  Recursing into +package+
    Creating /home/tod/venvs/pyratest/demoapp/demoapp/

*SNIP*

Welcome to Pyramid.  Sorry for the convenience.
===============================================================================
$ 

Example config (dev)

###
# app configuration
###

[app:main]
use = egg:demoapp

pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
    pyramid_debugtoolbar
    pyramid_tm

sqlalchemy.url = sqlite:///%(here)s/demoapp.sqlite
#sqlalchemy.url = postgresql://dbuser:dbpass@127.0.0.1:5433/devdb

# wsgi server configuration

[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543

#AND A LOT MORE STUFF!!!

Routing Requests

The part that we get.

Maps to Resources

PHP runs a file,

 

Java runs a class or a bean or some nonsense,

 

Python runs a function (not much better),

 

Actually that was a lie.  Sort of.  Look, we don't have time for this.  Just pretend.

Example the first:

# "config" below is presumed to be an instance of the
# pyramid.config.Configurator class; "myview" is assumed
# to be a "view callable" function

from views import myview

# This adds the route to some dictionary
config.add_route('myroute', '/prefix/{one}/{two}')

# This adds a view that uses the route
config.add_view(myview, route_name='myroute')

# Views and Routes are separate, and can be one-to-many, so a view can apply to a
# dozen routes for instance which is convenient because one function can be 
# generalized to handle multiple paths of URLS, but one code path (DRY)

Example the dos:

# In __init__.py or whatever you "app" main is

config.add_route('myroute', '/prefix/{one}/{two}')
config.scan('mypackage')


# In some other file that scan will pick up:

from pyramid.view import view_config
from pyramid.response import Response

@view_config(route_name='myroute')
def myview(request):
    return Response('OK')


# Decorators.  'nuff said.

You can't escape regex:

# Your routes can match regex stuff

# One or more digits
config.add_route('myroute', '/prefix/{one}/{objid:\d+}')
config.scan('mypackage')


# Then you can use it like so:

from pyramid.view import view_config
from pyramid.response import Response

@view_config(route_name='myroute')
def myview(request):
    myInt = int(request.matchdict['objid'])  # Guaranteed to be an int?
    return Response('OK: %d' % myInt)

# HTTP hates you, you will never change this fact.

More Functionality

Sometimes we just don't like pyramid (or whatever we're using, really. We're devs after all.)

Adding a DB handle

def db(request):
    return request.registry.settings['db.sessionmaker']()

def main(global_config, **settings):
    engine = engine_from_config(settings, 'sqlalchemy.')
    settings['db.sessionmaker'] = sessionmaker(bind=engine, 
                                               extension=ZopeTransactionExtension())
    session_factory = UnencryptedCookieSessionFactoryConfig('sesscookiename')
    config = Configurator(settings=settings, session_factory=session_factory)
    config.include('pyramid_jinja2')
    config.include('cornice')
    config.add_static_view('static', '../static', cache_max_age=3600)
    add_routes(config)  # Not in this paste
    add_views(config)  # Not in this paste

    # These modify the request to add db and user as methods, which once called are 
    # then reify values 
    # subclassing/overriding the Request will be... problematic, as discovered the hard way
    config.add_request_method(callable=db, name=None, property=True, reify=True)
    config.add_request_method(callable=user, name=None, property=True, reify=True)

    # This should add an adapter for types normally we don't wrap in JSON
    json_renderer = JSON()
    json_renderer.add_adapter(datetime.date, date_serializer) # function elsewhere
    config.add_renderer('json', json_renderer)

    config.scan()
    return config.make_wsgi_app()

Templating

A slight detour to Jinja2 land, a wonderful place, at least for our purposes

pyramid_jinja2

In your ini:

pyramid.includes =
    pyramid_debugtoolbar
    pyramid_tm
    pyramid_jinja2
    pyramid_exclog


In your main():

    config.include('pyramid_jinja2')


Depends on how you want to organize

Chameleon vs Jinja2

Chameleon:
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
    <!--! This comment will be dropped from output -->
    <div tal:repeat="item range(10)">
        <p tal:condition="repeat.item.even">Even</p>
        <p tal:condition="repeat.item.odd">Odd</p>
    </div>
    <!--? This comment will be included verbatim -->
    <div>
        <?python import pdb; pdb.set_trace() ?>
        <ul tal:switch="len(items) % 2">
            <li tal:case="True">odd</li>
            <li tal:case="False">even</li>
        </ul>
    </div>
</body>
</html>

Chameleon vs Jinja2

Jinja2:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
    <head>
        <title>My Webpage</title>
    </head>
    <body>
        <ul id="navigation">
        {% for user in users if not user.hidden %}
            <li>{{ user.username|e }}</li>
        {% endfor %}
        </ul>
        {# this is comment #}
        <h1>My Webpage</h1>
        {{ my_variable }}
    </body>
</html>

SQLAlchemy

The bits we take for granted, because they're just so awesome

Just some talking points

  • The Zope Transaction Extension
  • Connection Pooling
  • Nobody like exceptions

Hosting it!

We're going to draw a lot of things now and discuss, which isn't going to be in this slide deck thingamajig Mahoosi whatsit.

Questions.

If you dare.

You monster.

Pyramid, the Spot Between Flask and Django

By Tod Hansmann

Pyramid, the Spot Between Flask and Django

Given to UtahPython on February 12th, 2014 to little fanfare, and pizza.

  • 733