Like about a decade ago, I’m again setting up a local installation of Django. This time though I’m wanting to use virtual environments, and ideally different versions of Python, so I can have work on multiple versions of a Django website, each with different versions of Django and ideally Python.
Also, this time I’m on Mac using homebrew.
It seems the best way to manage having multiple versions of Python on a computer is to use PyEnv. Helpful commands:
Show the list of python versions that are installed:
Install Python 3.7.2. (Note: when I tried to install 3.6.x I got a weird error that seems to be because of all the 3.6.x versions have a bug that makes them not work on my version of Mac):
pyenv install -v 3.7.2
Switches the default version of Python to 3.7.2:
pyenv global 3.7.2
Reset the python version to whatever the system has by default (in my case it wasn’t installed, so this set it back to nothing):
pyenv global system
Python’s way of having different libraries installed for different projects is to use “virtual environments”. Basically, instead of storing libraries and packages in the system’s default folder, they get stored somewhere else, and whatever is doing the running/interpreting is told to look there for packages/libraires instead of the default location.
Important: virtual environments, by default, use the same version of Python. So getting them to use different versions of Python isn’t just another setting.
You can use virtual environments on their own, but I wanted to use them along with PyEnv, and for that there’s a PyEnv plugin called PyEnv VirtualEnv.
Helpful commands with PyEnv VirtualEnv:
List virtual environments:
Create virtual environment using Python 2.7.10:
pyenv virtualenv 2.7.10 my-virtual-env-2.7.10
Activate virtual environment (tell the terminal to use the virtual environment’s libraries):
pyenv activate my-virtual-env-2.7.10
Deactivate virtual environment:
Important: instead of creating folders of “virtual environments” wherever you run the commands (which is how virtual environments usually work), they’re all stored in the user’s home directory in a hidden folder called
~/.pyenv/versions., and the
site-packages would be in a folder like
So, I wanted my virtual environment to use the old version of Django we’re still running on the website, so I activated the virtual environment (
pyenv activate my-virutal-env-3.5.10) then installed Django (
pip install django==2.2.8 I think). Then I created the
sampleapp folder in the Apache web root (
/opt/homebrew/var/www), so it was
/opt/homebrew/var/www/sampleapp, and in there I created the actual Django sample app by running
django-admin startproject sampleapp . (that period is part of the command, by the way.)
Apache is the webserver which listens for you using your browser to visit
http://localhost and then forwards the request onto Python which does its magic and responds with whatever the webpage should look like.
Homebrew installs apache and the webserver root directory in different locations than linux (ok they’re all different too anyway). This was helpful for getting Apache installed: How To Set Up Apache and PHP from Homebrew on macOS | Tower Blog (git-tower.com)
Apache webroot (where HTML files are usually stored):
Apache configuration folder:
/opt/homebrew/etc/httpd, with the main configuration file being
Restarting Apache (after changing configuration etc):
brew services restart httpd
Ok so Apache itself doesn’t know how to talk to Python. It needs the
mod_wsgi module to do that. But the Digital Ocean tutorial on how to install mod_wsgi is out-of-date or wrong. Besides, the wsgi module is tied to a version of Python, so it’s best for Python to install it.
So we actually want to activate the virtual environment with the Python version we want to use (eg
pyenv activate my-virtual-env-3.5.10) then…
Install the mod_wsgi module:
pip install mod_wsgi
This squirrels the needed
mod_wsgi.so file away in the virtual environment’s folders. In my case, that was
So, to tell Apache to use that module, I edited
/opt/homebrew/etc/httpd/httpd.conf and added
LoadModule wsgi_module /Users/preci/.pyenv/versions/3.5.10/lib/python3.5/site-packages/mod_wsgi/server/mod_wsgi-py35.cpython-35m-darwin.so
then restarted Apache (
brew services restart httpd).
So, as mentioned in the Git Tower article, I modified the Apache configuration file to listen on port 80, enabled Virtual Hosts, and set the default server name (just to avoid being nagged about not setting it; I also enabled mod_rewrite although I’m not sure if that was necessary.)
Then in the virtual hosts file(
/opt/homebrew/etc/httpd/extra/httpd-vhosts.conf), I changed the
<VirtualHost *:8080> to
<VirtualHost *:80> so I wouldn’t have to always tell the browser
http://localhost:8080, but instead just
http://localhost (and let it use the default port 80). Under that, I added:
WSGIDaemonProcess sampleapp python-path=/opt/homebrew/var/www/sampleapp:/Users/preci/.pyenv/versions/my-virtual-env-3.5.10/lib/python3.5/site-packages/ WSGIProcessGroup sampleapp WSGIScriptAlias / /opt/homebrew/var/www/sampleapp/sampleapp/wsgi.py
Restarting the webserver (
brew services httpd restart) and pointing my browser to
http://localhost got me the Django sample app (which showed it was running Django 2.2).
Also, to verify it’s running Python 3.5. I edited
/opt/homebrew/var/www/sampleapp/sampleapp/urls.py and added
foobar() at the bottom. That will cause an error, which will show Django’s debug screen, which includes the Python version. Then restarted the webserver (
brew services restart httpd) and refreshed the browser.
Now, how to get different routes to use different version of Python? It looks like we’d basically need to run different webservers, as each Apache webserver can only use one mod_wsgi module, and each mod_wsgi module is tied to a specific version of Python.
The quickfix: don’t have separate websites running different versions of Python, just install a different mod_wsgi in the different virtual environments, then modify Apache configuration to use that different
mod_wsgi.so when you want to test that version of Python.
Let’s say I want to set my web app on Python 3.11.5 and Django 4.2…
Install Python 3.11.5:
pyenv install 3.11.5
Create a virtual environment for that version:
pyenv virtualenv 3.11.5 my-virtual-env-3.11.5
Activate that virtual environment (so pip uses that version fo Python):
pyenv activate my-virtual-env-3.11.5
Install Django 4.2:
pip install django==4.2
Create a new sample project:
django-admin startproject sampleapp2 ..
Install mod_wsgi for this version of Python:
pip install mod_wsgi (from within the Python 3.11.5 virtual environment).
Configure Apache to use this version of mod_wsgi, by opening
/opt/homebrew/etc/httpd/httpd.conf and changing the
LoadModule wsgi... line. to
LoadModule wsgi_module /Users/preci/.pyenv/versions/my-virtual-env-3.11.5/lib/python3.11/site-packages/mod_wsgi/server/mod_wsgi-py311.cpython-311-darwin.so
And have it serve the
sampleapp2 by modifying
WSGIDaemonProcess sampleapp2 python-path=/opt/homebrew/var/www/sampleapp2:/Users/preci/.pyenv/versions/my-virtual-env-3.11.5/lib/python3.5/site-packages/ WSGIProcessGroup sampleapp2 WSGIScriptAlias / /opt/homebrew/var/www/sampleapp2/sampleapp2/wsgi.py
and restart Apache (
brew services restart httpd).
Adding the error to the
urls.py file this time didn’t show the nice Debug screen though. Maybe something changed in Django between 2.2 and 4.2?
Anyway, so switching Python versions, virtual environments, and mod_wsgi files when you need to test a new version fo Python is an option. But a bit of a pain. Someone else asked about running multiple Python versions simultaneously from Apache but it doesn’t seem possible unless you run multiple versions of Apache. Yegh.