Using lesscss with Pyramid and Fanstatic

We all know twitter bootstrap. It became really popular in the past few months. We saw it all over the internet with same header and same colors.

In this post I'll show you how to customize twitter bootstrap with pyramid and Fanstatic. In other words, how to use lesscss (the language used by bootstrap) with pyramid.

lesscss allow to write dynamic css stylesheets with a specific language. You can find a lot of examples and documentation on their website.

In this example we will use:

  • pyramid_fanstatic: A Fanstatic integration as a pyramid tween. It also provide a pyramid scaffold and an helper to reload the development server when a static resource change.
  • js.lesscss: A Fanstatic package to embed lesscss as a python dependencie. This package was created by Stéphane Klein and I've update it to use lessc to compile .less files

Ok. Let's go!

Create a virtualenv and install a bunch of packages:

$ virtualenv myenv
$ cd myenv
$ source bin/activate
$ pip install pyramid_fanstatic js.lescss

Now that we have a virtualenv with all dependencies we need to create a pyramid application:

$ pcreate -s starter -s pyramid_fanstatic lesscss_example
$ cd lesscss_example

A README_FANSTATIC.txt file will show you how to finalize your installation. We need that because the pyramid_fanstatic scaffold does not redifine files like setup.py and __init__.py so you can use it with all scaffolds included in pyramid and other projects. Here is the file content:

To finalize your installation you'll need to follow those steps.

Add those line the app:main section of your development.ini:

[app:main]

fanstatic.bottom = true
fanstatic.debug = true

Add some requirements to your setup.py:

requires = ['pyramid', 'pyramid_debugtoolbar',
            'pyramid_fanstatic',
            # if you want to use lesscss
            #'js.lesscss'
            ]

Also add those entry points to the same file bellow the paste.app_factory:

# Fanstatic resource library
[fanstatic.libraries]
lesscss_example = lesscss_example.resources:library

# A console script to serve the application and monitor static resources
[console_scripts]
pserve-fanstatic = lesscss_example.resources:pserve

You also need to add pyramid_fanstatic tween to your application. Add the following to your __init__.py file:

config.include('pyramid_fanstatic')

Run python setup.py develop to get the pserve-fanstatic script available.

That's it. Once all those steps are done we can have a look at the resources.py created by pyramid_fanstatic scaffold. Here is a modified version to activate the main.less resource:

from fanstatic import Library
from js.lesscss import LessResource

library = Library('lesscss_example', 'resources')

less_resource = LessResource(library, 'main.less')


def pserve():
    """A script aware of static resource"""
    import pyramid.scripts.pserve
    import pyramid_fanstatic
    import os

    dirname = os.path.dirname(__file__)
    dirname = os.path.join(dirname, 'resources')
    pyramid.scripts.pserve.add_file_callback(
                pyramid_fanstatic.file_callback(dirname))
    pyramid.scripts.pserve.main()

The trick is to use a LessResource instead of the standard Fanstatic Resource. This class compile the .less file at init time. The pserve() function is the target of our pserve-fanstatic entry point. It run pyramid's pserve script after adding the Fanstatic resources to the list of files watched by the monitor used to reload the application when a file changed. Don't forget the --reload option when running this script else the monitor is not initialized.

Modify the main.less file in the resources directory:

$ cat lesscss_example/resources/main.less                                                                    ✹
// main lesscss style sheet for lesscss_example

@color: #ccc;
@border: thin solid black;

blockquote {background-color: @color; border: @border;}

Ok, we are ready to build our first view. Just add this to your __init__.py:

from lesscss_example.resources import less_resource


def home(request):
    """a simple view to test our resource"""
    less_resource.need()
    return {}

Fanstatic use a thread local registry to store the resource used by a request so the only thing needed is to use the resource method .need() and this resource will be injected in your html.

Let's register the view and it's associated route:

config.add_route('home', '/')
config.add_view(home, route_name='home', renderer='templates/mytemplate.pt')

mytemplate.pt is a very basic template with an empty <head />. Fanstatic will inject resources tags for you. You remember that ?

Easy, right ? Check the result with pserve development.ini. At this time nothing should work. Why ? Because we want to use lessc and we dont have it.

I'll not cover node.js / less installation here. You can find a lot of tutorial on other places. You can also find a buildout.cfg to get lessc in your buildout at the bottom of this post. Notice than lesscss only work with node 0.4.12 for me at the moment.

pserve-Fanstatic will try to find lessc binary in the following directories:

  • $PWD/bin
  • $HOME/bin
  • /usr/local/bin
  • /usr/bin

Once you got a lessc binary, run pserve-fanstatic:

$ pserve-fanstatic --reload development.ini

This will run the app in "compile" mode. This mean that the .less is compiled when you are starting the server but the browser will use the .less file. When you are ok with the result, restart the server with the standard pserve script. Your browser will use a compiled version of the .less file (.less.min.css).

You can have a look at the minified resource:

$ cat lesscss_example/resources/main.less.min.css
blockquote{background-color:#cccccc;border:thin solid #000000;}

That's nice. How about twitter bootstrap ? Let's try it. Checkout bootstrap in your resources/ directory:

$ git clone https://github.com/twitter/bootstrap.git lesscss_example/resources/bootstrap

And register bootstrap.less in the Fanstatic registry. Add this to your resources.py file:

bootstrap = LessResource(library, 'bootstrap/lib/bootstrap.less')

Register a view to test the new resource:

from lesscss_example.resources import bootstrap


def home2(request):
    """a simple view who use bootstrap"""
    bootstrap.need()
    return {}


config.add_route('bootstrap', '/bootstrap')
config.add_view(home2, route_name='bootstrap',
                      renderer='templates/mytemplate.pt')

And thats it. Run pserve-fanstatic and go to /bootstrap. Play with the variables.less and have fun. You'll get a bootstrap.less.min.css usable in production.

As a bonus I'll show how to use buildout to make all that stuff easier. This will install node.js and lessc so it can be used by pserve-fanstatic.

[buildout]
# run eggs, pserve and node parts
parts = eggs node
# set our project as a develop egg
develop = .
# dont check for new releases
newest = false
# prefer stable release (only buildout can do that!)
prefer-final = true

[eggs]
# the eggs part will install all dependencies and setup our application in
# development mode
recipe = z3c.recipe.scripts
eggs =
    pyramid
    lesscss_example

[node]
# install node.js + lessc
recipe = gp.recipe.node
# you can use an existing node binary
#binary = /usr/local/bin/lessc
npms = less
scripts = lessc

That's all... All this code is available as a github repository.

Django is awesome

10/14/2011 python django

Yeah, Django is awesome. You have to know that I use Django every days at work. But... there's some points that I really dislike. I guess I'm the best french django opponent. Now I can show this post when people ask me why.

Django is monolithic

Everybody know that. No discussion is needed.

Django has a real community

Some Django's third party products are great. But all contribution made for Django are only for django. Django has it own middleware specification even if the pep 333 exists. That's sucks. What if I want to use all the great stuff with my own wsgi framework ?

Next point: Django use svn. What ? This old VCS ? Why I can't contribute by forking on github or bitbucket ? Is that a real modern community ?

Django templating can't contain some logics

Oh yeah, that's a great philosophy. But I'm a mature developer and I want to use a simple modulo in a template without having to write a 20 lines template tag. I promise that I don't put so much logics in my template.

You can use Jinja!

Yes, I can. But then I need to forget all the great stuff provided by third party products. So, in fact, I can't.

Webob is great but Django's Request/Response is better

Web0b is a 200% well tested library. It just work. Why this is not used by Django ? Is it so hard ? Some people tried to include that library in Django but... it just fail. See this page. No longer maintained... I guess the author switch to another framework.

Packaging

Is that really exist ? No, It doesn't matter. All my python code should go in app/ then you can checkout my code in your apps/ folder. no problem. After a few years passed with Tarek "el packager" Ziade I became sick when I see the code generated by Django's templates.

Django has view class !!

That's the good news of the latest release. Awesome ! That's just made me remember Zope 2. I guess that this post explain this better than I can.

EOP

End Of Post. I guess I've pointed all things I dislike in Django. Or maybe it's just a work in progress...

Django still great. But it can be better without so much effort. Use WebOb... Add a setup.py to project/app templates... Use a Pyramid like renderer to allow more than one template engine... Use github... Please, just do it, Django people!

Je suis un pirate

20/00/2010 useless propagande life

Ce billet est un petit coup de gueule que j'ai depuis longtemps en travers de la gorge et que j'ai envie d'évacuer. C'est aussi un témoignage qui prouve en partit que ce sont ceux qui consomme le plus de culture qui en télécharge le plus (parce que je ne fais excessivement aucun des deux :D)

Donc. Oui, je suis un pirate. J'ai des mp3 et des films téléchargés illégalement chez moi. C'est une honte, selon notre gouvernement.

La plupart de mes films viennent d'amis qui passent chez moi en transit et qui ont avec eux un ordinateur ou un disque dur externe. Cela serait une offense que de refuser, non ? Au passage je ne me ferais jamais attraper par la HADOPI. J'ai jamais été doué avec le P2P et ça m'emmerde de passer du temps à ça.

Mes mp3 viennent d'une radio en ligne que je paye quelques euros par mois et dont j'aspire les morceaux que j'aime pour les mettre sur mon iPod. Après tout, je ne vois pas pourquoi je payerai deux fois pour le même morceau. Je ne donnerais pas la combine, mais c'est aussi HADOPI-PROOF, forcément.

A coté de ça, je consomme bien plus de culture que je n'en "pirate". Je vais au cinéma. A moins 3 fois par mois. Je vais voir des concerts. Au moins 3 fois par mois. Je vais jusqu'à débourser 80euros pour allez voir Vanessa Paradis. C'est dire.

On peut donc estimer que mon budget culturel est approximativement de 3x20 euros pour les concerts 3x10 euros pour le ciné. Soit 90 euros par mois. Je trouve que c'est une somme non négligeable et qu'en ces temps de crise ou il faut travailler plus pour gagner moins, le commun des mortel n'a pas un tel budget à investir dans sa culture. La culture est donc vouée à être un passe temps de riche.

Tout cela pour en venir a la question qui me chagrine: A partir d'un budget de combien d'euros n'est-on plus considéré comme redevable au majors qui contrôle le marché culturel ? Est-ce que si je donne plus de 200 euros par mois aux majors j'ai le droit de télécharger un film sans être considéré comme un pirate ?

Le pire la dedans, c'est que malgré les initiatives promises pour accroître l'offre légale, si je veux regarder "The Rocky Horror Picture Show" (film culte si il en est) en VOD, je ne peux PAS. Et ce, pour deux raisons. La première, il faut fouiller tout l'internet pour trouver un fournisseur qui diffuse ce film. La deuxième, si finalement je finis par le trouver, cela sera probablement pour m'apercevoir qu'il est contaminé par des DRM que je ne sais pas lire avec mon beau MacBookPro.

Donc, franchement, ouvrez les yeux. HADOPI (et plus globalement tous les moyens mis en oeuvre pour lutter contre le téléchargement) ne protège pas la culture. Elle lui nuit !

NON MAIS VANESS QUOI !!!!

13/42/2010 groupie life

Hier je mange avec Gwen à midi. Tranquille quoi. Tout à coup il m'annonce que sa femme à chopé des places pour aller voir Vanessa Paradis en concert acoustique. Voici à peu prêt ma réaction:

Quoi ????? Non mais je suis super jaloux là !!!! C'est une fée cette fille.
Je suis trop fan !!!

Gros coup de déprime sachant que c'est au Casino de Paris. A peu prêt à 300m de chez moi... Et que apparemment c'était bien complet. *loose*

Bref, ce matin j'écoute ma radio favorite et je tombe sur ça:

Et ça:

*frissons* *larme à l'oeil*

Du coup dans un élan de désespoir je vais sur un site de vente en ligne. Et... J'AI CHOPER UNE PLACE !!!!!

30 juin au Casino de Paris, 18ème rang. Plus qu'a attendre... Et prévoir les
kleenex !

Using restkit proxy in your WSGI app

06/33/2010 python wsgi proxy

Here is my use case. A few days ago I've wrote an application to mirror my Flickr accounts. The pics are downloaded on file system and the metadata are stored in CouchDB. Now I use the Flickr interface to upload and tags pics but I got my own data on my own server. That's always cool.

Well done but now it can be useful to have a small web app to see the pics. Right ? That's what I've do. Since I love jQuery and CouchDB is full json compliant I don't want to write a complex app with tones of python code. So the idea is to have a small wsgi app to only serve static javascripts/html/css files and a proxy app to serve json by proxying CouchDB.

For now I'm using WSGIProxy. The code is very simple and look like this:

from wsgiproxy.exactproxy import proxy_exact_request
from webob import Request

class Proxy(object):
    def __init__(self, db=None, **kwargs):
        self.db = db
    def __call__(self, environ, start_response):
        req = Request(environ)
        if req.method == 'GET':
            req.server_name = '127.0.0.1'
            req.server_port = 5984
            req.script_name = ''
            req.path_info = '/%s%s' % (self.db, req.path_info)
            resp = req.get_response(proxy_exact_request)
            resp.content_type = 'text/jasacsript'
        else:
            resp = exc.HTTPForbidden()
        return resp(environ, start_response)


def make_app(global_conf, **local_conf):
    conf = global_conf.copy()
    conf.update(local_conf)
    return Proxy(**conf)

That's cool and simple and ok for small files. But if you want to handle large request WSGIProxy will raise a MemoryError. This is no longer fun. I've already sent a patch but don't want to bother the Paste team with that.

Then I've try to use restkit and thought that it can be the best library to wrote a proxy app. restkit manage a pool of http connection and handle large file request in a clean way.

The result is a small contribution. A set of WSGI applications included in the wsgi_proxy extention.

Here is a simple Paste config file to show own to use the proxies:

[server:main]
use = egg:Paste#http
port = 4969

[app:main]
use = egg:Paste#urlmap
/couchdb = couchdb
/ = proxy

[app:couchdb]
use = egg:restkit#host_proxy
uri = http://localhost:5984/mydb

[app:proxy]
use = egg:restkit#host_proxy
uri = http://benoitc.github.com/restkit/
max_connections=50
allowed_methods = get head post

No code needed. Cheers.

You can also use the Proxy class to proxify clients request and transform the response on the fly:

from webob import Request
from restkit.ext.wsgi_proxy import Proxy

proxy = Proxy()

def application(environ, start_response):
    req = Request(environ)
    req.environ['SERVER_NAME'] = 'example.com'
    req.environ['SERVER_PORT'] = '80'
    # do stuff
    ...
    resp = req.get_response(proxy)

    # do stuff ...
    ...

    return resp(environ, start_response)

I've also tried to replace WSGIProxy in deliverance with an ugly monkey patch:

from restkit.ext.wsgi_proxy import Proxy
from wsgiproxy import exactproxy
print 'Patching exactproxy with restkit proxy'
exactproxy.proxy_exact_request = Proxy(max_connections=4, allowed_methods=['GET', 'HEAD', 'POST']).__call__

It work perfect.