My Python Development Environment, 2018 Edition

For years I’ve noodled around with various setups for a Python development
environment, and never really found something I loved — until now.

My setup pieces together pyenv, pipenv, and pipsi. It’s probably a tad more
complex that is ideal for most Python users, but for the things I need, it’s
perfect.

My Requirements

I do have somewhat specific (maybe unusual?) requirements:

  • I need to develop against multiple Python versions, including 2.7, various
    Python 3 versions (3.5 and 3.6, mostly), and PyPy.
  • I work on many projects simultaneously, each with different sets of
    dependencies, so some sort of virtual environment or isolation is critical.
  • I use multiple OSes: macOS at work, and Linux (well, Linux-ish – actually it’s
    WSL) at home.
  • I want to avoid using the System-provided Python. On macOS it’s too outdated.
    On Linux, the system Python is used by the OS itself, so if you hose your
    Python you can hose your system.
  • I use a bunch Python-based CLI stuff, like youtube-dl, awscli, doc2dash,
    etc. I want to be able to install and use them without fussing around with
    activating environments, but I also don’t want their dependencies to clutter
    up a global installation.
  • I deploy stuff mostly to Heroku (personal) and cloud.gov (work). I expect this
    not to change: I’m spoiled, and never want to manage my own infrastructure
    again.
  • Although Docker meets all these requirements, I don’t really like using it.
    I find it slow, frustrating, and overkill for my purposes.

The Setup

1. pyenv

Why? I need to run multiple Python versions, isolated from the system
Python. pyenv makes it easy to install, manage, and switch between those
multiple Pythons. As a bonus, pipenv integrates with pyenv and will
automatically install missing Python versions if they’re required by a
Pipfile.

On my Mac, I installed pyenv from Homebrew (brew install pyenv). On Linux,
I used the Github installation technique documented in the
installation instructions,
which was easy and went smoothly.

Then, I installed some Python versions:

pyenv install 3.6.4
pyenv install 3.5.4
pyenv install 2.7.14
pyenv install pypy3.5-5.10.0

And made sure my default Python was set to the latest and greatest:

pyenv global 3.6.4

2. pipsi

Why? pipsi lets me install Python-based CLI stuff (like youtube-dl,
awscli, doc2dash, etc.) without those projects’ dependencies messing up my
global Python.

Normally, installing pipsi is easy:

curl https://raw.githubusercontent.com/mitsuhiko/pipsi/master/get-pipsi.py | python

However, at the time I’m writing this (late February 2018), this didn’t work
for me; I needed to do some tweaking to get it to work. I followed the workarounds
documented in issues #124 and
#125. Hopefully this’ll be fixed shortly.

I didn’t do this, but it might be a good idea to install pipsi using the system
Python (i.e. run pyenv local system before running the installer). The
downside is that this will use a potentially old-ass Python, but the upside is
that you won’t break your pipsi install if you delete a pyenv version (like
upon an upgrade).

3. pipenv

Why? pipenv handles dependency- and virtual-environment-management in a
way that’s very intuitive (to me), and fits perfectly with my desired workflow.

The documentation covers a
few different ways to install pipenv.
Because I’m already using pipsi, I chose the
pipsi-based installation:

pipsi install pew
pipsi install pipenv

What it looks like in use

With this all together, all my use-cases are handled simply:

To start new projects, I just make a directory and type pipenv install ...
to start installing my dependencies. Pipenv creates a Pipfile for me, and
manages it, and I’m up and running.

To work on existing projects, I clone a repository and either run pipenv
install
(for projects that already have a Pipfile), or pipenv install
-r requirements.txt
(which as a side-effect automatically converts a the
requirements file to a Pipfile).

If I need to switch Python versions, I run pyenv local <version> in my
project directory. I can also add:

[requires]
python_version = "<version>"

to my Pipfile, and pipenv will enforce that version requirement.

When I want to install CLI stuff, I use pipsi:

pipsi install awscli
pipsi install doc2dash
# ... etc

When it comes time to deploy, both Heroku and cloud.gov will read and
understand my Pipfile. If I need to deploy to something that doesn’t
do Pipfile-based installs, I create a requirements.txt by running
pipenv lock --requirements.