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.

Made with Slides.com