Advertisement
  1. Code
  2. Python

Revisiting Python Packaging With Pipenv

Scroll to top

Overview

Python is one of the friendliest yet most powerful languages out there. It is easy for beginners to pick up, yet packs a strong punch and is used extensively in diverse domains such as scientific programming, web application programming, and DevOps. But one of the weakest points of Python has been its support for packaging complex applications and their dependencies. 

Over the years, there have been many efforts to improve the situation. In August 2017, I wrote a tutorial on the state of the art in Python packaging: How to Write, Package and Distribute a Library in Python

It's been only four months, and there is a new player in town. Pipenv is now the officially recommended tool for packaging by PyPA (Python Packaging Authority). In this tutorial you'll learn why Pipenv significantly improves the state of packaging and overall development workflow for Python developers and how to use it effectively.

Python Dev Workflow for Humans

The goal of Pipenv is to improve the development workflow of Python developers when it comes to managing dependencies and virtual environments. It is another fine library from the industrious Kenneth Reitz, who is known mostly for the requests package (HTTP for humans), but wrote a few other excellent packages. 

Do We Need Yet Another Packaging Tool?

Yes, we do! Pipenv takes a page from modern package management practices and imports them into the Python world. 

Installing Pipenv

You can install Pipenv with pip install pipenv. You'll get a nice output with emojis:

1
$ pip install pipenv
2
✨🍰✨

You'll have to do it just once. If you don't have pip installed, you can use this bootstrap command: $ curl https://github.com/pypa/pipenv/blob/master/get-pipenv.py | python

Pipfile and Pipfile.lock

Pipenv can create an empty virtual environment for you. Here is a quick demo: 

1
~/git > mkdir testpipenv
2
~/git > cd testpipenv
3
~/git/testpipenv > pipenv --three
4
5
Output:
6
7
Creating a virtualenv for this project…
8
Using /usr/local/bin/python3 to create virtualenv…
9
⠋Running virtualenv with interpreter /usr/local/bin/python3
10
Using base prefix '/usr/local/Cellar/python3/3.6.3/Frameworks/Python.framework/Versions/3.6'
11
New python executable in /Users/gigi.sayfan/.local/share/virtualenvs/testpipenv-0GShD6dy/bin/python3.6
12
Also creating executable in /Users/gigi.sayfan/.local/share/virtualenvs/testpipenv-0GShD6dy/bin/python
13
Installing setuptools, pip, wheel...done.
14
15
Virtualenv location: /Users/gigi.sayfan/.local/share/virtualenvs/testpipenv-0GShD6dy
16
Creating a Pipfile for this project…

This will create an empty Pipfile with no dependencies. But since you'll probably want to install some packages for your project, you can just use pipenv to install a package and it will create the virtual environment automatically. For example:

1
~/git/testpipenv > pipenv install requests
2
3
Output:
4
5
6
Installing requests…
7
Collecting requests
8
  Using cached requests-2.18.4-py2.py3-none-any.whl
9
Collecting idna<2.7,>=2.5 (from requests)
10
  Using cached idna-2.6-py2.py3-none-any.whl
11
Collecting chardet<3.1.0,>=3.0.2 (from requests)
12
  Using cached chardet-3.0.4-py2.py3-none-any.whl
13
Collecting certifi>=2017.4.17 (from requests)
14
  Using cached certifi-2017.11.5-py2.py3-none-any.whl
15
Collecting urllib3<1.23,>=1.21.1 (from requests)
16
  Using cached urllib3-1.22-py2.py3-none-any.whl
17
Installing collected packages: idna, chardet, certifi, urllib3, requests
18
Successfully installed certifi-2017.11.5 chardet-3.0.4 idna-2.6 requests-2.18.4 urllib3-1.22
19
20
Adding requests to Pipfile's [packages]…

21
  PS: You have excellent taste! ✨ 🍰 ✨

22
Locking [dev-packages] dependencies…

23
Locking [packages] dependencies…

24
Updated Pipfile.lock (7b8df8)!

The level of detail is excellent, and it uses nice colors too. Here is the resulting Pipfile:

1
[[source]]
2
3
url = "https://pypi.python.org/simple"
4
verify_ssl = true
5
name = "pypi"
6
7
8
[dev-packages]
9
10
11
12
[packages]
13
14
requests = "*"
15
16
17
[requires]
18
19
python_version = "3.6"

The Pipfile keeps track of your project's top-level dependencies—here, just requests = "*". It uses TOML as its format, which is a popular choice these days for configuration files (Rust's Cargo, Python's PEP-518). 

The Pipefile.lock file, on the other hand, is a JSON file that specifies some metadata and the exact versions (including hashes) of all the recursive dependencies (top-level dependencies and their dependencies). Here is the Pipfile.lock file:

1
{
2
    "_meta": {
3
        "hash": {
4
            "sha256": "33a0ec7c8e3bae6f62dd618f847de92ece20e2bd4efb496927e2524b9c7b8df8"
5
        },
6
        "host-environment-markers": {
7
            "implementation_name": "cpython",
8
            "implementation_version": "3.6.3",
9
            "os_name": "posix",
10
            "platform_machine": "x86_64",
11
            "platform_python_implementation": "CPython",
12
            "platform_release": "16.7.0",
13
            "platform_system": "Darwin",
14
            "platform_version": "Darwin Kernel Version 16.7.0: Wed Oct  4 00:17:00 PDT 2017; root:xnu-3789.71.6~1/RELEASE_X86_64",
15
            "python_full_version": "3.6.3",
16
            "python_version": "3.6",
17
            "sys_platform": "darwin"
18
        },
19
        "pipfile-spec": 6,
20
        "requires": {
21
            "python_version": "3.6"
22
        },
23
        "sources": [
24
            {
25
                "name": "pypi",
26
                "url": "https://pypi.python.org/simple",
27
                "verify_ssl": true
28
            }
29
        ]
30
    },
31
    "default": {
32
        "certifi": {
33
            "hashes": [
34
                "sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694",
35
                "sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0"
36
            ],
37
            "version": "==2017.11.5"
38
        },
39
        "chardet": {
40
            "hashes": [
41
                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
42
                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
43
            ],
44
            "version": "==3.0.4"
45
        },
46
        "idna": {
47
            "hashes": [
48
                "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
49
                "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
50
            ],
51
            "version": "==2.6"
52
        },
53
        "requests": {
54
            "hashes": [
55
                "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
56
                "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
57
            ],
58
            "version": "==2.18.4"
59
        },
60
        "urllib3": {
61
            "hashes": [
62
                "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
63
                "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
64
            ],
65
            "version": "==1.22"
66
        }
67
    },
68
    "develop": {}

If you want to see a graph of all your dependencies, type: pipenv graph

1
~/git/testpipenv > pipenv graph
2
requests==2.18.4
3
  - certifi [required: >=2017.4.17, installed: 2017.11.5]
4
  - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
5
  - idna [required: <2.7,>=2.5, installed: 2.6]
6
  - urllib3 [required: >=1.21.1,<1.23, installed: 1.22]

Using Installed Packages With Pipenv

Once you've installed a package with Pipenv, it is accessible in your virtual environment just like a standard package (the same as if you pip installed it). The only precaution is that you must use your virtual environment interpreter. Pipenv provides two helpful commands: run and shell

You use pipenv run python <your program>.py to run your program, and you use pipenv shell to start a new shell with your virtual environment Python interpreter. Here is how to use the shell command to start an interactive Python session that uses the installed requests package to get a quote of the day from a REST API. The virtual environment is activated, and launching Python uses the right interpreter where requests is available.

1
~/git/testpipenv > pipenv shell
2
Spawning environment shell (/bin/bash). Use 'exit' to leave.
3
source /Users/gigi.sayfan/.local/share/virtualenvs/testpipenv-0GShD6dy/bin/activate
4
~/git/testpipenv > source /Users/gigi.sayfan/.local/share/virtualenvs/testpipenv-0GShD6dy/bin/activate
5
(testpipenv-0GShD6dy) ~/git/testpipenv > python
6
Python 3.6.3 (default, Nov 19 2017, 16:39:12)
7
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.38)] on darwin
8
Type "help", "copyright", "credits" or "license" for more information.
9
>>> import requests
10
>>> r = requests.get('https://quotes.rest/qod')
11
>>> r.ok
12
True
13
>>> r.json()
14
{'success': {'total': 1}, 'contents': {'quotes': [{'quote': 'Hang Out with People Who are Better than You.', 'author': 'Warren Buffett', 'length': None, 'tags': ['getting-better', 'inspire', 'people'], 'category': 'inspire', 'title': 'Inspiring Quote of the day', 'date': '2018-01-03', 'id': None}], 'copyright': '2017-19 theysaidso.com'}}
15
>>> quote = r.json()['contents']['quotes'][0]['quote']
16
>>> author = r.json()['contents']['quotes'][0]['author']
17
>>> print(f'{quote} ~~ {author}')
18
Hang Out with People Who are Better than You. ~~ Warren Buffett
19
>>>

Importing From requirements.txt

If you want to migrate an existing project with a requirements.txt, Pipenv has got you covered. Simply: pipenv install -r <path/to/requirements.txt>.

All your dependencies will be imported into the Pipfile. To actually install the dependencies and generate the Pipfile.lock, you need to pipenv install. Once you've verified that everything works as expected, you can delete your requirements.txt file.

If your requirements.txt exists in the same directory that you create the virtual environment then Pipenv will automatically generate the Pipfile. But beware that if your requirements.txt file contained pinned versions then they will be pinned in the Pipfile too. In the Pipenv world, pinning should happen in the Pipfile.lock file. Pipenv will give a friendly reminder. See below:

1
~/git/testpipenv > cat requirements.txt
2
requests==2.18.4
3
~/git/testpipenv > pipenv --three
4
Creating a virtualenv for this project…
5
Using /usr/local/bin/python3 to create virtualenv…
6
⠋Running virtualenv with interpreter /usr/local/bin/python3
7
Using base prefix '/usr/local/Cellar/python3/3.6.3/Frameworks/Python.framework/Versions/3.6'
8
New python executable in /Users/gigi.sayfan/.local/share/virtualenvs/testpipenv-0GShD6dy/bin/python3.6
9
Also creating executable in /Users/gigi.sayfan/.local/share/virtualenvs/testpipenv-0GShD6dy/bin/python
10
Installing setuptools, pip, wheel...done.
11
12
Virtualenv location: /Users/gigi.sayfan/.local/share/virtualenvs/testpipenv-0GShD6dy
13
Requirements.txt found, instead of Pipfile! Converting…
14
Warning: Your Pipfile now contains pinned versions, if your requirements.txt did.
15
We recommend updating your Pipfile to specify the "*" version, instead.

Here is the pinned version in the Pipfile that is recommended to change to "*":

1
[packages]
2
3
requests = "==2.18.4"

Let's install the dependencies now:

1
~/git/testpipenv > pipenv install

2
Pipfile.lock not found, creating…
3
Locking [dev-packages] dependencies…
4
Locking [packages] dependencies…
5
Updated Pipfile.lock (0b0daf)!
6
Installing dependencies from Pipfile.lock (0b0daf)
7
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 5/5 — 00:00:01
8
To activate this project's virtualenv, run the following:

9
 $ pipenv shell

10
~/git/testpipenv >

Editable Dependencies

You can tell Pipenv to install a path as editable. This is useful when you depend on packages you're developing and want to depend on your source package without actually installing them every time you make a change. In particular, it's useful for the current directory when you're actively working in it. To do that, use the -e and --dev flags:

1
> pipenv install '-e .' --dev

You need to have a proper setup.py file.

Managing Your Environment With Pipenv

You've already seen a lot of what Pipenv can do for you. Let's dig deeper into some additional commands and options.

Installing Packages

The pipenv install command supports several options:

  • --dev: Install both develop and default packages from Pipfile.lock.
  • --system: Use the system pip command rather than the one from your virtualenv.
  • --ignore-pipfile: Ignore the Pipfile and install from the Pipfile.lock.
  • --skip-lock: Ignore the Pipfile.lock and install from the Pipfile. In addition, do not write out a Pipfile.lock reflecting changes to the Pipfile. 

Depending on your workflow and preferences, you may want to use one or more of these options at different times.

Uninstalling Packages

To uninstall a dependency, type: pipenv uninstall <package name>. For example:

1
~/git/testpipenv > pipenv uninstall requests
2
Un-installing requests…
3
Uninstalling requests-2.18.4:
4
  Successfully uninstalled requests-2.18.4
5
6
Removing requests from Pipfile…
7
Locking [dev-packages] dependencies…
8
Locking [packages] dependencies…
9
Updated Pipfile.lock (625834)!

Note that I didn't have to specify "requests==2.8.14" when uninstalling, even though it was pinned in the Pipfile.

Locking Dependencies

If you want to generate a snapshot of your current dependencies (e.g. before a release), use the lock command. This is the key to deterministic and repeatable builds: pipenv lock --pre.

Removing the Virtual Environment

Pipenv is awesome, but you may clean up some of your virtual environments from time to time. It's as simple as pipenv --rm.

Security

Pipfile.lock takes advantage of some great new security improvements in pip. By default, the Pipfile.lock will be generated with the sha256 hashes of each downloaded package. This will allow pip to guarantee you’re installing what you intend to when on a compromised network or downloading dependencies from an untrusted PyPI endpoint.

In addition, Pipenv provides the check command, which checks for compliance with PEP 508 -- Dependency specification for Python Software Packages as well as package safety:

1
~/git/testpipenv > pipenv check .
2
Checking PEP 508 requirements…
3
Passed!
4
Checking installed package safety…
5
All good!

Conclusion

Pipenv finally brings Python packaging to the forefront of modern software development. It takes inspiration from other successful dependency management systems like Rust's Cargo and Javascript's Yarn. 

It marries virtual environments and package management and provides a superior experience with beautiful and colorful informational messages, and implicit best practices! I highly recommend that you start using Pipenv to manage your Python projects.

Additionally, don’t hesitate to see what we have available for sale and for study in the Envato Market, and don't hesitate to ask any questions and provide your valuable feedback using the feed below.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.