Facebook connect with python

 

A few weeks ago I've discover Facebook Connect. So I decide to try to use it in a new project. It look like an easy way for new user to register in my application.

The first step is to follow the Quick start guide. Then whe need to take care of Facebook in our application. There is already a cool python library to play with Facebook called pyfacebook. So you need to install it.

In my project I aleady use repoze.what for authentification. It just rocks. So i decide to write an IIDentifier plugin for Facebook:

import sha
import facebook
from webob import Request
from zope.interface import implements
from repoze.who.interfaces import IIdentifier

DEFAULT_FIELDS = ['uid', 'name', 'first_name', 'birthday', 'relationship_status',
                  'proxied_email', 'sex', 'hometown_location',
                  'pic', 'pic_big', 'pic_small', 'pic_square']

class Params(dict):
    def __getattr__(self, attr):
        return self.get(attr, '')
    def __html__(self):
        return repr(self)

class Facebook(object):
    implements(IIdentifier)

    def __init__(self, api_key, secret_key):
        self.api_key = api_key
        self.secret_key = secret_key

    def identify(self, environ):
        req = Request(environ)

        # initialize the api object
        fbapi = facebook.Facebook(self.api_key, self.secret_key) # init pyfacebook instance
        if fbapi.check_session(req):
            environ['repoze.who.fb'] = fbapi
            user = fbapi.users.getInfo([fbapi.uid], DEFAULT_FIELDS)[0]

            # we used the proxied_email and a generated password to retriave
            # the user from our DB
            user.update(
                login=user['proxied_email'],
                email=user['proxied_email'],
                password=sha.new('%s%s' % (fbapi.uid, self.secret_key)).hexdigest(),
                )

            # we store the facebook data in environ
            environ['repoze.who.fbuser'] = Params(user.items())
            return user

    def remember(self, environ, identity): pass
    def forget(self, environ, identity): pass

At the first time there is no user in our DB. So the user isn't really connected to our interface. The trick is to use a javascript callback to show a registration form to the user with fields pre-filled with Facebook's data.

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
  <head></head>
  <body>
   <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript">
   </script>
   <fb:login-button onlogin="facebook_onlogin();"></fb:login-button>
   <script type="text/javascript">
     FB.init("YOUR_API_KEY_HERE", "xd_receiver.htm");
     var facebook_onlogin() function() {
         // here is the trick
         window.location.href = '/register/facebook';
     }
   </script>
</body> </html>

When the form is submitted, you need to create the real user in your application. That's all !

collective.releaser rocks !

 

Tarek have made some great works on collective.releaser and collective.dist the last months. There is currently a refactor branch.

The goal of collective.releaser is to normalize the release process of a buildout based project.

I've worked on it the last week to implement the latest needed steps to allow an easy way to release a buildout project.

So here is how it works. collective.releaser came with a paster template to generate a buildout skel. You got a dev.cfg for development with a develop option with all your develop eggs in it and a prod.cfg for production without develop and all versions fixed in the versions section.

I've modified the project_release so you just need to run:

$ bin/project_release --version=1.0

This will release eggs founded in the dev.cfg's develop option on a pypi server and fix the versions in buildout.cfg. If no version is founded for an egg in the versions section of the prod.cfg then the egg is released at the version passed to the command line. If a version is founded then the egg is released at this version. Here is an example:

The dev.cfg:

[buildout]
parts = eggs

develop =
  ../collective.releaser
  ../my.project

[eggs]
recipe = zc.recipe.egg
eggs =
  collective.releaser
  my.project

The prod.cfg:

[buildout]
parts = eggs
versions = versions

[versions]
collective.releaser=0.6.1

[eggs]
recipe = zc.recipe.egg
eggs =
  collective.releaser
  my.project

collective.releaser package is already at 0.6.1 so no release is needed. my.project as no versions specified so a new 1.0 release is generated and uploaded on your pypi server.

The generated buildout.cfg will look like this:

[buildout]
extends = prod.cfg
versions = versions

[versions]
collective.releaser=0.6.1
my.project=1.0

After all eggs are released, project_release will generate a tarball with the tagged version of the project and all eggs founded in the develop option.

Then to upgrade your project you just need to untar the archive on your production server. Launch bin/buildout. And that's it.

This will probably make life easier for developers ! Hope we can make a new self generated release soon :)

Fetching packages from various vcs in your buildout

 

I've recently released a buildout extension to checkout packages in a buildout from various vcs.

Here is a buildout config used to install this blog's skin package. This checkout deliverance from svn and pyquery from his bitbucket repository:

[buildout]
newest=false
parts = eggs
index=http://pypi.zopyx.com/
develop=
    .
extensions=gp.vcsdevelop
vcs-extend-develop=
    svn+http://codespeak.net/svn/z3/deliverance/trunk#egg=deliverance
    hg+ssh://bb/olauzanne/pyquery/@tip#egg=pyquery

[eggs]
recipe=zc.recipe.egg
eggs=
    GawelOrgSkin

This extension is less than 100 lines of code. All checkout stuff is handle by pip which support many vcs.

Skinning with pyquery and deliverance

 

I'm using deliverance since a few month now to skin this blog and afpy.org. I also contribute to pyquery since i like the idea of manipulating xml in python like with jQuery. So the next step is to use a kind of pyquery rule in deliverance.

After looking at the deliverance code it seems that adding new rules is easy. You just need to register it in the deliverance rules. So here is the result:

from deliverance import rules
from pyquery import PyQuery as pq

class PyQuery(rules.AbstractAction):
    """PyQuery rule for deliverance"""
    name = 'pyquery'
    def __init__(self, source_location, callback=None):
        self.source_location = source_location
        self.callback = callback

    def apply(self, content_doc, theme_doc, resource_fetcher, log):
        """apply the rule"""
        self.callback(pq([content_doc]), pq([theme_doc]), resource_fetcher, log)

    @classmethod
    def from_xml(cls, tag, source_location):
        """Parses and instantiates the class from an element"""
        use = tag.attrib['use']
        modname, funcname = use.split(':')
        mod = __import__(modname, globals(), locals(), [''])
        callback = getattr(mod, funcname)
        return cls(source_location, callback)

# register the new rule
rules._actions['pyquery'] = PyQuery

Now i'm able to use my rule. I just need to import the above module before any deliverance one:

from myproject import pyquery_rule
from deliverance.middleware import DeliveranceMiddleware
# initialize your middleware

Then here is the rules:

<ruleset>
  <theme href="/theme.html" />
  <rule class="default">
      <pyquery use="myproject.rules:default" />
  </rule>
</ruleset>

Where myproject/rules.py look like this:

def default(content, theme, resource_fetcher, log):
    """rule used for testing"""
    content('title').text('My site - '+content('title').text())
    content('#footer').remove()
    theme('body').append(content('body'))

content and theme are pyquery objects. Feel free to do whatever you want with them. Enjoy !!

Notice that this work only with deliverance's trunk (or >=0.3).

Combine zc.buildout and pip benefits

 

I've just released a new zc.buildout recipe which allow to install packages with pip.

I am getting really excited about it because it has many advantages taken from both components.

  1. The recipe adds a virtualenv in the parts/ directory of your buildout then use this binary to generate executable python scripts. So you have a clean sandbox.
  2. The recipe is based on zc.recipe.egg#scripts so you can share your eggs between buildouts as usual.
  3. Of course, you can install some .pybundle files.
  4. You can build package from svn with the editables option.
  1. Each line found in the install option is the last part of a pip command. This allow you to build eggs with dependencies. For example to install lxml in a pure sandbox without libxml2 and libxslt installed you need Cython installed and this command line python setup.py install --static-deps to install lxml. This is easy with the recipe. Here is a sample configuration file for this case:
[buildout]
# the cache dir is used by buildout & pip
download-cache = download
parts = eggs

[eggs]
recipe = gp.recipe.pip

# eggs installed by pip (also add the Deliverance bundle)
install =
    Cython
    --install-option=--static-deps lxml==2.2alpha1
    http://deliverance.openplans.org/dist/Deliverance-snapshot-latest.pybundle

# eggs installed by zc.recipe.egg
eggs =
    Paste
    pyquery

That's all !! This works perfectly on my Mac OSX and should works on most system.

In fact zc.recipe.egg will work in most cases but when you need a clean sandbox and some extra options, gp.recipe.pip is a good alternative.