Nix shell with Python packages through pip

I am trying to create a small dev environment for a project involving Python, using Nix shells on Ubuntu. For now, I want to rely on pip to manage my Python dependencies, not nixpkgs. The project has a few large dependencies, such as PySide2. I noticed that I should use Python virtual environments in Nix shells to avoid permission issues when installing packages, so this is what I do. My shell.nix:

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
    buildInputs = [
        (pkgs.python312.withPackages (ps: [ps.pip]))
        pkgs.stdenv.cc.cc.lib   # need libstdc++.so.6
    ];
    shellHook = ''
        export TMPDIR=/tmp
        export "LD_LIBRARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH"
        export VENV_DIR=$(mktemp -d)
        python -m venv $VENV_DIR
        source $VENV_DIR/bin/activate
        echo "Virtual environment is ready and activated in $VENV_DIR."
        pip install pyside2
    '';
}

I launch this shell with nix-shell --pure, which works, but when I run python -c "import PySide2", I am greeted with a segmentation fault. I am assuming this problem is not particularly related to PySide2, rather a more fundamental setup issue, but I don’t know. What are possible causes for this, and how would I troubleshooting these kinds of issues? Thanks!

1 Like

I recommend using devenv for this as it provides first-class support for native Python packages.

As such, what you’re trying to do can be simply written as:

# devenv.nix
{ pkgs, lib, config, inputs, ... }:

{
  languages.python = {
    enable = true;
    venv.enable = true;
    venv.requirements = ''
      pyside2
    '';
  };

  enterShell = ''
    python -c "import PySide2" && echo "No errors!"
  '';
}

Here you’ll find all the Python options you can use.

I have a few python projects where other developers aren’t using nix, I typically handle them like the snippet below. But mine seem to have simpler requirements than yours.

pkgs.mkShell {
            # The Nix packages provided in the environment
            packages = [
              pkgs.python311
              pkgs.python311Packages.pip
              # Whatever other packages are required
            ];
            shellHook = ''
              python -m venv .venv
              source .venv/bin/activate
              pip install -r requirements.txt
            '';
};

I am basically doing the same thing, but PySide2 won’t work out of the box after pip installing it.

After some digging, I managed to satisfy all the system library dependencies that PySide2 has, at least to an extent where my application runs (note this includes some more than what’s strictly necessary):

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell rec {
    buildInputs = with pkgs; [
        # python
        python310
        python310Packages.pip
        python310Packages.virtualenvwrapper

        # pyside2 dependencies
        qt5.full stdenv.cc.cc.lib
        glibc glib libGL zlib bzip2 openssl libpng libjpeg
        ffmpeg libxkbcommon fontconfig freetype zstd dbus
        xorg.libXrender xorg.libxcb xorg.libX11 xorg.libXext 
        xorg.libXcursor xorg.xhost
    ];

    shellHook = ''
        export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath buildInputs}"
        export TMPDIR=/tmp  && export VENV=$(mktemp -d)
        virtualenv $VENV
        source $VENV/bin/activate
        pip install pyside2
    '';
}

I still don’t know what caused the segfault.

For other people on a similar mission, there are wrappers for PySide2 and PySide6 in nixpkgs which will also ensure dependencies are satisfied, so you can do something like this (notice --system-site-packages when creating the virtual environment):

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell rec {
    buildInputs = with pkgs; [
        python310
        python310Packages.pip
        python310Packages.virtualenvwrapper
        python310Packages.pyside6
    ];

    shellHook = ''
        export TMPDIR=/tmp  && export VENV=$(mktemp -d)
        virtualenv --system-site-packages $VENV
        source $VENV/bin/activate
    '';
}

but I was looking for a more generic solution. I spend enough time debugging the PySide2 Python package that I don’t feel like adding another wrapper that can fail.