poetry2nix

repository·master·Indexed 21 days ago

https://github.com/nix-community/poetry2nix

A tool that converts Poetry projects into Nix derivations by parsing pyproject.toml and poetry.lock files. It provides functions such as mkPoetryApplication for packaging Python applications, mkPoetryEnv for creating development environments, and mkPoetryPackages for accessing project metadata and generated packages. It supports Nix Flakes, overlays, private repositories via NETRC, and custom Python package overrides.

Tokens
5.8K
Snippets
15
Records
22
Agent score
76%

What's inside poetry2nix

  1. Understand how overrides interact with `poetry.lock`

    master

    When using Nix to override a package version, the Nix override supersedes the version specified in poetry.lock.

    If poetry.lock specifies version 1.0 for foobar, but your Nix configuration overrides foobar to version 2.0, the resulting build will use version 2.0. This can lead to discrepancies between your lock file and your actual Nix build environment.

    let poetryOverrides = final: prev: {
        foobar = prev.foobar.overridePythonAttrs (old: rec {
          version = "2.0";
          src = prev.pkgs.fetchFromGitHub {
            owner = "fakerepo";
            repo = "foobar";
            rev = "refs/tags/${version}";
            sha256 = lib.fakeSha256;
          };
        });
      };
    in
    poetry2nix.mkPoetryApplication {
      projectDir = ../.;
      overrides = poetry2nix.overrides.withDefaults poetryOverrides;
    }
  2. Understand how poetry2nix interacts with nixpkgs

    master

    Package Resolution

    poetry2nix overlays packages from your poetry.lock file on top of nixpkgs. When a package is defined in the lock file, the poetry2nix version is used, and the corresponding package in nixpkgs is ignored.

    Build Dependencies

    If a package is required for a build but is not present in your poetry.lock file (such as common build-system requirements), poetry2nix will fall back to using the package definition from nixpkgs.

  3. Quickstart: Package a Python application without Flakes

    master

    To turn a Poetry project into a Nix package without using Flakes, create a default.nix file in your project root (next to pyproject.toml and poetry.lock). This method typically assumes you are using the niv tool to pin nixpkgs and poetry2nix.

    Steps:

    1. Initialize dependencies with niv:
      nix-shell -p niv
      niv init
      niv add nix-community/poetry2nix
    2. Create default.nix: Use poetry2nix.mkPoetryApplication to define your package.
    3. Build the application:
      nix-build default.nix
    4. Run the application: The executable will be located in the ./result/bin/ directory. Replace <script> with the name defined in your [tool.poetry.scripts] section in pyproject.toml.
    # file: default.nix
    let
      sources = import ./nix/sources.nix;
      pkgs = import sources.nixpkgs { };
      # Let all API attributes like "poetry2nix.mkPoetryApplication"
      # use the packages and versions (python3, poetry etc.) from our pinned nixpkgs above
      # under the hood:
      poetry2nix = import sources.poetry2nix { inherit pkgs; };
      myPythonApp = poetry2nix.mkPoetryApplication { projectDir = ./.; };
    in
    myPythonApp
  4. Quickstart: Package a Python application with Flakes

    master

    If you use Nix Flakes, you can import poetry2nix directly as an input. This approach allows you to use the poetry2nix.lib.mkPoetry2Nix helper to create a custom mkPoetryApplication function that uses the specific versions of Python and Poetry pinned in your nixpkgs input.

    Steps:

    1. Create flake.nix using the pattern below.
    2. Run the application:
      nix run .

    Using Templates:

    To start a new project with a pre-configured flake template, run:

    nix flake init --template github:nix-community/poetry2nix
    # file: flake.nix
    {
      description = "Python application packaged using poetry2nix";
    
      inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
      inputs.poetry2nix.url = "github:nix-community/poetry2nix";
    
      outputs = { self, nixpkgs, poetry2nix }: 
        let
          system = "x86_64-linux";
          pkgs = nixpkgs.legacyPackages.${system};
          # create a custom "mkPoetryApplication" API function that under the hood uses
          # the packages and versions (python3, poetry etc.) from our pinned nixpkgs above:
          inherit (poetry2nix.lib.mkPoetry2Nix { inherit pkgs; }) mkPoetryApplication;
          myPythonApp = mkPoetryApplication { projectDir = ./.; };
        in
        {
          apps.${system}.default = {
            type = "app";
            # replace <script> with the name in the [tool.poetry.scripts] section of your pyproject.toml
            program = "${myPythonApp}/bin/<script>";
          };
        };
    }
  5. Run tests for poetry2nix

    master

    If you are contributing to the project, you can run the test suite using nix-build.

    • Run all tests: nix-build --keep-going --show-trace tests/default.nix
    • List available test names: nix eval --impure --expr 'let pkgs = import <nixpkgs> {}; in pkgs.lib.attrNames (import ./tests/default.nix {})'
    • Run a specific test (e.g., bcrypt): nix-build --attr bcrypt --keep-going --show-trace tests/default.nix
    • Test with a specific channel (e.g., unstable): nix-build --expr 'with import <unstable> {}; callPackage ./tests/default.nix {}'
    nix-build --attr bcrypt --keep-going --show-trace tests/default.nix
  6. Fix 'Could not find a version that satisfies the requirement' errors

    master

    This error often occurs when a package requires a build-time dependency like setuptools-scm to resolve its versioning. You can resolve this by adding the required tool to nativeBuildInputs for that specific package via an override.

    Example: If python-ulid fails because it needs setuptools-scm, apply this override:

    {
      python-ulid = prev.python-ulid.overridePythonAttrs (
        old: {
          nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ final.setuptools-scm ];
        }
      );
    }
  7. Use the poetry2nix Flake Overlay

    master

    The poetry2nix flake provides an overlay that allows you to merge poetry2nix into your pkgs set. This enables you to access its API via pkgs.poetry2nix.

    To use the overlay in a flake.nix, replace your standard pkgs and mkPoetryApplication definitions with the following pattern:

    pkgs = nixpkgs.legacyPackages.${system}.extend poetry2nix.overlays.default;
    myPythonApp = pkgs.poetry2nix.mkPoetryApplication { projectDir = self; };
  8. Handle Rust and Cargo hash errors

    master

    When dealing with Rust-based dependencies that cause Cargo hash mismatches, you have two primary options:

    1. Pin the package version in pyproject.toml

    Force the dependency to a specific version that has a known working hash. Important: You must use exact equality (=) and avoid the caret (^) prefix.

    [tool.poetry.dependencies]
    bar = "1.0.0"

    Note: Run poetry lock after making this change.

    2. Use preferWheels as an escape hatch

    If you trust the PyPI wheel distributions, you can tell poetry2nix to prefer pre-compiled wheels instead of building from source. This bypasses many Cargo/Rust build issues but is more vulnerable to supply chain attacks.

    poetry2nix.mkPoetryApplication {
      projectDir = ./.;
      preferWheels = true;
    }
    poetry2nix.mkPoetryApplication {
      projectDir = ./.;
      preferWheels = true;
    }
  9. Fix ModuleNotFoundError by overriding build dependencies

    master

    If you encounter a ModuleNotFoundError: No module named 'PACKAGENAME' during build, it usually means a Python dependency requires a specific build tool (like setuptools, pdm, or flit) that isn't present in its build environment. Since poetry2nix prefers building from source, you must explicitly add the missing tool to the package's buildInputs using overridePythonAttrs within the overrides attribute of mkPoetryApplication.

    Key details:

    • Package names are normalized according to PEP-517 (e.g., flit_scm becomes flit-scm).
    • You can use the short name (e.g., setuptools) instead of the full path (e.g., python39Packages.setuptools).
    • If you have multiple overrides, you can chain them or use a mapping pattern to reduce repetition.
    poetry2nix.mkPoetryApplication {
      projectDir = ./.;
      overrides = poetry2nix.defaultPoetryOverrides.extend
        (final: prev: {
          django-floppyforms = prev.django-floppyforms.overridePythonAttrs
          (
            old: {
              buildInputs = (old.buildInputs or [ ]) ++ [ prev.setuptools ];
            }
          );
        });
    }
  10. Use private Python repositories with authentication

    master

    To use private repositories (e.g., via [[tool.poetry.source]] in pyproject.toml), poetry2nix requires a NETRC file for authentication. Follow these three steps:

    1. Create a .netrc file: Place it in your home directory (~/.netrc) or a central location like /etc/nix/netrc.

      machine https://example.org
      login <repository-username>
      password <repository-password-or-token>
    2. Mount the file into the Nix sandbox: Use the extra-sandbox-paths option so the build process can access the file.

      • Recommended (Per-build): Pass it directly in the terminal to avoid exposing secrets to all builds.
      • Global (Not recommended): Add to /etc/nix/nix.conf via extra-sandbox-paths /etc/nix/netrc.

      Note: You must be a trusted-user in your Nix configuration for this to work.

    3. Pass the NETRC environment variable: Tell poetry2nix where the file is located inside the sandbox.

      • For Flakes: You must add the NETRC environment variable to the nix-daemon service (e.g., via systemd on NixOS).
      • For Non-Flake projects: Use a fake Nix search path -I NETRC=<path> in your command.

    Important: Using impureEnvVars in attributes does not work for this purpose.

    # For non-flake projects
    nix-build -I NETRC=/etc/nix/netrc --option extra-sandbox-paths /etc/nix/netrc default.nix
    
    # For flake projects
    nix build . --extra-sandbox-paths /etc/nix/netrc
  11. Create a custom poetry2nix instance with custom overrides

    master

    You can create a custom instance of poetry2nix using overrideScope. This is useful for injecting custom defaultPoetryOverrides globally for your project.

    Using overrideScope in a Nix expression:

    let
      p2nix = poetry2nix.overrideScope (final: prev: {
        defaultPoetryOverrides = prev.defaultPoetryOverrides.extend (pyfinal: pyprev: {
          my-custom-pkg = pyprev.my-custom-pkg.overridePythonAttrs (oldAttrs: { });
        });
      });
    in
      p2nix.mkPoetryApplication {
        projectDir = ./.;
      }

    Using as a Nixpkgs Overlay:

    let
      pkgs = import <nixpkgs> {
        overlays = [
          (final: prev: {
            poetry2nix = prev.poetry2nix.overrideScope (p2nixfinal: p2nixprev: {
              defaultPoetryOverrides = p2nixprev.defaultPoetryOverrides.extend (pyfinal: pyprev: {
                my-custom-pkg = pyprev.my-custom-pkg.overridePythonAttrs (oldAttrs: { });
              });
            });
          })
        ];
      };
    in 
      pkgs.poetry2nix.mkPoetryApplication { projectDir = ./.; }
  12. Use the poetry2nix CLI to supplement git dependency hashes

    master

    The poetry2nix-cli is a tool designed to supplement sha256 hashes for git dependencies in your Python projects. It is provided as a Nix derivation that wraps the core CLI logic and ensures nix-prefetch-git is available in the PATH during execution to facilitate hash calculation for git-based dependencies.

    # The CLI is installed via Nix and provides the `poetry2nix` command
    poetry2nix [args]