Python with flakes

Hello all,

I’m using Nix for deployment in a corporate setting and I’m trying to find a good way of packaging/distributing a python project. It uses quite a number of dependencies and there is a focus on reliability, so I’ve been using flakes to pin versions as precisely as possible. Flakes also seem like a good path in that, as pure functions, there’s the potential to introduce cacheing at some point in future.

As a starting point, I’ve been using the poetry2nix approach within a flake to create an environment. This has worked well, except that:

  • A couple of packages don’t work with poetry2nix, and I’ve had to install them via pip instead. This works but then starts to make the whole exercise feel redundant.
  • The build times are quite slow and (in my experience so far) seem to be rather variable. If a new user does poetry install on a newly built environment with just a pyproject.toml and poetry.lock then it can take an hour or so. Partly this is time downloading dependencies and partly time building them.

Given this I am wondering if a simpler approach would be to just download wheels for all the dependencies, and have those as an input to the flake, eg by creating a derivation to hold the wheel files. I could then use a shellHook that would install them via something like pip install --user ${wheels-derivation}/*.whl. From brief experimentation this seems to be much faster to build, but more cumbersome to maintain.

I am assuming that either of these approaches would lend themselves to cacheing further down the line. In my mind I’m not entirely clear on how the cacheing process works, and what it means to, say, cache the output of a nix develop or nix build call on a given flake. Particularly if the flake alters local local state as a side-effect, eg with pip install --user calls.

If anyone has any thoughts or experience in this area I’d be very glad to hear about it.


1 Like

Mach-nix supports installing wheels from pypi, as well as a variety of other options for grabbing python packages (including “use the ones from nixpkgs” and “pick the first available from these places”).

It also gives you a less system-breaking approach to installing packages for a devShell/shell.nix, because it doesn’t use pip to install them into the python path, potentially breaking other applications and projects.

You should probably use mach-nix for the dependencies that don’t work with poetry either way. Using pip completely defies the purpose.

The advantage of poetry2nix is precisely that all packages are built from scratch, so you can assert the provenance and know they’re reproducibly buildable. You don’t get those guarantees with wheels, they could contain absolutely anything.

Whether this is worth the build time depends on your use case; do note that a shared binary cache with your dependencies pre-built would significantly improve the situation.

If you want to understand nix caching, I’d suggest looking at the nix pills. In essence, any derivation built with nix will end up in /nix/store, and nix knows where a derivation will end up so it can reuse the built artifact from there. If another device has access to that store (i.e. if it’s in a shared binary cache), nix will download the binary from there instead of rebuilding.

This requires using a derivation, like the ones generated by poetry2nix and mach-nix. If you run pip install in some script that your flake provides, none of that is cached. You’re doing something very odd and I wonder how it’s even working :slight_smile:

1 Like

I have the following setup for a Django project which seems to work quite well so far, it uses mach-nix.
The container image currently isn’t working yet but the devShell is working fine.
However please be aware that this is only a personal project and far from being run in production.

Thank you, this was helpful. mach-nix seems to be much less picky than poetry2nix, all of the packages I’ve tried with it so far have worked, so it feels like it might be a good solution. poetry2nix with a cache might also be a viable option if it reduced build times.

I don’t think I was thinking things through particularly clearly with regards to pip. Pip doesn’t install to the nix store, so obviously it’s no good for reproducability, caching etc.

Thank you, that does work well. I’ve taken what you have here and created what I think is a minimal example of it, here: nix-development-configs/pythonflake-mach-nix at main · harris-chris/nix-development-configs · GitHub. I added an buildPythonApplication call which nix run can then call.


Glad it worked.
Another thing I like to do is this line here:

With this you get a symlink to the Python environment which can be useful to point your editor to the correct Python version.