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 :)

Kenneth Reitz
Wandering street photographer, idealist, and moral fallibilist.
http://kennethreitz.org
Previous
Previous

MentalHealthError: an exception occurred.

Next
Next

Introducing Records: SQL for Humans™