A Better Pip Workflow™

Update: I developed Pipenv to solve these problems. Check it out.


When developing Python applications today, it’s standard practice to have a requirements.txt file in the root of your repository.

This file can be used in different ways, and typically takes one of these two forms:

  1. A list of top-level dependencies a project has, often without versions specified.
  2. A complete list of all dependencies a project has, each with exact versions specified.

## Method #1: Simple Requirements

A list of top-level dependencies a a project has, often without versions specified.

$ cat requirements.txtrequests[security]flaskgunicorn==19.4.5

Method #1 is very simple, and is effectively the user experience that everyone using requirements files wants. However, when a requirements.txt file like this is used to deploy to production, unexpected consequences can occur. Effectively, because versions haven’t been pinned, running $ pip install will give you different results today than it will tomorrow.

This is bad. As different versions of sub-dependencies are released, the result of a fresh $ pip install -r requirements.txt will result in different packages being installed, and potentially, your application failing for unknown and hidden reasons.

## Method #2: Exact Requirements

A complete list of all dependencies a project has, each with exact package versions specified.

$ cat requirements.txtcffi==1.5.2cryptography==1.2.2enum34==1.1.2Flask==0.10.1gunicorn==19.4.5idna==2.0ipaddress==1.0.16itsdangerous==0.24Jinja2==2.8MarkupSafe==0.23ndg-httpsclient==0.4.0pyasn1==0.1.9pycparser==2.14pyOpenSSL==0.15.1requests==2.9.1six==1.10.0Werkzeug==0.11.4

Method #2 is best-practice for deploying applications, and ensures an explicit runtime environment with deterministic builds.

All dependencies, including sub-dependencies, are listed, each with an exact version specified.

This type of requirements.txt is generated from the output of running $ pip freeze from within a current working runtime environment for the application. This encourages dev/prod parity, and encourages you to treat code within external packages with the same level of respect as your application code (because it is your application code).

## The Frustrations

While the Method #2 format for requirements.txt is best practice, it is a bit cumbersome. Namely, if I’m working on the codebase, and I want to $ pip install --upgrade some/all of the packages, I am unable to do so easily.

My previous method for doing so was to simply pick out the top-level dependencies with my eyes and manually type out $ pip install requests[security] flask --upgrade. This is not a good experience.

I thought long and hard about building a tool to solve this problem. Others, like pip-tools, already have. But, I don’t want another tool in my toolchain; this should be possible with the tools available.

Eventually, I figured out a nice way to have the best of both worlds in my Python projects, with the tools I already use. I’ve been using this workflow in my projects for a while now, and I’m very happy with the results.

## The Workflow

It’s very simple: instead of having one requirements file, you have two:

  • requirements-to-freeze.txt
  • requirements.txt

https://gist.github.com/kennethreitz/1ae33e0f6744a5ae1976.js requirements-to-freeze.txt uses Method #1, and is used to specify your top-level dependencies, and any explicit versions you need to specify.

requirements.txt uses Method #2, and contains the output of $ pip freeze after $ pip install requirements-to-freeze.txt has been run.

### Basic Usage

$ cd project-repo$ pip install -r requirements-to-freeze.txt --upgradeInstalling collected packages: six, enum34, ipaddress, ...$ pip freeze > requirements.txt

The best of both worlds.

I encourage you to give this workflow a try. There’s a good chance that it’ll save you from some failing builds and scratching heads in the future :)