2014-10-29

What is a Python decorator?

Suppose we have a function that returns the boiling point of water in celsius:
def boiling_point():
    return 100

print boiling_point() # returns 100
Suppose we are now at high altitude and the boiling point is now 97 degree. We can wrap the function like this:
def calibrate(f):
    def g():
        return f() - 3
    return g

def boiling_point():
    return 100

print boiling_point() # returns 100

boiling_point_calibrated = calibrate(boiling_point)
print boiling_point_calibrated() # returns 97
In fact, we can do better. We can assign the calibrated function back to the original function name. As a result, nothing is changed on the calling side:
def calibrate(f):
    def g():
        return f() - 3
    return g

def boiling_point():
    return 100

print boiling_point() # return 100

boiling_point = calibrate(boiling_point)
print boiling_point() # returns 97
When used in this way, the function calibrate() is called a decorator, and there is a shortcut for it:
def calibrate(f):
    def g():
        return f() - 3
    return g

@calibrate
def boiling_point():
    return 100

print boiling_point() # returns 97
Even better is for the offset to be configurable. The boiling point is dependent on the altitude after all. What we can do is to create a function which returns a function that decorates a function:
def calibrate_offset(offset):
    def calibrate(f):
        def g():
            return f() - offset
        return g
    return calibrate
    
@calibrate_offset(3)
def boiling_point():
    return 100

print boiling_point() # returns 97
Finally we can generalize the original function by adding *args and **kwargs to it:
def calibrate_offset(offset):
    def calibrate(f):
        def g(*args, **kwargs):
            return f(*args, **kwargs) - offset
        return g
    return calibrate
    
@calibrate_offset(3)
def boiling_point():
    return 100

print boiling_point() # returns 97
Lastly, decorator can be nested. What do you think x will be in the following case?
def calibrate_offset(offset):
    def calibrate(f):
        def g(*args, **kwargs):
            return f(*args, **kwargs) - offset
        return g
    return calibrate
    
@calibrate_offset(3)
@calibrate_offset(4)
def boiling_point():
    return 100

x = boiling_point()

2014-10-12

Django user login & logout signals

I would like to do something when a user logs in and out from my Django 1.7 project. The best way to do it is to take advantage of the Django Authentication System Login and Logout signals.

First I need to define the signal handlers in a signals submodule in one of Django application. (Note 1) In this case, I just write a log entry whenever a user logs in or logs out.
=== myapp/signals.py ===

from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.dispatch import receiver
from django.conf import settings

@receiver(user_logged_in)
def sig_user_logged_in(sender, user, request, **kwargs):
    logger = logging.getLogger(__name__)
    logger.info("user logged in: %s at %s" % (user, request.META['REMOTE_ADDR']))

@receiver(user_logged_out)
def sig_user_logged_out(sender, user, request, **kwargs):
    logger = logging.getLogger(__name__)
    logger.info("user logged out: %s at %s" % (user, request.META['REMOTE_ADDR']))
Then I create an application config in an apps submodule and override the Ready() method to register the signals. (Note 2)
=== myapp/apps.py ===

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'MyApp'
    verbose_name = "My Application"

    def ready(self):
        import myapp.signals # register the signals
Finally I put 'myapp.apps.MyAppConfig' in the INSTALLED_APPS setting. (Note 3)
=== myproject/settings.py ===

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp.apps.MyAppConfig',
)
The log will look like something similar to the following.
=== app.log === 

[2014-10-09 05:08:28,372] INFO [myapp.signals:10] user logged in: david at 127.0.0.1
[2014-10-09 05:08:29,791] INFO [myapp.signals:15] user logged out: david at 127.0.0.1

2014-10-06

Migrating from Django 1.6 to 1.7

Recently I upgraded a simple Django project from Django 1.6 (running on Python 2.7) to Django 1.7 (running on Python 3.4). Below are some of the steps taken.
1. Switch to new syntax of relative import

OLD> from local_setting import *
NEW> from .local_setting import *

2. Change HttpResponse() argument from mimetype to content_type

OLD> response = HttpResponse(mimetype='text/csv')
NEW> response = HttpResponse(content_type='text/csv')

3. Specify binary mode in open() to avoid UnicodeDecodeError

OLD> with open("binary_file") as
NEW> with open("binary_file", "rb") as

4. Replace __unicode__() with __str__()

OLD> def __unicode__(self):
NEW> def __str__(self):

I also updated my .vimrc to exclude the __pycache__ directories from showing up in netrw.
=== ~/.vimrc ===

let g:netrw_list_hide= '.*\.pyc$,.*\.swp$,__pycache__'