George V. Reilly

RunSnakeRun (wxPython) apps in a Brew Virtualenv

RunSnakeRun logo

I’m doing some Python profiling and I wanted to use the Run­SnakeRun utility to view the profile data. Un­for­tu­nate­ly, that’s not straight­for­ward on Mac OS X if you use a virtualenv, and it’s even less easy if you’re using the Python installed by the Homebrew (brew) package manager.

There are several problems:

Installing wxPython

I downloaded wxPython3.0-osx-3.0.2.0-cocoa-py2.7.dmg, released in November 2014.

If you open the DMG and attempt to run the PKG, you will likely get a misleading error message from OS X:

“wxPython3.0-osx-cocoa-py2.7.pkg” is damaged and can’t be opened. You should eject the disk image.

Open the System Pref­er­ences app, click Security & Privacy, then General. Click the Lock icon to make changes, then change Allow apps downloaded from to Anywhere. Now try running the wxPython PKG again to install wxPython. Afterwards, you should reset Allow apps downloaded from to its previous value.

Installing and running wxPython in a virtualenv

As I learned from earlier work by Mike Soulier and Robin Dunn, there are two issues in running wxPython apps in a virtual en­vi­ron­ment.

First, you need the wx.pth path con­fig­u­ra­tion file to be in your virtualenv’s site-packages directory. The solution is to copy the installed wxredirect.pth.

Second, the Python binary installed in the virtualenv is not a framework binary, and so wxPython cannot access the display or create GUI objects. The solution is to set PYTHONHOME to the virtualenv, then use the framework binary to run the wxPython app.

The following has been tested with

#!/bin/bash -ex

WXPYTHON_APP="runsnakerun/runsnake.py"
PYVER=2.7

if [ -z "$VIRTUAL_ENV" ] ; then
    echo "You must activate your virtualenv: set '$VIRTUAL_ENV'"
    exit 1
fi

SYSTEM_FRAMEWORK_PYTHON_ROOT="/Library/Frameworks/Python.framework/Versions/$PYVER"
# OS X 10.10
SYSTEM_FRAMEWORK_PYTHON_ROOT="/System$SYSTEM_FRAMEWORK_PYTHON_ROOT"

PYSUBVER="$(python --version 2>&1 | cut -d ' ' -f2)"  # e.g., 2.7.10
BREW_PYTHON_ROOT="$(brew --prefix)/Cellar/python/$PYSUBVER/Frameworks/Python.framework/Versions/$PYVER"

PYTHON_BINARY="bin/python$PYVER"
#FRAMEWORK_PYTHON="$SYSTEM_FRAMEWORK_PYTHON_ROOT/$PYTHON_BINARY"

FRAMEWORK_PYTHON="$BREW_PYTHON_ROOT/$PYTHON_BINARY"

VENV_SITE_PACKAGES="$VIRTUAL_ENV/lib/python$PYVER/site-packages"

# Ensure wx.pth is set up in the virtualenv
cp "/Library/Python/$PYVER/site-packages/wxredirect.pth" "$VENV_SITE_PACKAGES/wx.pth"

# Use the Framework Python to run the app
export PYTHONHOME=$VIRTUAL_ENV
exec "$FRAMEWORK_PYTHON" "$VENV_SITE_PACKAGES/$WXPYTHON_APP" $*

Previous Work

The following is an attempt from last year at setting up wxPython in a virtualenv, which no longer works. It sets up a script called fwpy in the virtualenv, which allows you to run a wxPython app with the framework Python.

#!/usr/bin/env python

# Adapted from http://wiki.wxpython.org/wxPythonVirtualenvOnMac

import os, sys
import optparse
from distutils.sysconfig import get_python_lib

site_packages = get_python_lib()
PYTHON_VERSION = sys.version[:3]  # e.g., "2.7"

if os.getenv('VIRTUAL_ENV'):
    print >> sys.stderr, "ERROR: You must *not* run this inside of a virtualenv. Run 'deactivate' first."
    sys.exit(1)

parser = optparse.OptionParser(
    description="virtualenv-aware wrapper for wxPython.",
    usage="usage: %prog virtualenv-name-or-path")
(options, args) = parser.parse_args()

venv = args and args[0]

def valid_virtualenv_dir(venv):
    return venv and os.path.exists(os.path.join(venv, "bin", "activate"))

if not valid_virtualenv_dir(venv):
    # Using virtualenvwrapper?
    if os.getenv("WORKON_HOME"):
        venv = os.path.join(os.getenv("WORKON_HOME"), venv)

if not valid_virtualenv_dir(venv):
    print "Can't find virtualenv", venv
    sys.exit(1)

framework_python = """\
#!/bin/bash
# Invoke Framework Python inside of a virtualenv; e.g., for wxPython

[ -z "$VIRTUAL_ENV" ] && echo "Not running inside of a virtualenv" && exit 1

PYTHONHOME="$VIRTUAL_ENV" exec "%s" "$@"
""" % sys.executable

wx_pth = None

for wx_pth_file in [
        os.path.join(site_packages, 'wx.pth'),
        '/Library/Python/2.7/site-packages/wxredirect.pth'
    ]:
    if os.path.exists(wx_pth_file):
        with open(wx_pth_file) as f:
            wx_pth = f.read().splitlines()[0]
            break

# This is the value of wx.pth for the system python
abs_wx_pth = os.path.join(site_packages, wx_pth)

fwpy = os.path.join(venv, "bin", "fwpy")
with open(fwpy, "w") as f:
    f.write(framework_python)
os.chmod(fwpy, 0755)
print "Wrote", fwpy

# TODO: copy wx.pth
target_wx_pth = os.path.join(venv, "lib", "python"+PYTHON_VERSION, "site-packages", "wx.pth")
with open(target_wx_pth, "w") as f:
    f.write(abs_wx_pth)
print "Wrote", target_wx_pth
blog comments powered by Disqus
Off to Berlin: Spring Cleaning » « On Blogging