Okay folks. Time’s up. It’s too late to say that Python’s packaging ecosystem
terrible any more. I’m calling it.
Python packaging is not bad any more. If you’re a developer, and you’re trying
to create or consume Python libraries, it can be a tractable, even pleasant
experience.
I need to say this, because for a long time, Python’s packaging toolchain was …
problematic. It isn’t any more, but a lot of people still seem to think that
it is, so it’s time to set the record straight.
If you’re not familiar with the history it went something like this:
The Dawn
Python first shipped in an era when adding a dependency meant a veritable
Odyssey into cyberspace. First, you’d wait until nobody in your whole family
was using the phone line. Then you’d dial your ISP. Once you’d finished
fighting your SLIP or PPP client, you’d ask a netnews group if anyone knew of a
good gopher site to find a library that could solve your problem. Once you
were done with that task, you’d sign off the Internet for the night, and wait
about 48 hours too see if anyone responded. If you were lucky enough to get a
reply, you’d set up a download at the end of your night’s web-surfing.
pip search
it wasn’t.
For the time, Python’s approach to dependency-handling was incredibly
forward-looking. The import
statement, and the pluggable module import
system, made it easy to get dependencies from wherever made sense.
In Python 2.0, Distutils was introduced. This let Python developers
describe their collections of modules abstractly, and added tool support to
producing redistributable collections of modules and packages. Again, this was
tremendously forward-looking, if somewhat primitive; there was very little to
compare it to at the time.
Fast forwarding to 2004; setuptools
was created to address some of the
increasingly-common tasks that open source software maintainers were facing
with distributing their modules over the internet. In 2005, it added
easy_install
, in order to provide a tool to automate resolving dependencies
and downloading them into the right locations.
The Dark Age
Unfortunately, in addition to providing basic utilities for expressing
dependencies, setuptools
also dragged in a tremendous amount of complexity.
Its author felt that import
should do something slightly different than what
it does, so installing setuptools
changed it. The main difference between
normal import
and setuptools
import
was that it facilitated having
multiple different versions of the same library in the same program at the same
time. It turns out that that’s a dumb idea, but in
fairness, it wasn’t entirely clear at the time, and it is certainly useful (and
necessary!) to be able to have multiple versions of a library installed onto a
computer at the same time.
In addition to these idiosyncratic departures from standard Python semantics,
setuptools
suffered from being unmaintained. It became a critical part of
the Python ecosystem at the same time as the author was moving on to
other projects entirely outside of programming.
No-one could agree on who the new maintainers should be for a long period of
time. The project was forked, and many
operating systems’ packaging toolchains calcified around a buggy, ancient
version.
From 2008 to 2012 or so, Python packaging was a total mess. It was painful to
use. It was not clear which libraries or tools to use, which ones were worth
investing in or learning. Doing things the simple way was too tedious, and
doing things the automated way involved lots of poorly-documented workarounds
and inscrutable failure modes.
This is to say nothing of the fact that there were
critical security flaws in various parts
of this toolchain. There was no practical way to package and upload Python
packages in such a way that users didn’t need a full compiler toolchain for
their platform.
To make matters worse for the popular perception of Python’s packaging
prowess, at this same time, newer languages and environments were getting a
lot of buzz, ones that had packaging
built in at the very beginning and had a
much better binary distribution story. These
environments learned lessons from the screw-ups of Python and Perl, and really
got a lot of things right from the start.
Finally, the Python Package Index, the site which hosts all the open source
packages uploaded by the Python community, was basically a proof-of-concept
that went live way too early, had almost no operational resources, and was
offline all the dang time.
Things were looking pretty bad for Python.
Intermission
Here is where we get to the point of this post - this is where popular opinion
about Python packaging is stuck. Outdated information from this period
abounds. Blog posts complaining about problems score high in web searches.
Those who used Python during this time, but have now moved on to some other
language, frequently scoff and dismiss Python as impossible to package, its
packaging ecosystem as broken, PyPI as down all the time, and so on. Worst of
all, bad advice for workarounds which are no longer necessary are still easy to
find, which causes users to pre-emptively break their environments where they
really don’t need to.
From The Ashes
In the midst of all this brokenness, there were some who were heroically,
quietly, slowly fixing the mess, one gnarly bug-report at a time. pip
was
started, and its various maintainers fixed much of easy_install
’s
overcomplexity and many of its flaws. Donald Stufft
stepped in both on Pip and PyPI and improved the availability of the systems it
depended upon, as well as some
pretty serious vulnerabilities
in the tool itself. Daniel Holth wrote
a PEP for the wheel
format,
which allows for binary redistribution of libraries. In other words, it lets
authors of packages which need a C compiler to build give their users a way to
not have one.
In 2013,
setuptools
and distribute
un-forked,
providing a path forward for operating system vendors to start updating their
installations and allowing users to use something modern.
Python Core started distributing
the ensurepip module
along with both Python 2.7 and 3.3, allowing any user with a recent Python
installed to quickly bootstrap into a sensible Python development environment
with a one-liner.
A New Renaissance
I won’t give you a full run-down of the state of the packaging art.
There’s already a website for that. I will,
however, give you a précis of how much easier it is to get started
nowadays. Today, if you want to get a sensible, up-to-date python development
environment, without administrative privileges, all you have to do is:
| $ python -m ensurepip --user
$ python -m pip install --user --upgrade pip
$ python -m pip install --user --upgrade virtualenv
|
Then, for each project you want to do, make a new virtualenv:
| $ python -m virtualenv lets-go
$ . ./lets-go/bin/activate
(lets-go) $ _
|
From here on out, now the world is your oyster; you can pip install
to your
heart’s content, and
you probably won’t even need to compile any C for
most packages. These instructions don’t depend on Python version, either: as
long as it’s up-to-date, the same steps work on Python 2, Python 3, PyPy and
even Jython. In fact, often the ensurepip
step isn’t even necessary since
pip
comes preinstalled. Running it if it’s unnecessary is harmless, even!
Other, more advanced packaging operations are much simpler than they used to
be, too.
- Need a C compiler? OS vendors have been working with the open source
community to make this easier across the board:
| $ apt install build-essential python-dev # ubuntu
$ xcode-select --install # macOS
$ dnf install @development-tools python-devel # fedora
C:\> REM windows
C:\> start https://www.microsoft.com/en-us/download/details.aspx?id=44266
|
Okay that last one’s not as obvious as it ought to be but they did at
least make it freely available!
Importantly, PyPI will almost certainly be online. Not only that, but a
new, revamped site will be “launching” any day now.
Again, this isn’t a comprehensive resource; I just want to give you an idea of
what’s possible. But, as a deeply experienced Python expert I used to swear at
these tools six times a day for years; the most serious Python packaging issue
I’ve had this year to date was fixed by cleaning up my git repo to delete a
cache file.
Work Still To Do
While the current situation is good, it’s still not great.
Here are just a few of my desiderata:
- We still need better and more universally agreed-upon tooling for
end-user deployments.
- Pip should have a GUI frontend so that users can write Python stuff without
learning as much command-line arcana.
- There should be tools that help you write and update a
setup.py
. Or a
setup.python.json
or something, so you don’t actually need to write code
just to ship some metadata.
- The error messages that you get when you try to build something that needs a
C compiler and it doesn’t work should be
clearer and more actionable for
users who don’t already know what they mean.
- PyPI should automatically build wheels for all platforms by default when you
upload sdists; this is a huge project, of course, but it would be super
awesome default behavior.
I could go on. There are lots of ways that Python packaging could be better.
The Bottom Line
The real takeaway here though, is that although it’s still not perfect, other
languages are no longer doing appreciably better. Go is
still working through
a number of different options regarding dependency management and vendoring,
and, like Python extensions that require C dependencies, CGo is sometimes
necessary and
always a problem. Node has
had its own
well-publicized problems
with their dependency management culture and package manager. Hackage is cool
and all but everything takes
a literal geological epoch to compile.
As always, I’m sure none of this applies to Rust and Cargo is basically
perfect, but that doesn’t matter, because nobody reading this is
actually using Rust.
My point is not that packaging in any of these languages is particularly bad.
They’re all actually doing pretty well, especially compared to the state of the
general programming ecosystem a few years ago; many of them are making regular
progress towards user-facing improvements.
My point is that any commentary suggesting they’re meaningfully better than
Python at this point is probably just out of date. Working with Python
packaging is more or less fine right now. It could be better, but lots of
people are working on improving it, and the structural problems that prevented
those improvements from being adopted by the community in a timely manner have
almost all been addressed.
Go! Make some virtualenv
s! Hack some setup.py
s! If it’s been a while and
your last experience was really miserable, I promise, it’s better now.
Am I wrong? Did I screw up a detail of your favorite language? Did I forget
to mention the one language environment that has a completely perfect, flawless
packaging story? Do you feel the need to just yell at a stranger on the
Internet about picayune details? Feel free to
get in touch!