Introduction

The virtualenv tool is a great way to easily set up a Python environment that is isolated from the other Python installs that may be on your computer. These isolated environments enable the possibility of installing a custom set of 3rd party packages that may be quite different than what is installed in your global Python, without the installed packages cluttering up the import path or conflicting with other versions of the same package in the global Python. For more details on virtualenv please visit their PyPI page at http://pypi.python.org/pypi/virtualenv.

Despite all of virtualenv's goodness, it does have a problem that impacts those who want to use it with wxPython on the Mac. In a nutshell the problem is that wxPython requires a framework build of Python so it is given permission to access the display and create GUI objects, and although the Python binary used by virtualenv is from the framework, the rest of the environment is not the proper structure to be recognized as a framework by the system. So when you try to create a wx.App in this environment wxPython detects that it doesn't have access to the display and it gives you an error.

There is another wxPython virtualenv complication due to the complex nature of how wxPython is installed that makes it difficult (or perhaps impossible) to "install" wxPython into the virtualenv like you would for other packages. However there is a very simple workaround that I'll show below.

Before starting I should give a bit of credit to others that have worked on solving this problem already. I read Batok's page and that gave me the hints I needed to be able to come up with the solution that I present here. The reason I didn't just use his approach is that it removes some of the isolation as the virtualenv ends up having access to the eggs and etc. installed in the global Python and this somewhat defeats the purpose of having the virtualenv.

How to use wxPython with virtualenv on Mac OSX

I'll assume that you already have Python installed (I'm using 2.7.1 installed from Python.org's x86-64/i386 installer) and that you've also installed virtualenv for that Python, and that you are familiar with virtualenv. If not then see virtualenv's documentation for how to proceed.

The first thing we will do is to create the virtual environment for this demonstration. This is how I do it:

$ virtualenv --python=python2.7 --distribute --no-site-packages --unzip-setuptools  test1
Running virtualenv with interpreter /Library/Frameworks/Python.framework/Versions/Current/bin/python2.7
New python executable in test1/bin/python
Please make sure you remove any previous custom paths from your /Users/robind/.pydistutils.cfg file.
Installing distribute....................................................................................................................................................................................done.

If you look in the ./test1 folder you'll find a bin dir with a python executable in it, as well as a lib dir populated with a copy of the Python standard library and also a couple packages installed in site-packages. That was easy, eh? If you run that instance of Python and look at the sys.path you'll see that the path is probably very different than what you get from doing the same thing with your global Python, especially if you've installed lots of eggs.

$ ./test1/bin/python -c "import sys,pprint; pprint.pprint(sys.path)"
['',
 '/Users/robind/PyVE/test1/lib/python2.7/site-packages/distribute-0.6.15-py2.7.egg',
 '/Users/robind/PyVE/test1/lib/python2.7/site-packages/pip-1.0-py2.7.egg',
 '/Users/robind/PyVE/test1/lib/python27.zip',
 '/Users/robind/PyVE/test1/lib/python2.7',
 '/Users/robind/PyVE/test1/lib/python2.7/plat-darwin',
 '/Users/robind/PyVE/test1/lib/python2.7/plat-mac',
 '/Users/robind/PyVE/test1/lib/python2.7/plat-mac/lib-scriptpackages',
 '/Users/robind/PyVE/test1/lib/python2.7/lib-tk',
 '/Users/robind/PyVE/test1/lib/python2.7/lib-old',
 '/Users/robind/PyVE/test1/lib/python2.7/lib-dynload',
 '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
 '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
 '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
 '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
 '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
 '/Users/robind/PyVE/test1/lib/python2.7/site-packages']

One of the extra goodies installed in the environment's bin dir is a script that fiddles with the $PATH such that the environment's version of python and the other executables there will be found first. It's a nice convenience, but not strictly necessary for using the environment.

$ . ./test1/bin/activate
(test1)$ which python
/Users/robind/PyVE/test1/bin/python
(test1)$ which pip
/Users/robind/PyVE/test1/bin/pip

Notice that I run the command with an extra '.' (dot) at the beginning of the command line. This causes the script to run in the current shell instead of spawning a new shell for it. That way the settings it makes persist in the current shell after the script completes. Also notice that the prompt string has changed to show the name of the environment. There is also a deactivate command that can be used to restore things as they were before.

When you use the environment's Python to build and install a Python module or package, or if you use the environment's easy_install or pip commands to grab something from PyPI then it is installed into the environment's site-packages folder. Let's try installing py2app just to see how it works (NOTE: I clipped out some warnings that you can ignore):

(test1)$ pip install py2app
Downloading/unpacking py2app
  Downloading py2app-0.6.3.tar.gz (5.4Mb): 5.4Mb downloaded
  Running setup.py egg_info for package py2app
Downloading/unpacking altgraph>=0.9 (from py2app)
  Downloading altgraph-0.9.tar.gz (357Kb): 357Kb downloaded
  Running setup.py egg_info for package altgraph
Downloading/unpacking modulegraph>=0.9 (from py2app)
  Downloading modulegraph-0.9.tar.gz (57Kb): 57Kb downloaded
  Running setup.py egg_info for package modulegraph
Downloading/unpacking macholib>=1.4 (from py2app)
  Downloading macholib-1.4.1.tar.gz (374Kb): 374Kb downloaded
  Running setup.py egg_info for package macholib
Installing collected packages: py2app, altgraph, modulegraph, macholib
  Running setup.py install for py2app
    Installing py2applet script to /Users/robind/PyVE/test1/bin
  Running setup.py install for altgraph
  Running setup.py install for modulegraph
    Installing modulegraph script to /Users/robind/PyVE/test1/bin
  Running setup.py install for macholib
    Installing macho_standalone script to /Users/robind/PyVE/test1/bin
    Installing macho_dump script to /Users/robind/PyVE/test1/bin
    Installing macho_find script to /Users/robind/PyVE/test1/bin
Successfully installed py2app altgraph modulegraph macholib
Cleaning up...

Ok, the next step is to make wxPython available to this virtual environment. As mentioned above wxPython's installation is complex enough that it would be difficult to install in the environment in the normal manner. However wxPython is already installed in a way that allows it to be somewhat isolated from different wxPython versions and also to be shared by different versions of Python, such as Apple's Python and the Python from python.org, so we can easily use that installation structure in the virtual environment as well. All it takes is adding a .pth file in the environment's site-packages that tells it how to find wxPython, and to make it even simpler we'll just link to the file in the global Python's site-packages folder. That way when wxPython is upgraded then the virtual environment will see the updated version as well.

(test1)$ ln -s /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/wxredirect.pth \
               ./test1/lib/python2.7/site-packages 

(test1)$ python -c "import wx; print wx.version()"
2.9.2.0 (osx-cocoa-unicode)
(test1)$ python -c "import wx; print wx.__file__"
/usr/local/lib/wxPython-2.9.2.0/lib/python2.7/site-packages/wx-2.9.2-osx_cocoa/wx/__init__.pyc

At this point it may appear that everything is working and that we're finished. However as mentioned above this environment is not recognized as a framework or application bundle by the system and so wxPython applications can not initialize.

(test1)$ python
Python 2.7.1 (r271:86882M, Nov 30 2010, 10:35:34) 
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import wx
>>> app = wx.App()
This program needs access to the screen.
Please run with a Framework build of python, and only when you are
logged in on the main display of your Mac.
(test1)$ 

All we need to do to fix this is to use the Python framework's python executable, but set it up in such a way that the virtual environment's library and site-packages will be used instead of those in the framework. It turns out that somebody has used Guido's Time Machine and implemented this feature for us in the form of the PYTHONHOME environment variable. By setting that to the virtualenv's location you can cause Python at runtime to use a different location as its install prefix, including where it looks for the site.py file which is where the virtualenv tool implements some of it's magic. I came up with this little script to help automate things for me:

#!/bin/bash

# what real Python executable to use
PYVER=2.7
PYTHON=/Library/Frameworks/Python.framework/Versions/$PYVER/bin/python$PYVER

# find the root of the virtualenv, it should be the parent of the dir this script is in
ENV=`$PYTHON -c "import os; print os.path.abspath(os.path.join(os.path.dirname(\"$0\"), '..'))"`

# now run Python with the virtualenv set as Python's HOME
export PYTHONHOME=$ENV 
exec $PYTHON "$@"

All you need to do is copy this script into in a file in your environment's bin folder, (I called mine fwpy for FrameWork PYthon) and give it executable permissions. Then when you need to run some wxPython application in your virtual environment just use your copy of this script instead of "python" and you should be all set.

Another possible solution would be to modify the activate script created by virtualenv to also set the PYTHONHOME value in the environment, and then copy the framework's python binary into the virtualenv's bin dir, overwriting the python that is already there. That way you can still use "python" instead of whatever you named your wrapper script, but you would also have to ensure that you always use the activate script and never run the virtualenv's python without it.

Have fun!

RobinDunn

Comments

FWIW, I found it useful to put this in my activate script, to implement the suggestion in the last paragraph:

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

# Point PYTHONHOME here, so we can use the framework python and find the site libs here
export PYTHONHOME=`dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"` 

I also linked the framework python into the virtualenv bin, rather than copy it, so it is clear where it is actually coming from. It seems to work OK.

~~MartinGregory

wxPythonVirtualenvOnMac (last edited 2014-12-31 17:54:56 by RobinDunn)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.