Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
751 lines (496 sloc) 22.9 KB

About Django from A Pyramid Guy

Authors: Chris McDonough
Agendaless Consulting
Date: 9/6/2012 (DjangoCon 2012)

http://plope.com/static/presentations/djangocon2012/

Who Am I

  • BBS nerd in the 80s.
  • Bad Perl hacker until Python. Came to Python via Zope in 1999. Worked at Digital Creations (aka Zope Corporation) until 2003.
  • Primary author of: Pyramid web framework, Supervisor UNIX process control system, Deform form system, Repoze collection of middleware, and other unmentionables. Contributor to Zope, WebOb, and lots of other OSS projects.

Who Am I (Cont'd)

  • Quoting Glyph: "I've been doing IRC support for 10 years, so I'm pretty much dead inside."
super-computer-nerd.jpg

What is a Web Framework

  • A web framework receives a request, calls user code in order to return a response. Everything else is a bonus.
throwandcatch.jpg

Django Does a Lot Of Things Right

  • Django gets a lot of things right.
  • If I had just come to Python, I'd probably be using it.
gettingitright.jpg

Django Docs Do It Right

  • Django docs raised the Python documentation bar.
  • Great resources for multiple audiences.
spellbook.jpg

Django Defaults Do It Right

  • Django provides people with unambiguous ways to solve 80% of their web development problems.
  • Doesn't prevent specialization or use of external libraries.

Django Views Do It Right

  • No magical globals or threadlocals.
  • Django view lookup and execution is very fast.
nomagic.jpg

Django Forms Do It Right

  • Decoupling of models from forms.
  • Optional recoupling via modelforms.
as400_sign_on.gif

Django Extensibility Does It Right

  • Replaceable backends for authentication and storage, etc.
  • Encourages an environment of plugins at different levels.

Django Reality Does It Right

  • Can't argue with success.
  • Scores of very successful sites built using Django.
toronto_skyscrapers.jpg

So WTF?

  • Why maintain a different framework if Django is so awesome?
omg_wtf.jpg

What is Pyramid

  • James Bennett's PyCon 2012 "Django In Depth" tutorial: ~20 minutes out of 3 hours devoted to things that Pyramid actually does: low-level template API, view lookup and execution, HTTP responses, middleware.
  • Pyramid is a corner of a corner of Django, magnified. It handles view lookup and execution, templating, internationalization, and provides related convenience APIs.
  • It has no built-in form generation system. It does not prefer any particular persistence system. It does not ship with an admininstrative application.

What Is Pyramid (Cont'd)

  • Built for extensibility and for composition of larger systems.
  • Maybe 10K LOC, of which maybe 4K is a configuration system that allows for composing larger systems from smaller ones.
  • Something like Pyramid could be used to build something like Django.
  • Pyramid's current release (1.3) supports Python 2.6, 2.7, 3.2, and 3.3.

Small Pyramid Program

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
   return Response('Hello %(name)s!' % request.matchdict)

if __name__ == '__main__':
   config = Configurator()
   config.add_route('hello', '/hello/{name}')
   config.add_view(hello_world, route_name='hello')
   app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 8080, app)
   server.serve_forever()

Is Pyramid a Microframework?

  • Pyramid is sort-of microframework-like. You can write a Pyramid application in a single file.
  • Self-identifying microframeworks tend to suggest application development patterns to its users which promote convenience over explicitness.
  • Pyramid for better or worse doesn't do this stuff, because it aims to be useful in larger systems where these patterns can lead to extensibility problems.

Scaffolding (Larger Apps)

  • Most people use a "scaffold" to generate a multifile project instead of starting one from scratch.
  • Generated code depends on some combination of Pyramid, Pyramid plugins, and other third-party libraries and frameworks.
  • pcreate generates code that you can use to generate a Python distribution (something you can credibly upload to PyPI).
pcreate -s alchemy myproject

Higher-Level Frameworks

ptah

kotti

poolyx

substanced

These are more like Django than Pyramid is like Django. They each have an admin interface and make choices about what type of persistence system will be best supported.

I'm No Genius

urinals.jpg

Django Avoids Setuptools

  • Django lets people remain ignorant of distribution issues for maybe longer than they should need to be.
  • Django is an outlier in Python-land convention wise: setup.py develop of django doesn't work (although pip install -e . does), avoidance of setuptools also prevents use of console scripts (e.g. django-admin), setup.py test won't work. Django "apps" are not just Python distributions, they're a special Djangoey thing.
  • Shared conventions are important because they spread the support burden across a wider base.

Django Avoids Setuptools (2)

  • Django is itself one big "distribution" (in distutils terminology). In other words, it "has no dependencies".
  • But it indeed has parts that could be useful independent of the remainder. Breaking Django into smaller pieces might improve the quality of each of those pieces. E.g. django-orm, django-forms.
  • Big downside: documenting and supporting both in-context and out-of-context usage is a huge pain. Very often just not worth it.

Django Avoids Setuptools (3)

  • But ignoring Python packaging issues isn't helping to improve them. Python packaging and distribution needs you very badly. We all carry the support load of users new to Python who come in via Django.
  • Fewer documentation issues to cope with by ignoring existing tools and conventions, but contributing to docs for setuptools and/or recommending setuptools and virtualenv to new users would float all boats.

Subclassing Is Convenient

  • You don't have to explain a protocol (the protocol is Python).
  • But offering extensibility via subclassing is often a poorer choice than offering extensibility via composition and very explicit interfaces. People begin depending upon the implementation details of the classes you tell them to subclass.
  • Not uncommon to see a subclass of a subclass of a subclass of a subclass; figuring out how the thing works can be an exercise in pain and multiple editor windows.

Pyramid "Class-Based Views"

from pyramid.views import view_config

class Foo(object):
    def __init__(self, request):
        self.request = request

    @view_config(route_name='fred')
    def thisnamedoesntmatter(self):
        request.response.body = 'fred'
        return request.response

Pyramid "Class-Based Views" (2)

Framework effectively does when the route named fred is matched:

response = Foo(request).thisnamedoesntmatter()

No subclassing required (although it's of course possible). A "scan" picks up the view configuration statements, or same work can be done imperatively.

Pyramid "Class-Based Views" (3)

from pyramid.views import view_config

class Foo(object):
    def __init__(self, request):
        self.request = request

    @view_config(route_name='fred')
    def thisnamedoesntmatter(self):
        request.response.body = 'fred'
        return request.response

    @view_config(route_name='bob')
    def doesntmattereither(self):
        request.response.body = 'bob'
        return request.response

Globals are Convenient

  • You don't have to explain a protocol for obtaining a value (the protocol is import).
  • Global registries make it impossible to embed more than one application into the same Python process.
  • Development of circular imports is likely. Depending on DJANGO_SETTINGS_MODULE envvar necessitates putting settings import at function scope. This is a tip-off that settings aren't-really-global.

Globals Are Convenient (Cont'd)

Pyramid configuration phase:

if __name__ == '__main__':
    settings = {'mailhost':'localhost'}
    config = Configurator(settings=settings)

This is the only place where we deal with settings at a "global" level. Instead of importing them, elsewhere in the code people do e.g.:

def someview(request):
    mailhost = request.registry.settings['mailhost']

Module-Scope Work Is Convenient

From Django tutorial, at module scope:

from django.contrib import admin
admin.autodiscover()

autodiscover() called for its side effects. Presumably mutates a global (admin).

Module-Scope Work Is Convenient (2)

  • These things can be done at module scope without concern:
    • An import of another module or global.
    • Assignment of a variable name in the module to some constant value.
    • The addition of a function via a def statement.
    • The addition of a class via a class statement.
    • Control flow which may handles conditionals for platform-specific handling or failure handling of the above.

Module-Scope Work Is Convenient (3)

Instead of admin.autodiscover() getting called at module scope, it would be better if a pattern like this was used:

if __name__ == '__main__':
   config = DjangoConfiguration()
   config.admin.autodiscover('app1', 'app2')

The state being mutated is not global.

Pluggable Apps / Reusable Apps

  • Pluggable apps probably aren't really that pluggable, reusable apps probably aren't as reusable as you might like.
  • IMO, even a framework as high-level as Django can't really offer such a feature without stretching the truth just a little bit.
  • The only thing IMO that can truly offer pluggable apps: another app. No general-purpose framework can do a great job here. (Examples: Wordpress, Jenkins, Plone).

Rendering Is Meta-View

render_to_response using template in view is not much fun to test.

def aview(request):
    return render_to_response('my_template.html', {'a':1})

Being able to return a dict (or another kind of object) from a view callable is easier to unit test and allows the view code to be reused for different renderings.

Rendering Is Meta-View (2)

A Pyramid view function that has configuration which names a Mako template renderer:

@view_config(route_name='aview', renderer='sometemplate.mak')
def aview(request):
    return {'username':request.user.name}

Rendering Is Meta-View (3)

Theoretical test code:

class Dummy(object): pass

def test_it():
   from somewhere import aview
   request = Dummy()
   request.user = Dummy()
   request.user.name = 'fred'
   assert aview(request) == {'username':'fred'}

Rendering Is Meta-View (4)

The same view function can be used to render either a Mako template or as JSON.

@view_config(route_name='aview.html', renderer='sometemplate.mak')
@view_config(route_name='aview.json', renderer='json')
def aview(request):
    return {'username':request.user.name}

The JSON renderer is not a template renderer (it has no extension), but it's still a renderer.

Unit Tests

  • Extensive use of Django test client for tests will cause test suite to run more slowly than necessary. "Integration" or "system" testing.
  • A slow enough test suite won't be run before commit.
  • Testers who don't understand any type of testing other than "system" or "integration" testing tend to bring poor testing practices to unrelated systems.

Static Files

  • Python WSGI servers are getting better at serving static files. E.g. Gunicorn supports sendfile on UNIX.
  • Might be time to reconsider offloading media to a dedicated non-Python server and make use of what's available in WSGI-land.

End Of "But..."

  • No more criticisms during this talk.

Community

  • Pyramid community is maybe 5%-10% the size of the Django community and it's growing.
  • Your success is our success. I'm satisfied to have Pyramid in a fight for the #2 Python web framework spot forever.
  • Create adapter for WebOb that implements the Django request API? Create an adapter for SQLAlchemy that implements the Django ORM API?

Collaboration (Low-Level)

  • These are likely losers. They are "30 year plans". Things change so fast. Who will pay immediately? Who will benefit immediately?
  • Might be better to try to use common non-domain-specific dependencies (e.g. setuptools, virtualenv, WSGI middleware, etc). It would be a great win to share documentation burden, even if we had to fork it for our own contextual requirements.

Collaboration (High-Level)

  • Django is limited by backwards compatibility concerns. It's impractical to make large architectural changes now. Your users would kill you.
  • Take useful bits out of Django and turn them into independent subsystems that share/require no global state. Create a bw compat "django" package that re-gloms them all back together.
  • Consider Pyramid or another smaller Python framework as a base for a bw incompat Django offshoot that ditches the glom.

Challenges

  • I challenge you to investigate how other frameworks work and steal liberally.
  • I challenge you to embrace existing Python packaging and distribution tools.
  • I challenge you to contribute to efforts that more directly benefit the broader Python web community.

Images

Super Simple Throw and Catch: http://www.abdopub.com/shop/pc/configurePrd.asp?idproduct=29321

Spellbook: http://alteredroute.blogspot.com/2008/10/potions-and-spells.html

Computer nerd: http://noscope.com/photostream/various/super-computer-nerd.jpg/view

Images (Cont'd)

Getting it right: http://sluggerotoole.com/2011/03/29/looking-back-at-the-dups-2007-manifesto/dup-getting-it-right-logo/

Urinals: http://www.businessblunder.com/2011/01/bad-architecture-examples/

No magic: http://gordonscruton.blogspot.com/2011/06/no-magic-please-part-1-learning.html

Images (Cont'd)

AS/400 Sign On: http://forums.speedguide.net/showthread.php?229405-AS400-question

OMG WTF: http://www.graphicshunt.com/funny/images/omg_wtf-12875.htm

Toronto skyscrapers: http://www.primalpics.com/photos/010000/2200/002174_toronto_skyscrapers.htm


Chris McDonough, Agendaless Consulting
Something went wrong with that request. Please try again.