by zahlman | 2025-11-18 21:28:53+00:00
> One big problem with pip in the early days was that it only handled source distributions. This means it could download a gzip file of source code and put it in the right place, call some hooks that was it. If you wanted to package some C code that could be used from Python you would need to zip up the C code along with it’s Python bindings and publish it to PyPI, then when pip installed the package it would download the code and then run the compiler locally on your machine to turn the C code into something that could actually be executed. If you didn’t have all the C compilers and related tools on your machine you were in for a bad time.

This seems at least inaccurate. Pip could handle Setuptools' existing binary .egg format from the start, and these commonly even included pre-compiled .pyc files. They just weren't (often?) used to ship pre-compiled C because a) people had a more rigid security posture back then and wanted to compile for themselves; b) eggs were not wheels, and in particular wheel-specific standards like "manylinux" (https://peps.python.org/pep-0600/) didn't exist yet. (I don't think eggs even had a standardized way of specifying a platform in the filename.) And on the other hand, even today not everything is available as a wheel, and some people are on platforms that are not well covered by cross-compiled wheels.

Also, it wasn't pip "running the compiler" really. It would shell out to Setuptools for that. (Setuptools is no longer a dependency of pip, but for source distributions pip still dynamically obtains and uses either Setuptools or another build backend as specified by the package, per https://peps.python.org/pep-0517/.

> But it took a different road and instead of always creating your environment in the current directory

Just to nitpick, `python -m venv` lets you specify a path, it isn't just a name for the venv folder in the current directory.

> Because PyPI is for Python code and its dependencies it doesn’t want to think about the dependencies of those dependencies. So if your C code depends on something else then you need to bundle all of that together.

Not necessarily. Nothing in https://packaging.python.org/en/latest/specifications/binary..., as far as I can tell, actually requires a wheel to contain any Python code. It's just that interfacing C code in one installed wheel to another might be a bit tricky (I guess the simplest thing is to figure out the relative path to the .so in the other installed wheel, but probably the right thing is to mess around with `importlib_resources` and go through some thin Python wrappers — for example, uv is available as a wheel that basically just has the compiled Rust binary plus a Python script that deduces the path to that binary).

> This package manager effectively wrapped pip but also stored a lock file of the solve and allowed you to reproduce your environment easily. While I’m sure poetry has some other great features I tend to think of it as pip + locking.

It's meant for project management — in particular, a bunch of its subcommands will actually update the contents of pyproject.toml, as well as managing the poetry.lock. And it provides its own build backend (analogous to the role Setuptools plays now), called `poetry-core` as a package now but historically known as Masonry. It's meant (much like uv, and of course rye as you mention) to be an all-in-one tool for devs, rather than just a way for users writing a few lines of Python to access heavyweight dependencies. It just doesn't include the "get Python itself" step.

> Next-gen pip with uv

As you can imagine from the above, I think it's better termed "next-gen Poetry" than "next-gen pip". And "reimplementing pip directly" is missing a lot. The advantages come mainly from architectural changes rather than being written in Rust.