To continue my last post about reusable Django apps, I’d like to talk about setuptools, show you a way to install Django by using setuptools and propose the reintroduction into Django’s code, even if setuptools have been dumped by the developers not long ago due to manageability reasons. Features like entry points, egg-files, the develop command, automatic versioning and tight integration with the Python Package Index make it worth installing.
Reusable Django apps
I started django-reusableapps considering the widespread use of “setuptools” in highly dynamic Python web projects like Turbogears and Pylons and the still unresolved problem of a smart “Django apps” API. Being the responsible Google Summer of Code student for this topic, I now decided (some months after my GSoC) to go with “setuptools”, dump my GSoC code and rethink the whole reason why Django apps should be pluggable: DRY.
It’s very easy to develop a custom Django app nowadays, but quite hard to quickly create meshed up projects, manage production sites and share the involved app with a larger audience in a semi-automatic manner like the Python Package Index.
The entry points for example, enable Python applications (such as Django or any Django-based app) to provide hooks to other applications in an automatic but unobstrusive, hence powerful fashion – a perfect choice for an Application API, from my point of view.
django-reusableapps resembles the plugin loading mechanism of Trac and should be considered a very temporary solution, until Django supports app-loading from eggs natively.
Using setuptools with Django aps
To start with a “setuptools”-based Django installation you need a new setup.py file to replace the version provided by Django (note this currently only works with a current subversion checkout). Please delete the symbolic link or the django directory you might have created in the site-packages directory by following Django’s installation instructions.
Then, add the following text to the “setup.cfg” file in the django source directory:
[egg_info] tag_build = dev tag_svn_revision = 1Please have a look at Phillip’s comment, update your setup.py and setup.cfg files and reinstall Django.
This tells “setuptools” to generate version numbers like 0.97dev-r6706 during the installation, marking it as a developer snapshot which would be superseded by a 0.97 release, once it’s released.
You can then use virtually any command of setup.py: install, develop, bdist_egg, bdist_mpkg, bdist_wininst, bdist_rpm etc.
One bonus of the new setup.py file is the instant availablitiy of a Django command, the same utility known as django-admin.py and manage.py - just by declaring a “console_scripts” entry point in the “setup()” function:
entry_points = {
'console_scripts': 'django = django.core.management:execute_from_command_line',
},
Django’s SVN release and setuptools
There is an easy way with “setuptools” to keep track of the changes in Django’s svn tree. Use the develop command to avoid installing Django over and over again after each subversion update:
$ cd django_src $ sudo python setup.py develop ... Creating /Library/Python/2.5/site-packages/Django.egg-link (link to .) Adding Django 0.97dev-r6704 to easy-install.pth file Installed /Users/Jannis/Code/django_src ...
This tells “setuptools” to create a link in the “site-packages” directory to the current directory with the Django checkout. Updating Django with subversion is also easy:
$ cd django_src $ svn update U django/contrib/admin/templatetags/admin_list.py U django/template/defaultfilters.py U tests/regressiontests/templates/filters.py Updated to revision 6708. $ sudo python setup.py develop ... Creating /Library/Python/2.5/site-packages/Django.egg-link (link to .) Removing Django 0.97dev-r6704 from easy-install.pth file Adding Django 0.97dev-r6706 to easy-install.pth file Installed /Users/Jannis/Code/django_src ...
You need to run “python setup.py develop” after every update in order to keep the version string of Django in sync with the checkout. The already existing link to the “old” version (0.97dev-r6704) is then replaces by a link to the “new” version (0.97dev-r6706).
Django can be a dependency of a Django-based app
While you build a Django app, you might consider to include information about dependencies which are required to be installed by using setuptools’ install_requires keyword. Fortunately this also applies to Django developer snapshots, if originally installed with “setuptools” like I described above. Just add a keyword to “setup()” of your app’s “setup.py”:
install_requires = ['Django >= 0.97dev-r6706,==dev',],
This tells “setuptools” to install Django (if necessary) either by using a current subversion checkout or the newest release if available.
Django can have dependencies and extras
Django tries hard to have as less dependencies as possible by bundling some essential packages (e.g. simplejson) and informing users to install packages if required (e.g. Flup, PIL, PyYaml and all database adapters). These dependencies could also be defined in a setup.py file in the extras_require keyword:
extras_require = {
'MySQL': ["MySQLdb>=1.2.1p2"],
'SQLite': ["pysqlite>=2.0.3"],
'PostgreSQL': ["psycopg>=1.1.21"],
'PostgreSQL2': ["psycopg2>=2.0.5"],
'Oracle': ["cx_Oracle>=4.3.1"],
'PyYaml': ["PyYaml"],
}
Later, while installing Django, extras could be specified in square brackets — for example one or more extras:
$ easy_install Django[PostgreSQL] $ easy_install Django[SQLite, PyYaml] $ easy_install http://code.djangoproject.com/svn/django/trunk/
Now imagine the same functionality with Django-based apps. Think of installing a weblog and all its recommended dependencies by running:
$ easy_install ColtraneBlog[DefaultThemes]
I hereby propose the reintroduction of “setuptools” because of the great advantages while designing a future “Django Applications” API.
Why not add the app-loading mechanism of django-reusableapps to Django’s core. Specifically add it to django.db.models.loading or another module which looks for qualified apps, either depending on INSTALLED_APPS or the “django.apps” entry point.
comments
The django-reusableapps links throughout the article are wrong, they should point to http://code.google.com/p/django-reusableapps/ I believe.
Thanks Jonathan! It's fixed now..
You're missing a 'tag_build = dev' from that setup.cfg; you need your versions to be like "0.97dev-rNNNN" in order to be treated as older versions than a plain "0.97".
Thanks Phillip!
Django has its own function to determine the current SVN version (django.get_version). It takes a Django version string like (0, 97, 'pre') and returns something like u'0.97-pre-SVN-6708', which is unfortunately wrong, since it gives the last revision number of the whole SVN repository including branches and tag, not only from the trunk.
You are right, I forgot to change the version string to just including the pure version number (like 0.97) and let setuptools handle the rest. My setup.py file and the required keywords for setup.cfg are now modified. Thanks again!
With a little hack you can use setuptools with distutils setup.py's. Setuptools monkeypatches distutils.core.setup(), so anything run after you import setuptools gets all the setuptools features. To make use of this you can do:
python -c "import setuptools, os; __file__=os.path.abspath('setup.py'); execfile('setup.py')" develop
Or whatever command you want to run.
I didn't know that setuptools works in this way. So, it should be feasible to provide a setup.py file which elegantly falls back to distutils if setuptools can't be imported.
The only delicate issue is the package data, though distuils *has* support for "package_data" in 2.4, it's not Django's minimal Python version support 2.3.
So the only solution is the "data_files" keyword, automatically filled like in the current http://code.djangoproject.com/browser/django/trunk/setup.py and a "try: import except: ImportError"-switch, right?
Is there a way to get this working with a git checkout of django? For now it only creates a django0.97dev package, so no revision is included. Does setuptools check for .svn to get the info?
Yeah, it checks for the svn revision in get_svn_revision() (egg_info.py in setuptools). This is unfortunate and should be replaced by a pluggable interface. Could you please try <a href="http://pypi.python.org/pypi/setuptools-git/" rel="nofollow">setuptools-git</a>?
I guess the actual version string parser of setuptools needs increasing revision numbers, but don't know enough about git's internals. Did you make a clone of the Django repo with git-svn? Maby the "find-rev" command of git-svn could be used for this?
"git svn log --limit 1" did the trick for me (I cloned the repo), but if someone is using a git checkout from a git-svn repo, git svn log won't work, so I am using "git log" and search for git-svn-id :)
The patch at http://apolloner.eu/files/setuptools_git_svn.diff (for egg_info.py) tries to use the "git log" command if "tag_svn_revision" is true and a .git file/dir exists.
Now I am able to run python setup.py develop :)
Wow, this is great stuff. This should definitely be applicable to bzr and all the other versioning system with svn support.