Packaging a Django reusable app

May 5, 2010 Tags: django, python, packaging

I suck less at packaging.

Yesterday I released an update on Pypi for a Django reusable app I wrote: django-geoportail. Actually, I released two updates because the first one wasn't fully operational and it makes me think all the previous releases weren't either. First lesson learned: check the packages you upload to pypi actually work.

So, I had to learn the hard way how to package a Django app. The biggest difference with a standard python package is that you may want to include non-python files, such as media files and templates. Let's have a look at a basic setup.py:

# -*- coding: utf-8 -*-
from distutils.core import setup

setup(
    name='django-geoportail',
    version='0.3.1',
    author=u'Bruno ReniƩ',
    author_email='bruno.renie.fr',
    packages=['geoportal'],
    url='http://bitbucket.org/bruno/django-geoportail',
    license='BSD licence, see LICENCE.txt',
    description='Add maps and photos from the French National Geographic' + \
                ' Institute to GeoDjango',
    long_description=open('README.txt').read(),
    zip_safe=False,
)

And here is the structure of what I want to package:

geoportal/
|-- admin.py
|-- forms
|   |-- fields.py
|   |-- __init__.py
|   `-- widgets.py
|-- __init__.py
|-- models.py
|-- templates
|   |-- geoportal
|   |   |-- map.html
|   |   `-- widget.html
|   `-- gis
|       `-- admin
|           |-- geoportal.html
|           `-- geoportal.js
|-- templatetags
|   |-- geoportal_tags.py
|   `-- __init__.py
|-- tests.py
`-- utils.py

I was naively thinking that having packages=['geoportal'] in setup.py plus including the static files in a MANIFEST.in file would magically include everything. Well, the template files are included in the source distribution, but they are skipped at the time of the installation. Even worse, sub-packages are skipped as well, leaving me without the forms and the templatetags directories.

The solution for including sub-packages is to use find_packages:

from distutils.core import setup
from setuptools import find_packages

setup(
    name='django-geoportail',
    # ...
    packages=find_packages(),
)

find_packages will look for every directory containing a __init__.py file and include it.

Next, the templates. Two things are needed to include them:

  • add include_package_data=True in the setup parameters

  • create a MANIFEST.in file to declare the files to include:

    include AUTHORS CHANGES README INSTALL LICENCE
    recursive-include geoportal *.py *.html *.js
    include docs/Makefile
    recursive-include docs/source *
    

    Here, I include all the HTML and javascript file under the geoportal directory, the standard authors / changes / readme files and the documentation. During installation, only the files in a python package will be copied to the site-packages. The docs and text files at the root level are just useful for people who manually download the tarball: one unzipped, it provides all the documentation and information about the package.

Finally, I like the zip_safe=False option. It prevents the package manager to install a python egg, instead you'll get a real directory with files in it. I find it very convenient for debugging, when some information can be found by looking at the code.

If you're a packaging expert and think I'm doing anything wrong, I'd like to read your thoughts!

UPDATE 2010-06-08: in the first version of this post, I explained that I didn't understood what the MANIFEST.in file was for. This is now clarified, include_package_data can only work if some data is included in the MANIFEST.in file.

Comments

August 16, 2010Fabien

Thanks to you, it saved a lot of my time :)

September 3, 2010Jens Hoffmann

Thanks a lot for this quick and good howto!

Add a comment

Comments are closed for this entry.

Short URL

http://bruno.im/e8