Help using poetry in a flake devshell

The Context

One day I will be cool enough to just fix my team’s dependencies so that I can use poetry2nix for everything. Today is not that day.

Instead I’m using a flake devshell for things that poetry can’t handle (like poetry itself) and I’m using poetry for everything that poetry can handle (like fastapi). This usually works. poetry run is invoked from the devshell, so I end up with both sets of dependencies.

The Problem

I think that something from the devshell is conflicting with something in poetry. No tests run if I use the devshell, because there’s a conflict in conftest.py

❯ nix develop
$ poetry run pytest
ImportError while loading conftest '/Users/matt/src/myproj/tests/conftest.py'.
tests/conftest.py:14: in <module>
    from fastapi.testclient import TestClient
...
../../Library/Caches/pypoetry/virtualenvs/myproj-R0lYn3as-py3.11/lib/python3.11/site-packages/fastapi/exceptions.py:6: in <module>
    from typing_extensions import Annotated, Doc  # type: ignore [attr-defined]
E   ImportError: cannot import name 'Doc' from 'typing_extensions' (/nix/store/0wi4xxrgz86n5c8n8snaq7wl5mmc229n-python3.11-typing-extensions-4.7.1/lib/python3.11/site-packages/typing_extensions.py)

It works if I exit the devshell… until tests fail due to missing dependencies

❯ poetry run pytest -s -vv 'tests'
... several tests pass ...
... a test fails because `ruff` is missing from the environment (defined in the flake, which is now ignored)...

The problem appeared when fastapi updated. There’s a conversation about it here: Module 'Doc' import error when using FastAPI · tiangolo/fastapi · Discussion #10476 · GitHub, many people are having this problem in google collab. I bet that the conflict that’s present in collab is also in my devshell.

The Question

This is the path from the error:

/nix/store/0wi4xxrgz86n5c8n8snaq7wl5mmc229n-python3.11-typing-extensions-4.7.1/lib/python3.11/site-packages/typing_extensions.py

How can I trace it back to a package in my devshell which it is presumably conflicting with?

Candidates are:

devShells.default = pkgs.mkShell {
  packages = [
    (pkgs.poetry.overrideAttrs (previousAttrs: {
      makeWrapperArgs = ''
        --suffix PYTHONPATH : .
      '';
    }))
    deps.kustomize
    pkgs.python311
    pkgs.python311Packages.python-lsp-server
    pkgs.python311Packages.python-lsp-ruff
    pkgs.python311Packages.pylsp-rope
    pkgs.python311Packages.pylsp-mypy
    pkgs.python311Packages.pudb

    (pkgs.pre-commit.overrideAttrs (previousAttrs: {
      doCheck = false;
      makeWrapperArgs = ''
        --prefix PYTHONPATH : .
        --prefix PYTHONPATH : $PYTHONPATH
        --prefix PYTHONPATH : ${pkgs.python311Packages.python}/${pkgs.python311Packages.python.sitePackages}
        --prefix PYTHONPATH : ${pkgs.python311Packages.ruamel-yaml}/lib/python3.11/site-packages
      '';
    }))
    pkgs.python311Packages.psycopg
    #pkgs.python311Packages.greenlet  # for locust, which has cpp deps
    #pkgs.python311Packages.mitmproxy
    pkgs.ruff
    pkgs.just
    pkgs.jq
    pkgs.teleport
    pkgs.kubectl
    pkgs.wget
    pkgs.tilt
    pkgs.kind
    pkgs.circleci-cli
    pkgs.openssl
    pkgs.yq-go
    pkgs.actionlint
  ];
  nativeBuildInputs = (if pkgs.stdenv.isLinux then [ pkgs.autoPatchelfHook ] else []);

  shellHook = ''
    # pip builds ruff in a way that references some dependencies implicitly by filepath
    # this breaks on NixOS which likes to be explicit about such things
    # https://github.com/astral-sh/ruff-pre-commit/issues/22#issuecomment-1684170057
    if cat /etc/os-release | grep NixOS &> /dev/null ; then
      if [ -d ~/.cache/pre-commit ] ; then
        patch=$(autoPatchelf ~/.cache/pre-commit/)
        if [[ $? -ne 0 ]]; then
          echo "$patch"
          exit 1
        else
          echo "patched ~/.cache/pre-commit"
        fi
      fi
    fi

    export KIND_CLUSTER_IMAGE='kindest/node:v1.25.3'
    export export PYTHONBREAKPOINT="pudb.set_trace"
    '';
};

This gave me my list of culprits:

nix-store --query --referrers-closure /nix/store/0wi4xxrgz86n5c8n8snaq7wl5mmc229n-python3.11-typing-extensions-4.7.1/lib/python3.11/site-packages/typing_extensions.py
/nix/store/0wi4xxrgz86n5c8n8snaq7wl5mmc229n-python3.11-typing-extensions-4.7.1
/nix/store/xl8qq0xv84pqsgdc5l9i42lsr76nh218-python3.11-async-timeout-4.0.3
/nix/store/1fsr1w7j2cx66kq8fzzpqi88sc94i7d2-python3.11-redis-5.0.0
/nix/store/0jqcci9bm53xkxyf9j8n536fx4xc40j6-python3.11-cachy-0.3.0
/nix/store/s9jkw35p84ygszlf9qp29cc5iavbb99n-python3.11-mypy-1.5.1
/nix/store/1mm4ndiw5aq2qgs8dmaysgxpm9h5ni9x-python3.11-pylsp-mypy-0.6.7
/nix/store/fiaj73dvfchfs5vp1r58r1c86f41s25y-python3.11-psycopg-3.1.12
/nix/store/289x9s7az1janl9wvz35ihy5dpfs11db-nix-shell-env
/nix/store/5cwyyrplzdw82qnj0i17nbckf7pa2fzk-nix-shell-env
/nix/store/5kjpc3qa3n93mv9fqym6kd3fzl59f1ik-nix-shell-env
/nix/store/g1nj48s3lcl9blrjqahbcjhww85vx3a7-nix-shell-env
/nix/store/l2cl8iy9kv329brkgr9k4l08c2iddr8x-nix-shell-env
/nix/store/naa98n9y3541g6cr18qyvx9vwb9f16l4-nix-shell-env
/nix/store/ycss8x9yf74s5g6l691s5m6nigppray4-nix-shell-env
/nix/store/yy2na1v16avgdhklylad2ampaa85122p-nix-shell-env

Removing both pylsp-mypy and psycopg “solved” the problem.

If anybody understands why poetry would let this happen (the PATH ordering seems right), I’d be keen to know.

This happens because of how setup hooks uses $PYTHONPATH and pollutes downstream through the environment.

There is no magical silver bullet to avoid this but generally you should use python3.withPackages.
It’s wrapper doesn’t propagate dependencies, so it doesn’t leak.

Other applications written in Python still leaks through $PYTHONPATH however.

Another thing you can add to your workflow to avoid this is to simply unset PYTHONPATH in a shellHook.

1 Like