2018-12-08

Flutter Development in Chromebook

In November 2018, I am excited to have my HP Chromebook set up for Flutter development with VS Code. I am sharing here how I did it:


  1. Enable Chromebook Developer Mode
  2. Enable Linux in ChromeOS
  3. Install Flutter
    1. Download tar file from Flutter website
    2. Move tar file to Linux partition
    3. Untar (no need to run any install script)
  4. Download and unzip Android Studio, and install Flutter plugin
  5. Enable Android Developer Mode in ChromeOS
    1. Chromebook settings
    2. Google Play Store
    3. Manage Android Properties
    4. About
    5. Device
    6. Build (tap 7 times)
  6. Enable Android ADB Debugging
    1. Chromebook settings
    2. Google Play Store
    3. Manage Android Properties
    4. Developer Options
    5. ADB Debugging
  7. Run ~/Android/Sdk/platform-tools/adb connect 100.115.92.2:5555
  8. Run ~/Android/Sdk/platform-tools/adb devices
  9. Download VS Code .deb package, double click to install
  10. Install VS Code Flutter plugin
  11. VS Code: locate Dart-SDK at ~/flutter/bin/cache/dart-sdk

2016-02-03

My Filezilla client application takes forever (a.k.a several minutes) to start up if the last local directory is a remote share which is not available any more, possibly waiting for time out.

A way to fix this is to patch the Filezilla configure file ~/filezilla/filezilla.xml.

The relevant section is:

<FileZilla3>
    <Settings>
        ...
        <Setting name="Last local directory">/mnt/remote/share/</Setting>
        ...
    </Settings>
</FileZilla3>

I need to change the Last local directory value to some local directory such as "/".  That can be done easily with xmlstarlet.

$ xmlstarlet edit --update '//Setting[@name="Last local directory"]' --value "/" ~/.filezilla/filezilla.xml


Basically you ask xmlstarlet to edit filezilla.xml by updating the value of a "Setting" node with the name "Last local directory" to "/".

Of course you will have to write the output back to filezilla.xml.

You can install xmlstarlet with apt-get:

$ sudo apt-get install xmlstarlet


I am using:
- Ubuntu 14.04
- Filezilla Client 3.7.3

2015-12-14

Auto loading user-defined models in Django shell

I would like the Django shell to auto-load all user-defined models on startup without the 
use of a third-party app. Below is that I did.

[00] # FILE .djangoshellrc
[01] # alias m='PYTHONSTARTUP="path/to/.djangoshellrc" python manage.py'
[02] 
[03] import sys
[04] 
[05] from django.apps import apps
[06] 
[07] def load_models():
[08]     try:
[09]         models = [m for m in apps.get_models() if not m.__module__.startswith('django')]
[10]         for model in sorted(models, key=lambda x: x.__name__):
[11]             name = model.__name__
[12]             globals()[name] = model
[13]             print("[+] imported %s" % name)
[14]     except:
[15]         print("[*] %s" % sys.exc_info()[0])
[16]         print("[*] %s" % sys.exc_info()[1])
[17] 
[18] load_models()

Line 00-01: To use this script, create a bash alias to start the Django shell, with 
this script specified in the PYTHONSTARTUP environ variable.  In other words, this 
script will be run on startup as long as the alias is used to start the Django shell.

Line 03-05: All the necessary imports.

Line 09: Ask Django to provide a list of all the app models, with the django built-in 
models filtered out.

Line 10: Iterate the models list after sorting them based on the model names.

Line 11-13: Add the models to the globals dict with the model name as the key,
effectively doing a "from <app>.models import <Model> in the Django shell."

Hope this script is useful to you.

2015-12-07

Comparing changes in a Django form

With the help of the Form.changed_data list, the Form.initial dict, and the Form.cleaned_data dict, Django makes it easy to compare changes in a form.
For example, we can override the form_valid method in an UpdateView to print the old and new values of all the changed fields as below:
def form_valid(self, form):
    for fieldname in form.changed_data:
        print(fieldname)
        print("old=%s" % form.initial[fieldname])
        print("new=%s" % form.cleaned_data[fieldname])
    return super().form_valid(form)

References:
Form.changed_data
Form.initial
Form.cleaned_data

2015-11-30

ReportLab Chinese Support

I need to generate a PDF with Chinese characters in a Django project. Generating a PDF is straightforward as ReportLab does the job well. The hard part is to make sure the user can read the Chinese characters in the PDF.
At the end I settled in using the Chinese font provided by the Adobe's Asian Language Packs. That way, if no usable Chinese font is available, the user can download and install the Chinese font on demand automatically. This approach offers good performance since nothing is embedded in the PDF.
The following is the code snippet in registering the Chinese font for ReportLab to use.
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
font_chinese = 'STSong-Light' # from Adobe's Asian Language Packs
pdfmetrics.registerFont(UnicodeCIDFont(font_chinese))
...
canvas.setFont(font_chinese)

2015-11-22

Django Form - Confirm Before Saving (Part 2)

In Part 1 of this post, we create a form which conditionally shows a checkbox for user confirmation. The implementation is to raise an validation error when a condition is met. That does not work well with multiple confirmations, as the checking stops when the first condition is met. We need a way to check all conditions without raising an validation error, with one checkbox to confirm them all. We can do that with Form.add_error()
Instead of raising an validation error like in Part 1 of this post:
class InvoiceForm(forms.ModelForm):
    ...
    def clean(self):
        super().clean()
        ...
       if all((key in self.cleaned_data for key in ['date', 'confirm'])):
           if self.cleaned_data['date'].weekday() == 6 and not self.cleaned_data['confirm']:
                raise forms.ValidationError(MSG001)
We add an validation error when a condition is met:
class InvoiceForm(forms.ModelForm):
    ...
    def clean(self):
        super().clean()
        ...
        if 'confirm' in self.cleaned_data and not self.cleaned_data['confirm']:
            if 'date' in self.cleaned_data:
                d = self.cleaned_data['date']
                if d.weekday() == 6:
                    self.add_error(None, forms.ValidationError(CONFIRM_MSG[1]))
                if (datetime.date.today() - d).days > 14:
                    self.add_error(None, forms.ValidationError(CONFIRM_MSG[2]))
In the example above, a confirmation is required if either the invoice date falls on a Sunday or the invoice date is more than 14 days in the past. CONFIRM_MSG is a dict of confirmation messages such as the following:
CONFIRM_MSG = {
    1: "The invoice date does not fall on a Sunday.  Please check confirm to proceed.",
    2: "The invoice date is more than 14 days in the past.  Please check confirm to proceed.",
}
In the Form.__init__() method, we check for existence of any of the confirmation messages to show the checkbox. Note the use of the Form.non_field_errors() method to retrieve all non-field errors, such as those added in the Form.clean().
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        ...
        confirm_set = set(CONFIRM_MSG.values())
        if confirm_set.intersection(self.non_field_errors()):
            self.fields['confirm'].widget = forms.CheckboxInput()

2015-11-17

Django Form - Confirm Before Saving (Part 1)

How can I ask the user to confirm before saving a form if the form meets certain conditions? I tried different approaches and settled on the following.
This example is about saving an invoice. I would like to ask the user to confirm (by checking a checkbox) before saving an invoice if the invoice date falls on a Sunday. The checkbox will only appear if confirmation is required.
Step 1: Add a checkbox in the form as a hidden input.
class InvoiceForm(forms.ModelForm):
    confirm = forms.BooleanField(initial=False, required=False, widget=forms.HiddenInput)
    class Meta:
        model = Invoice
        fields = ('data', ..., 'confirm',)
Step 2: Check for the condition in the form.clean() method. The required data are retrieved from the form cleaned_data dictionary. Note that it is a straightly a read only access. If the condition is met, raise a validation error thereby invalidating the form.
MSG001 is a string constant used as the error message. For example something like "The invoice date does not fall on a Sunday. Please check confirm to proceed."
class InvoiceForm(forms.ModelForm):
    ...
    def clean(self):
        super().clean()
        ...
        if all((key in self.cleaned_data for key in ['date', 'confirm'])):
            if self.cleaned_data['date'].weekday() == 6 and not self.cleaned_data['confirm']:
                raise forms.ValidationError(MSG001)
Step 3: Check for the error condition in the __init__() method of the form, and dynamically change the checkbox from a hidden input to a regular checkbox on the fly. This effectively adds a checkbox to the form and unless it is checked, the form is invalid.
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        ...
        if any(e == MSG001 for e in self.errors.get('__all__', [])):
            self.fields['confirm'].widget = forms.CheckboxInput()
Step 4: In the HTML template, add conditional logic to show the checkbox either as a hidden input or as a checkbox as appropriate.
{% if form.confirm in form.hidden_fields %}
  {{ form.confirm }}
{% else %}
  {{ form.confirm.label_tag }}{{ form.confirm }}
{% endif %}
Final thoughts:
This approach does not require AJAX or any third party packages. The reason is to minimize Javascript knowledge and external dependency.
This approach uses validation error as a signal to add a checkbox to the form.
This approach modifies a form field dynamically in the form.__init__() method.
Also, in another approach, I tried to to update the form.cleaned_data dict without success. The changes I made are lost somewhere in the call chain. It is evident that I do not have sufficient knowledge in how Django forms work.
Continue to Part 2 of this blog for working with multiple confirmations.

2015-07-20

Abstract methods in Python3

You can use the Abstract Base Classes to implement abstract methods in Python. For example, if you have an Animal class and a Cat class like the following:
class Animal():
    def speak(self):
        print("Animal speak")

class Cat(Animal):
    pass

class Doc(Animal):
    pass

Cat().speak() # returns "Animal speak"
Dog().speak() # returns "Animal speak"
You can turn Animal.speak() to an abstract method with a metaclass and a decorator:
import abc

class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def speak(self):
        print("Animal speak")

class Cat(Animal):
    def speak(self):
        print("Cat speak")

class Dog(Animal):
    pass

Cat().speak() # returns "Cat speak"
Dog().speak() # returns "TypeError: Can't instantiate abstract class Dog with abstract methods speak"
Note that although the Animal.speak() method is always shadowed by its subclass method, it can still be called explicitly by using super():
class Cat(Animal):
    def speak(self):
        super().speak()

Cat().speak() # returns "Animal speak"

2015-07-13

Add a button to export the search results in Django - Part 2

Refering to a previous post, it turns out that using str() to serialize the QuerySet query is not a viable solution if one of the selection criteria is a date. The str() will not quote the date value resulting in an invalid SQL statement.
Using pickle to serialize the query solves the problem.
# Replace str()
request.session['search_sql'] = str(Person.objects.filter(**kw).query)

# with pickle.dumps()
request.session['search_query'] = pickle.dumps(Person.objects.filter(**kw).query)
When the time comes to re-execute the query:
# Rather than executing a raw sql statement:
sql = request.session.get('search_sql')
object_list = Person.objects.raw(sql)

# deserialize the saved query and assign it to the QuerySet instead:
saved_query = request.session.get('search_query')
query = pickle.loads(saved_query)
object_list = Person.objects.all()
object_list.query = query
Although less secured, in order to store the query object in the request session, have to use the pickle based session serializer instead of the JSON based counterpart in Django 1.7.
=== settings.py ===
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'

2015-07-06

Set the width of an input field in an inline formset

To set the width of an input field in an inline formset, you can specify the widget and its attributes in the inlineformset_factory call. For example:
 LineFormSet = forms.models.inlineformset_factory(Document, DocumentLine, 
-                  fields=['seqno', 'sku', 'desc', 'qty', 'unit_price'])
+                  fields=['seqno', 'sku', 'desc', 'qty', 'unit_price'], 
+                  widgets={
+                    'seqno': forms.TextInput(attrs={'size':'10'}),
+                    'sku': forms.TextInput(attrs={'size':'10'}),
+                    'desc': forms.TextInput(attrs={'size':'50'}),
+                    'qty': forms.TextInput(attrs={'size':'10'}),
+                    'unit_price': forms.TextInput(attrs={'size':'10'})})
Reference: https://docs.djangoproject.com/en/1.7/topics/forms/modelforms/#specifying-widgets-to-use-in-the-inline-form

2015-06-29

Automatic version numbering with Mercurial hook - Part 2

The pre-commit hook commands in the last post only works in Linux or Mac. I need to run it in Windows as well. Instead of converting it to MS-DOS commands, I found it easier to write an external python script as the hook handler, as it will work in Windows, Linux and Mac.
Step 1 - Modify the hook to run an external script.
# .hg/hgrc

[hooks]
pre-commit.version = python:./script/hghooks.py:increment_version
Step 2 - Create a python module with the handler function.
It basically uses the subprocess module to get the local revision number, strip any ending newline or plus sign, add one to it, and then write it out to the version.py file. Note that we obtain the repo root folder from the repo object passed into the hook handler, so that we don't have to hardcode the path to the version file.
# ./script/hghooks.py

import os.path
import subprocess

def increment_version(*args, **kw):
    repo = kw['repo']
    version = int(subprocess.check_output(["hg", "id", "-n"]).strip().strip("+")) + 1
    setting = "VERSION = %s" % version
    file = os.path.join(repo.root, "dms/dms/version.py") 
    with open(file, "w") as fp:
        fp.write(setting)
    print(setting)
Using an external script makes it not only cross-platform, but also version control friendly because it is not stored in the .hg folder!

EDITED 2015-07-09 In order to take care of multiple heads, the increment_version() is revised as follow:

# ./script/hghooks.py

import os.path
import subprocess

def increment_version(*args, **kw):
    """hg id -n can returns:
    145      # no uncomitted changes
    145+     # with uncomitted changes
    146+145+ # multi-heads"
    The strip() and split() will take care of all the above cases.
    """
    repo = kw['repo']
    version = int(subprocess.check_output(["hg", "id", "-n"]).strip().split("+")[0]) + 1
    setting = "VERSION = %s" % version
    file = os.path.join(repo.root, "pms/pms/version.py") 
    with open(file, "w") as fp:
        fp.write(setting)
    print(setting)
EDITED 2015-07-15
The script is further enhanced by removing the hard-coded file path to the Mercurial project config file, and add a test to make sure something has changed indeed before incrementing the version number. The final version of the script is listed below, and is also available in Bitbucket.
import os.path
import subprocess

def increment_version(ui, repo, **kw):
    """
    An in-process Mercurial hook to increment a version setting.

    FILE .hg/hgrc

    [hooks]
    pre-commit.version = python:path/to/hghooks.py:increment_version

    [hook_params]
    version.file = path/to/version.py

    ENDFILE

    hg id -n can returns:
        145      # no uncomitted changes
        145+     # with uncomitted changes
        146+145+ # multi-heads"
        The strip() and split() will take care of all the above cases.
    """
    has_changes = subprocess.check_output(["hg", "stat"])
    if has_changes:
        version = int(subprocess.check_output(["hg", "id", "-n"]).strip().split("+")[0]) + 1
        setting = "VERSION = %s" % version
        path = ui.config('hook_params', 'version.file', default='version.py', untrusted=False)
        if not os.path.isabs(path):
            path = os.path.join(repo.root, path) 
        with open(path, "w") as fp:
            fp.write(setting)
        print(setting)

2015-06-22

Automatic version numbering with Mercurial hook

Assuming you put your version number setting in a version.py file as VERSION = NNN, you can add the following to the project .hg/hgrc to automatically increment the version number upon each commit. The version number is actually the local revision number before the commit plus 1, although not exactly robust but is generally increasingly upward. Any conflict should be resolved manually.
# .hg/hgrc
[hooks]
pre-commit.version = echo VERSION = $((`hg id -n | tr -d +` + 1)) > ./path/version.py
We use Mercurial pre-commit hook to update the version number in the file version.py. When the hook is run, the current directory is set to the repo's root directory. We get the local revision number with "hg id -n", remove the trailing "+" if any, and then add one to it as the version number. Please note that the hook is pre-commit not precommit. Both hooks exist, but if you use the precommit hook, you will not get what you want. See this post for details.
EDITED 2015-07-06: See Part 2 for a different approach.

2015-05-25

PyQt5 Password Dialog

The following is the complete source code showing how to program a password input dialog in PyQt5. The password dialog is based on QInputDialog, with the QLineEdit.Password set as the echo mode. Note that we do not need the QApplication to provide an event loop in this simple demonstration.
import sys

from PyQt5.QtWidgets import QApplication, QInputDialog, QLineEdit

def getPassword():
    text, ok = QInputDialog.getText(None, "Attention", "Password?", 
                                    QLineEdit.Password)
    if ok and text:
        print("password=%s" % text)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    getPassword()



2014-12-30

How Django shows the “It worked!” page?

If you do the following, you will see a “It worked!” page at http://127.0.0.1:8000

$ django-admin.py startproject myproject
$ python manage.py runserver

Let's find out how it is done.

First search the django package to find the phrase “It worked!”. Assuming you are at the root of the django package:

$ grep -ri "it worked!" --include=*.py .
./views/debug.py:  <h1>It worked!</h1>

Look into debug.py and you will see “It worked!” is defined in the string “DEFAULT_URLCONF_TEMPLATE”.

“DEFAULT_URLCONF_TEMPLATE” is used in a module level function “default_urlconf()” in the same file.

The “default_urlconf()” function is called by another module level function “technical_404_response()” in the same file.

Portion of “technical_404_response() is listed below:

    try:
        tried = exception.args[0]['tried']
    except (IndexError, TypeError, KeyError):
        tried = []
    else:
        if (not tried # empty URLconf
            or (request.path == '/'
                and len(tried) == 1 # default URLconf
                and len(tried[0]) == 1
                and getattr(tried[0][0], 'app_name', '') == 
                        getattr(tried[0][0], 'namespace', '') == 'admin')):
            return default_urlconf(request)

You can see that it checks for the special case of hitting the root url “/” and filtering out the admin site.

Let's do a search on technical_404_response():

$ grep -ri "technical_404_response" --include=*.py .
./views/debug.py:def technical_404_response(request, exception):
./contrib/staticfiles/handlers.py:   return debug.technical_404_response(request, e)
./core/handlers/base.py:             response = debug.technical_404_response(request, e)

The relevant file is “base.py”. In the definition of class BaseHandler(object) in base.py, if the DEBUG setting is True, then it will call technical_404_response() and eventually returns the “It worked!” page, as illustrated in the code fragment below.

        except http.Http404 as e:
            logger.warning('Not Found: %s', request.path,
                        extra={
                            'status_code': 404,
                            'request': request
                        })
            if settings.DEBUG:
                response = debug.technical_404_response(request, e)

2014-12-23

How to install a python package for one particular webapp in WebFaction

Both of the following examples are based on a Django 1.7 / Python 3.4 app.

Example 1: ReportLab
$ pip3.4 install --user -U setuptools
$ cd ~/webapps/APP
$ PYTHONPATH=$PWD/lib/python3.4 pip3.4 install \
  --install-option="--install-scripts=$PWD/bin" \
  --install-option="--install-lib=$PWD/lib/python3.4" \
  reportlab==3.1.8

$ python3.4 manage.py shell
>>> import reportlab
>>> reportlab.__file__ # to verify
Example 2: django-taggit
$ pip3.4 install --user -U setuptools
$ cd ~/webapps/APP
$ PYTHONPATH=$PWD/lib/python3.4 pip3.4 install -t $PWD/lib/python3.4 \
  django-taggit

$ python3.4 manage.py shell
>>> import taggit
>>> taggit.__file__ # to verify
The exact method to use depends on how the package is built.

2014-12-16

Ever wondering what does {% load staticfiles %} mean?

It means Django will loop through all INSTALLED_APPS directory to look for a “templatetags” subdirectory.

If found, it will in turn look for a “staticfiles.py” file inside the templatetags subdirectory.

If staticfiles.py is found, then it will import it.

For example, if django.contrib.staticfiles is in the INSTALLED_APPS, then
{% load staticfiles %}
will import the following file:
django/contrib/staticfiles/templatetags/staticfiles.py
There is a module level function called “static” defined in staticfiles.py, which is the “static” in the {% static %} template tag.

2014-12-02

How Django deals with methods renaming

A RenameMethodsBase metaclass is defined in the django.utils.deprecation module. It seems that Django uses it to deal with methods renaming. Below is a simplified explanation of how it works.

First we subclass the RenameMethodsBase to specify the old and new method names, as well as the type of warning. Then we use it as a metaclass on two classes Field1 and Field2.
import warnings
warnings.simplefilter("always")

from django.utils.deprecation import *

class RenameFieldMethods(RenameMethodsBase):
    renamed_methods = (
        ('_has_changed', 'has_changed', DeprecationWarning),
    )

class Field1(metaclass=RenameFieldMethods):
    def _has_changed(self):
        print('_has_changed')

class Field2(metaclass=RenameFieldMethods):
    def has_changed(self):
        print('has_changed')
If we instantiate Field1 and call both old and new methods, the following results:
>>> f1 = Field1()
>>> f1.has_changed()
_has_changed
>>> f1._has_changed()
__main__:1: DeprecationWarning: `Field1._has_changed` is deprecated, use `has_changed` instead.
_has_changed
On the other hand if we instantiate Field2 and call both old and new methods, the following results:
>>> f2 = t2.Field2()
>>> f2.has_changed()
has_changed
>>> f2._has_changed()
__main__:1: DeprecationWarning: `Field2._has_changed` is deprecated, use `has_changed` instead.
has_changed
In short, both methods will succeed, but if called by the name of the old method, a warning is issued.

2014-11-09

Surprise in Django render()

The doc says render() (Note 1) is the same as a call to render_to_response() with a context_instance argument that forces the use of a RequestContext.

It happens that render() takes a request object as its first argument, and a context_instance and a dictionary as two of its optional arguments. The context_instance is the context instance to render the template with. By default, the template will be rendered with a RequestContext instance (filled with values from request and dictionary).

As a Django beginner, I naively assume the request object will be automatically available in the template as I pass it as the first argument in the first place, and also because the context_instance argument forces the use of a RequestContext instance which is filled with values from the request object. Turns out that is not the case.

The request object is for the construction of the RequestContext only, which by default (as of Django 1.7) does not include the request object itself (Note2). It took me some time to figure that out.

To have the request object available in the template, I will have to pass it to render() in the dictionary argument:
    return render(request, template_name, {'request':request})
Or enable the request context processor at the settings, such that the request object will be automatically added to every RequestContext instance:
=== settings.py ===

from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS += (
    'django.core.context_processors.request',
)

2014-11-02

Appending to Django default settings

You can have a very minimal settings.py file in Django 1.7. Have you ever wondered how Django provides all the default settings?

All the default settings are defined in the module django/conf/global_settings.

There is usually no need to access the global_setting directly as explained in the doc..

The settings object will automatically get the default from the global_setting module on demand:
from django.conf import settings

if settings.DEBUG:
    # Do something
Or you can override a setting in settings.py like below:
DEBUG = True
However, if you ever want to add something to an existing setting instead of replacing it, you will need to do something like below in settings.py:
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS += (
    'django.core.context_processors.request',
)

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()