Stop Homebrew Version-Jacking Your Python All The Time
It happened again. I do a careless brew upgrade and homebrew blows away1 the stable python install I had 20 virtualenvs pointing to in favor of a new major python version released two days ago that many extensions and modules won’t even support building against for another 30-90 days.
Let’s stop homebrew from upgrading us past where we expect to be2 with pyenv and all the shell hacks required to run it.
pyenv makes available many python versions and forks (pypy, stackless, jython, micropython, etc) and installs them all from source in a user directory for non-conflicting system development. As of this writing, pyenv install --list reports 4383 unique python implementations available for building.
Install pyenv
First clone pyenv (and no, do not install pyenv from homebrew either, it also causes problems4):
Setup pyenv
Remainder of the setup process requires modifying your $PATH and setting some variables, which depends on your system and shell, so pick the right combination for your platforms then open a new shell to continue using pyenv with your shim-adjusted path.
Also note: any previously system-wide installed python helpers (like /usr/local/bin/poetry, etc) placed into your path should also be removed in favor of letting the pyenv shims at the start of your path pick the correct version for you. The pip/homebrew /usr/local/bin packages are pinned to your system or package manager python version and they don’t check your environment python for any usage.
Install your first private python version
Because pyenv builds each python version from source, you need to have all the python build dependencies installed before asking pyenv to isolate a python version for you.
Check out the pyenv build environment page for which packages you likely need across various platforms.
After your system has all the prereq packages, just pyenv install 3.9.1, wait 5 minutes (python builds fast under defaults), and you have a new python version available!
You can customize the build process in many ways to turn on optimizations and LTO (increases build time to 15+ minutes) using export PYTHON_CONFIGURE_OPTS="--enable-optimizations --enable-ipv6 --with-lto or use different libraries than the default configure scripts will find.
Use your private python version
Now with a python version installed, you can set your local directory python version using pyenv local 3.9.1 and pyenv will drop a file named .python-version in your current directory with the contents 3.9.1.
If you successfully updated your $PATH in the previous steps so pyenv shims are before your system python paths, when you run python, you’ll get the version specified by the nearest .python-version file without needing to do any virtualenv trickery.
How pyenv Works
pyenv works using shell magic and local version pinning files:
pyenvoverrides your pathpythonandpython3to use a version lookup script instead of being a binary or symlink itself.- (
pyenvalso overrides/intercepts all other python utilities likepipandeasy_installandvirtualenv, etc; in fact every utility gets the same5 shim just re-exec’ing the called script name with the requested.python-versionpath)
- (
pyenvlooks to see if the current directory has a.python-versionfile- if not,
pyenvchecks the next highest directory, all the way up. - if so, when you call
python(which in your path is now~/.pyenv/shims/python) the script actually calls$PYENV_ROOT/versions/<version in your file>/bin/python
- if not,
All Done
and now you’re done! You have a way to install multiple python versions at once, each with correct access to their own helper utilities like pip and poetry without any other path conflicts or manual trickery required.
yes, I know they are still there, at least until
brew cleanupdestroys them further.↩yes, it is possible to install old brew packages if you manually point to an old git revision, but then another upgrade will just overwrite your choices again in the future. everything ends up being a game of
brewroulette—“will this update completely destroy all my development environments? let’s find out!”↩- ↩
% pyenv install --list Available versions: 2.1.3 2.2.3 2.3.7 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.6.6 2.6.7 2.6.8 2.6.9 2.7.0 2.7-dev 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.10 2.7.11 2.7.12 2.7.13 2.7.14 2.7.15 2.7.16 2.7.17 2.7.18 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4-dev 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.4.7 3.4.8 3.4.9 3.4.10 3.5.0 3.5-dev 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 3.5.9 3.5.10 3.6.0 3.6-dev 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9 3.6.10 3.6.11 3.6.12 3.7.0 3.7-dev 3.7.1 3.7.2 3.7.3 3.7.4 3.7.5 3.7.6 3.7.7 3.7.8 3.7.9 3.8.0 3.8-dev 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.9.0 3.9-dev 3.9.1 3.10-dev activepython-2.7.14 activepython-3.5.4 activepython-3.6.0 anaconda-1.4.0 anaconda-1.5.0 anaconda-1.5.1 anaconda-1.6.0 anaconda-1.6.1 anaconda-1.7.0 anaconda-1.8.0 anaconda-1.9.0 anaconda-1.9.1 anaconda-1.9.2 anaconda-2.0.0 anaconda-2.0.1 anaconda-2.1.0 anaconda-2.2.0 anaconda-2.3.0 anaconda-2.4.0 anaconda-4.0.0 anaconda2-2.4.0 anaconda2-2.4.1 anaconda2-2.5.0 anaconda2-4.0.0 anaconda2-4.1.0 anaconda2-4.1.1 anaconda2-4.2.0 anaconda2-4.3.0 anaconda2-4.3.1 anaconda2-4.4.0 anaconda2-5.0.0 anaconda2-5.0.1 anaconda2-5.1.0 anaconda2-5.2.0 anaconda2-5.3.0 anaconda2-5.3.1 anaconda2-2018.12 anaconda2-2019.03 anaconda2-2019.07 anaconda3-2.0.0 anaconda3-2.0.1 anaconda3-2.1.0 anaconda3-2.2.0 anaconda3-2.3.0 anaconda3-2.4.0 anaconda3-2.4.1 anaconda3-2.5.0 anaconda3-4.0.0 anaconda3-4.1.0 anaconda3-4.1.1 anaconda3-4.2.0 anaconda3-4.3.0 anaconda3-4.3.1 anaconda3-4.4.0 anaconda3-5.0.0 anaconda3-5.0.1 anaconda3-5.1.0 anaconda3-5.2.0 anaconda3-5.3.0 anaconda3-5.3.1 anaconda3-2018.12 anaconda3-2019.03 anaconda3-2019.07 anaconda3-2019.10 anaconda3-2020.02 anaconda3-2020.07 graalpython-20.1.0 graalpython-20.2.0 ironpython-dev ironpython-2.7.4 ironpython-2.7.5 ironpython-2.7.6.3 ironpython-2.7.7 jython-dev jython-2.5.0 jython-2.5-dev jython-2.5.1 jython-2.5.2 jython-2.5.3 jython-2.5.4-rc1 jython-2.7.0 jython-2.7.1 jython-2.7.2 micropython-dev micropython-1.9.3 micropython-1.9.4 micropython-1.10 micropython-1.11 micropython-1.12 micropython-1.13 miniconda-latest miniconda-2.2.2 miniconda-3.0.0 miniconda-3.0.4 miniconda-3.0.5 miniconda-3.3.0 miniconda-3.4.2 miniconda-3.7.0 miniconda-3.8.3 miniconda-3.9.1 miniconda-3.10.1 miniconda-3.16.0 miniconda-3.18.3 miniconda2-latest miniconda2-3.18.3 miniconda2-3.19.0 miniconda2-4.0.5 miniconda2-4.1.11 miniconda2-4.3.14 miniconda2-4.3.21 miniconda2-4.3.27 miniconda2-4.3.30 miniconda2-4.3.31 miniconda2-4.4.10 miniconda2-4.5.1 miniconda2-4.5.4 miniconda2-4.5.11 miniconda2-4.5.12 miniconda2-4.6.14 miniconda2-4.7.10 miniconda2-4.7.12 miniconda3-latest miniconda3-2.2.2 miniconda3-3.0.0 miniconda3-3.0.4 miniconda3-3.0.5 miniconda3-3.3.0 miniconda3-3.4.2 miniconda3-3.7.0 miniconda3-3.8.3 miniconda3-3.9.1 miniconda3-3.10.1 miniconda3-3.16.0 miniconda3-3.18.3 miniconda3-3.19.0 miniconda3-4.0.5 miniconda3-4.1.11 miniconda3-4.2.12 miniconda3-4.3.11 miniconda3-4.3.14 miniconda3-4.3.21 miniconda3-4.3.27 miniconda3-4.3.30 miniconda3-4.3.31 miniconda3-4.4.10 miniconda3-4.5.1 miniconda3-4.5.4 miniconda3-4.5.11 miniconda3-4.5.12 miniconda3-4.6.14 miniconda3-4.7.10 miniconda3-4.7.12 pypy-c-jit-latest pypy-c-nojit-latest pypy-dev pypy-stm-2.3 pypy-stm-2.5.1 pypy-1.5-src pypy-1.5 pypy-1.6 pypy-1.7 pypy-1.8 pypy-1.9 pypy-2.0-src pypy-2.0 pypy-2.0.1-src pypy-2.0.1 pypy-2.0.2-src pypy-2.0.2 pypy-2.1-src pypy-2.1 pypy-2.2-src pypy-2.2 pypy-2.2.1-src pypy-2.2.1 pypy-2.3-src pypy-2.3 pypy-2.3.1-src pypy-2.3.1 pypy-2.4.0-src pypy-2.4.0 pypy-2.5.0-src pypy-2.5.0 pypy-2.5.1-src pypy-2.5.1 pypy-2.6.0-src pypy-2.6.0 pypy-2.6.1-src pypy-2.6.1 pypy-4.0.0-src pypy-4.0.0 pypy-4.0.1-src pypy-4.0.1 pypy-5.0.0-src pypy-5.0.0 pypy-5.0.1-src pypy-5.0.1 pypy-5.1-src pypy-5.1 pypy-5.1.1-src pypy-5.1.1 pypy-5.3-src pypy-5.3 pypy-5.3.1-src pypy-5.3.1 pypy-5.4-src pypy-5.4 pypy-5.4.1-src pypy-5.4.1 pypy-5.6.0-src pypy-5.6.0 pypy-5.7.0-src pypy-5.7.0 pypy-5.7.1-src pypy-5.7.1 pypy2-5.3-src pypy2-5.3 pypy2-5.3.1-src pypy2-5.3.1 pypy2-5.4-src pypy2-5.4 pypy2-5.4.1-src pypy2-5.4.1 pypy2-5.6.0-src pypy2-5.6.0 pypy2-5.7.0-src pypy2-5.7.0 pypy2-5.7.1-src pypy2-5.7.1 pypy2.7-5.8.0-src pypy2.7-5.8.0 pypy2.7-5.9.0-src pypy2.7-5.9.0 pypy2.7-5.10.0-src pypy2.7-5.10.0 pypy2.7-6.0.0-src pypy2.7-6.0.0 pypy2.7-7.0.0-src pypy2.7-7.0.0 pypy2.7-7.1.0-src pypy2.7-7.1.0 pypy2.7-7.1.1-src pypy2.7-7.1.1 pypy2.7-7.2.0-src pypy2.7-7.2.0 pypy2.7-7.3.0-src pypy2.7-7.3.0 pypy2.7-7.3.1-src pypy2.7-7.3.1 pypy3-2.3.1-src pypy3-2.3.1 pypy3-2.4.0-src pypy3-2.4.0 pypy3.3-5.2-alpha1-src pypy3.3-5.2-alpha1 pypy3.3-5.5-alpha-src pypy3.3-5.5-alpha pypy3.5-c-jit-latest pypy3.5-5.7-beta-src pypy3.5-5.7-beta pypy3.5-5.7.1-beta-src pypy3.5-5.7.1-beta pypy3.5-5.8.0-src pypy3.5-5.8.0 pypy3.5-5.9.0-src pypy3.5-5.9.0 pypy3.5-5.10.0-src pypy3.5-5.10.0 pypy3.5-5.10.1-src pypy3.5-5.10.1 pypy3.5-6.0.0-src pypy3.5-6.0.0 pypy3.5-7.0.0-src pypy3.5-7.0.0 pypy3.6-7.0.0-src pypy3.6-7.0.0 pypy3.6-7.1.0-src pypy3.6-7.1.0 pypy3.6-7.1.1-src pypy3.6-7.1.1 pypy3.6-7.2.0-src pypy3.6-7.2.0 pypy3.6-7.3.0-src pypy3.6-7.3.0 pypy3.6-7.3.1-src pypy3.6-7.3.1 pyston-0.5.1 pyston-0.6.0 pyston-0.6.1 stackless-dev stackless-2.7-dev stackless-2.7.2 stackless-2.7.3 stackless-2.7.4 stackless-2.7.5 stackless-2.7.6 stackless-2.7.7 stackless-2.7.8 stackless-2.7.9 stackless-2.7.10 stackless-2.7.11 stackless-2.7.12 stackless-2.7.14 stackless-3.2.2 stackless-3.2.5 stackless-3.3.5 stackless-3.3.7 stackless-3.4-dev stackless-3.4.1 stackless-3.4.2 stackless-3.4.7 stackless-3.5.4 stackless-3.7.5 what problems? pyenv doesn’t always cut a new release when it adds new python versions to its library, and homebrew only uses released versions, so maybe you do want a python version released in the past month but
pyenvhasn’t cut a release in two months… you end up having to clonepyenvmanually anyway (also, the source of python versions is only inside thepyenvrepo itself, it doesn’t maintain an independent download list).↩- ↩
% openssl sha512 ~/.pyenv/shims/* SHA512(/Users/matt/.pyenv/shims/2to3)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/2to3-3.9)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/chardetect)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/doesitcache)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/easy_install)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/easy_install-3.9)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/idle)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/idle3)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/idle3.9)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/keyring)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/pip)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/pip3)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/pip3.9)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/pkginfo)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/poetry)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/pydoc)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/pydoc3)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/pydoc3.9)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/python)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/python-config)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/python3)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/python3-config)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/python3.9)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/python3.9-config)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/python3.9-gdb.py)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415 SHA512(/Users/matt/.pyenv/shims/virtualenv)= 22c8ec0e34dc5b480a0550d87be2d3279e7ab3af0e58dcf5d30bb1d2bdced6a36e74220f444a716fbfc48f74c54a4c0fa9c76a339e285601705871e59540f415