2016-04-27

Using virtualenv with a Python CGI script

I've been learning Python recently, and I today I found myself writing a CGI script to run under Apache.  The only snag was that I wanted to use a specific package (Template Toolkit) and I didn't want to install it system-wide.  To complicate matters, I want to use Python 2.7, without disrupting the system installation of Python 2.6.

The Python community have several ways to do this: one is to install the package just for one user (pip install --user); and another is to use a virtual environment.

I didn't want to do the user-level installation.  The apache user is special, having no (default) shell and /var/www as home.  I don't want to start messing around with /var/www for the benefit of one CGI script.  And I want to be able to test my script from the command-line without having to be the apache user.

So, virtual environments seem to be the way to go, but how do I configure a CGI script to use a virtual environment?  Using the hashbang line to point at the python executable within the virtual environment directory doesn't work.  It just complains about missing shared libraries.  Virtual environments can be entered by sourcing a script, or by writing complex code within the script to mess around with the environment.

I found lots of documentation about setting up Apache to provide Django web services, and that would probably have worked.  But I wanted to get my simple CGI script up and running without having to learn a whole new web services architecture.

The solution I eventually settled on was to put a small Bash script into the virtual environment's bin directory:

#!/bin/bash
DIR=$(dirname $0)
source $DIR/activate
scl enable python27 "$DIR/python \"$@\""

And then I change the hashbang line in the CGI script to point at this wrapper:

#!/home/username/python/apache/bin/wrapper.sh

Hey presto!  The script runs using Python 2.7 and my virtual environment.  I can test using the same virtual environment.

I'm publishing this here in case someone else is looking to solve the same problem.

Update: I found that this approach doesn't work quite as well for Python 3.5, but the following does:
#!/bin/bash
DIR=$(dirname $0)
source /opt/rh/rh-python35/enable
source $DIR/activate
python "$@"

Update: Changed both scripts to use "$@" in place of plain $@.  This means that quoted arguments remain quoted, which is what you want here, say if you want to use -c.

2 comments:

  1. dirname stands for which directory's name?

    ReplyDelete
  2. dirname is a command. In this case it is finding the virtual environment's bin directory.

    ReplyDelete