My somewhat pragmatic approach towards pipenv

If you are a Python developer you probably have heard about pipenv already. It advertises itself as Python Development Workflow for Humans, the “for Humans” part should be a hint about who created it.

Pipenv surely has some nice features, but it also has others that I’m not interested. In this post, I intend to present the way I use it. I’ll try to illustrate how it fit my development workflow by sections.

Python installation

If you have pyenv installed pipenv integrates with it and can install the required version described in the Pipfile.

It’s a nice thing but by now I already pyenv directly and is used to it. Plus, as the next section explains, I prefer to deal with the installation and virtualenv creation by myself.

Virtualenv creation

If pipenv find itself running inside a virtualenv it’ll take advantage of that and use this environment, which is super cool. Otherwise, it’ll automatically create a new virtualenv for the project.

I have my own naming convention for all my virtualenvs, being able to exactly know the name of every venv is important to me because sometimes I need to quickly try a piece of code, and I can type it faster than using the autocomplete.

With this in mind, I prefer to create my venvs with pyenv, following my naming convention.

Virtualenv activation

If you are a Vim user I believe you’ll like this. 🙂
You can use pipenv run for running a single command and pipenv will automatically activate the project virtualenv for you.
If you want to keep the virtualenv activated, you can use pipenv shell.
Pipenv will automatically load .env files.

In a typical workday, I spend most of my time in PyCharm. If you configure a virtualenv for the active project when you open the terminal window inside it, the virtualenv will be automatically activated, this and the Django task runner makes both commands not super interesting to me.

For the .env loading part, in basically every project I use prettyconf or python-decouple, and both projects already solve this for me.

Managing packages

Here we’ll cover the feature set that makes pipenv an amazing project, for me at least.
To fully understand the awesomeness of pipenv in this regard a bit of context is needed.

Deterministic builds

Achieving truly deterministic builds with requirements.txt alone is a bit of a pain. One way to do this with pip alone is to install a package and then running pip freeze redirecting its output to the requirements.txt of your production environment.

One can argue that only pinning the direct dependency of your project will be enough, but I learned the hard way that this is not enough. By only pinning the direct dependency, you let the dependency of your dependency basically go crazy. Most of the time it’s not a problem, but when it becomes a problem it’s a hard one to track.

Kenneth makes a better job than I explaining this here.

Enter Pipfile

To solve this kind of problem and some others a replacement was created, Pipfile and Pipfile.lock, our “requirements.txt 2.0”.

Pipfile is where you declare the required Python version, the packages used in your production and development environment and their source, e.g. PyPI, PackageCloud, etc.

A simple Pipfile would look like this:

[source]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]

dj-database-url = "*"
Django = "<2"
facebook-sdk = "*"
opbeat = "*"
psycopg2-binary = "*"
python-decouple = "*"

[dev-packages]

selenium = "*"
django-debug-toolbar = "*"
model_mommy = "==1.3.0"

The anatomy is very simple to understand, the syntax resembles a lot of what we already have in our requirements.txt. But instead of having a requirements.txt and a requirements-dev.txt we can declare it in a single file. Cool!

But wait, why are almost every package not using a pinned version? It’s in Pipfile.lock where all of all packages versions are pinned, along with a bunch of other information, like the hashes of each package.

Because of this, the Pipfile.lock file tends to be a big one, so I’ll just show the section of a single package.

 "django": {
   "hashes": [
     "sha256:ac4c797a328a5ac8777ad61bcd00da279773455cc78b4058de2a9842a0eb6ee8",
     "sha256:22383567385a9c406d8a5ce080a2694c82c6b733e157922197e8b393bb3aacd9"
   ],
   "version": "==1.11.10"
 }

Here we have the required pinned version of the package along with its hashes, we’ll talk about the hashes later.

As you can imagine handling both files manually should be a very tedious and error-prone task. Luckily enough, we have pipenv.

Package installation

To install a package using pipenv is very much like installing it with pip, you just need to replace pip with pipenv.

$ pipenv install django

The cool thing here is that it’ll add it to your Pipfile and already lock it and all its dependencies in Pipfile.lock.

Installing packages for a project

When you clone or update a repository you normally would like to update and install all required packages. The way you do this with pipenv is by running install without extra parameters.

$ pipenv install

Most likely you will want to install the packages required in the development environment, for this just add the --dev option to the previous command.

$ pipenv install --dev

Tip: if the load created by the concurrent installation of packages are slowing down your machine, you could use an extra option to make it sequential:

$ pipenv install --sequencial

Updating the projects dependencies version

As we saw in our Pipfile you can still pin a specific version of any package but for most of it, we simply have a "*" instead, which makes sense as the pinned version would be set in the Pipfile.lock file.

Now suppose we decide that its time to update our dependencies to their latest version. It’s very simple, you just need to lock the dependencies again.

$ pipenv lock

It’ll update the Pipfile.lock file with the latest version for each dependency, and related dependencies, while respecting the version requirement informed in the Pipfile.
Like, but not limited to, this:

* for the latest version
==1.3.0 for the specific 1.3.0 version
<2 for versions smaller than 2

Security

Remember those hashes thing? Starting with pip 8.0, pip added a new security feature. Informing the hashes for each package you are now protected against package tampering. And the cool thing is that pipenv make this totally transparent.

Pipenv also adds another very cool command to add an extra protective layer for your project, with pipenv check you can verify if any of your used packages have a known security vulnerability.

Extra sweetness

Pipenv has a few more cool commands, like pipenv graph for print an dependency graph for you, and a few others, you should check it out in the docs.

Conclusion

For my workflow, some features from pipenv are not required and would even be a problem if I was forced to adopt it entirely and I’m glad that I can use only the ones that make sense to me. As I tried to highlight in the Managing packages section pipenv has too many cool features to be blindly ignored.

I hope you are convinced to give it a try at least, and if you like it, don’t forget to say Thanks to Kenneth.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s