Python packaging: custom scripts

In the post Python Packaging: setuptools and eggs, I described how to use setuptools to create a distributable egg.  Installing the egg would provide:

  1. a set of python packages and modules, usable as a library
  2. a set of “scripts”: small programs that live in the user’s bin directory, where ever that is; these, for the most part invoke functions within the packages

While setuptools can generate these scripts for us, sometimes that does not cut it. Maybe we really do want to create scripts ourselves and get them installed in the user’s `bin` directory.

Writing scripts yourself

In the last post we got setuptools to generate three scripts for us: rundog, rungendibal, runbjarne. Let us hand-craft a script that does the work of any of these, depending on a command line argument. Let’s call our script ‘runany’: it will take one argument—“dog”, “gendibal”, or “bjarne”—and run the appropriate script for the argument. Here is our script:

# scripts/runany
import sys, os
os.execlp("run" + which)

And we tell setuptools (actually, the underlying distutils) about it:


Now, we can build our egg. When the user installs the egg, he would have the following scripts available:

  • rundog, rungendibal, runbjarne: these are auto-generated by setuptools
  • runay: this is a wrapper script that setup’s Pythons path and calls our own script above.

The user can run our runany script like this:
$ runany dog
Bow, wow!
$ runany bjarne
Hello, C++ World!

Wrapper code

Let’s see what setuptools did. First, it put a runany script in the user’s path. What’s inside?

$ cat `which runany`
# EASY-INSTALL-SCRIPT: 'Speaker==0.2dev','runany'
__requires__ = 'Speaker==0.2dev'
import pkg_resources
pkg_resources.run_script('Speaker==0.2dev', 'runany')

If you were expecting to see the code we wrote above and are surprised, join the club. This script uses the pkg_resources.run_script to actually call our code. The rest is just ensuring that the proper version of the egg is being referenced.

Why does it do so? So that the user can have multiple versions of our package installed and be able to use them.

Where is our script anyway? It is here:

$ ls /lib/python2.5/site-packages/Speaker-0.2dev-py2.5-linux-x86_64.egg/EGG-INFO/scripts/

where install_dir refers to where ever the user wanted our egg to be installed.

In conclusion, we wrote some custom code, told setuptools where to find it (using `scripts=[…]` directive in, created an egg, installed it, and everything works.

Non-Python scripts

You might have noticed above that the custom script we wrote was in Python. This is not an accident: the distutils/setuptools directive `scripts=…` that we used to get our stuff packaged and installed expects to be given python scripts. Actually, distutils can handle non-Python scripts: it will copy them verbatim without wrapping them or adjusting them in anyway. However, setuptools always wraps scripts in a way that non-Python scripts are not supported. This can be considered a bug in setuptools. So, when using setuptools, non-Python scripts are out. This is not so bad: why would you want to write a shell or some other script for doing something that can be done in Python?

Non-package Data Files

What if you wanted an init-script to go into `/etc/init.d/’, or some config file to go into ‘/etc/’? setuptools won’t do that for you either. Setuptools will keep your files bundled inside the egg. But you can provide the user with a script that will extract and copy the files to arbitrary locations, on their discretion. See

Post a comment or leave a trackback: Trackback URL.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: