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.8Dictionaries?
http://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html
php:
$_SERVER['HTTP_REFERER']
pyramid:
request.referer # or referrer, sighThe 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 organizeChameleon 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