Fixing Python Import Resolution in Nix with Direnv
Introduction
I've started using development environments with nix-shell for my personal projects.
I greatly prefer the consistency of dropping into a nix-shell, over reading
Python's venv manual for the umpteenth time, then yak-shaving into whether I should
be using pyenv-virtualenv instead.
Here's a typical shell.nix file for Python 3.10 and some common packages like numpy.
{ pkgs ? import <nixpkgs> { } }:
let
my-python = pkgs.python310;
python-with-my-packages = my-python.withPackages
(p: with p; [ numpy pytorch matplotlib requests python-dotenv ]);
in pkgs.mkShell {
buildInputs = [
python-with-my-packages
];
shellHook = ''
PYTHONPATH=${python-with-my-packages}/${python-with-my-packages.sitePackages}
'';
}Relying on this, it's likely you will see your editor (specifically, your LSP implementation of choice) highlighting import errors, like so1:

However, there aren't actually any errors at runtime. When run inside the nix-shell environment,
we certainly have pytorch, matplotlib, etc.
So, how do we make our LSP server aware of the packages we have in our nix-shell environment?
Solution
TLDR:
- Install direnv.
- Install an editor extension/plugin for direnv, e.g. emacs-direnv.
If you use Doom Emacs, simply enable the
direnvmodule. echo use_nix > .envrcin your project directory.direnv allow .in your project directory.
Now reload your editor.
Explanation
Pyright, my language server of choice for Python, gets the installed packages directory
from the PYTHONPATH environment variable.
So, a subpar approach would be to hard-code that to the location in the /nix/store that
contains the packages.
Anytime you changed the Python version, for example, this value would change.
Instead of this, we can use direnv's clever integration with Nix.
The described use case is automatically loading environment variables in a shell; our use case is automatically loading environment variables into our editor.
So every time you open your project, direnv sees use_nix in .envrc, resolves your shell.nix file, and injects any environment variables (PYTHONPATH) into your editor, using your direnv editor plugin.

That's it! Pyright sees PYTHONPATH and we have working import resolution.
Follow-up
- nix-direnv may be quicker than direnv, reducing the time to resolve the nix-shell environment.
- lorri appears to be a more feature-rich replacement for the virtual environment use case.
In this case, the packages that aren't highlighted are the ones previously installed on my machine.