Usage
While the nixpkgs Python infrastructure is built mainly for manual packaging, pyproject.nix's builders are mainly targeted at lock file consumers like uv2nix.
This example shows the essence of implementing a lock file converter in pure Nix using pyproject.nix.
A real world implementation is more complex. To see a lock file converter built according to pyproject.nix best practices see uv2nix.
example.nix
{
python,
callPackage,
pyproject-nix,
lib,
}:
let
# Pyproject.nix packages quite a few, but not all build-system dependencies.
#
# We only package PyPI packages because lock file generators often miss this metadata, so it's required to help kickstart a Python set.
# This set is incomplete, and much smaller in both package count and scope than nixpkgs is.
baseSet = callPackage pyproject-nix.build.packages {
inherit python;
};
# A hypothetical over-simplified "lock file" format to demonstrate what typical usage would look like.
# This format is absent of important data such as PEP-508 markers and more.
#
# Also note that all sources are the same. In a real-world file these would of course all be different.
lock =
let
src = {
type = "pypi";
url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz";
hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760";
};
in
{
package-a = {
name = "a";
version = "1.0.0";
dependencies = { };
optional-dependencies = { };
inherit src;
build-system = {
flit-core = [ ];
};
};
package-b = {
name = "b";
version = "1.0.0";
# Depend on a with no optionals
dependencies = {
a = [ ];
};
inherit src;
build-system = {
flit-core = [ ];
};
};
package-c = {
name = "c";
version = "1.0.0";
dependencies = {
a = [ ];
};
# Has an optional dependency on b when cool_feature is activated
optional-dependencies = {
cool_feature = {
b = [ ];
};
};
inherit src;
build-system = {
flit-core = [ ];
};
};
package-d = {
name = "d";
version = "1.0.0";
dependencies = {
c = [ "cool_feature" ];
};
# A local package dependend on by it's path
src = {
type = "path";
path = ./packages/d;
};
build-system = {
flit-core = [ ];
};
};
};
# Create a PEP-508 marker environment for marker evaluation
environ = pyproject-nix.lib.pep508.mkEnviron python;
# Transform lock into a Pyproject.nix build overlay.
# This will create packages from the lock.
overlay =
pyfinal: _pyprev:
lib.mapAttrs (
name: lockpkg:
# If package is a local package use a project loader from pyproject-nix.lib.project
if lockpkg.src.type == "path" then
(
let
project = pyproject-nix.project.loadPyprojectDynamic {
projectRoot = lockpkg.src.path;
};
in
pyfinal.callPackage (
# Function called with callPackage
{
stdenv,
pyprojectHook,
resolveBuildSystem,
}:
# Call stdenv.mkDerivation with project
stdenv.mkDerivation (
# Render stdenv.mkDerivation arguments from project
pyproject-nix.build.lib.renderers.mkDerivation
{
inherit project environ;
}
{
inherit pyprojectHook resolveBuildSystem;
}
)
) { }
)
# If a package is a remote (pypi) package there is no ready made renderers to use.
# You need to apply your own transformations.
else if lockpkg.src.type == "pypi" then
pyfinal.callPackage (
{
stdenv,
fetchurl,
pyprojectHook,
resolveBuildSystem,
}:
stdenv.mkDerivation {
pname = lockpkg.name;
inherit (lockpkg) version;
src = fetchurl lockpkg.src;
nativeBuildInputs = [
# Add hook responsible for configuring, building & installing.
pyprojectHook
]
# Build systems needs to be resolved since we don't propagate dependencies.
# Otherwise dependencies of our build-system will be missing.
++ resolveBuildSystem lockpkg.build-system;
# Dependencies go in passthru to avoid polluting runtime package.
passthru = {
inherit (lockpkg) dependencies optional-dependencies;
};
}
) { }
else
throw "Unhandled src type: ${lockpkg.src.type}" null
) lock;
# Override set
pythonSet = baseSet.overrideScope (
_final: _prev: {
# Override build platform dependencies
#
# Use this when overriding build-systems that need to run on the build platform.
pythonPkgsBuildHost = overlay;
# Override target platform packages.
#
# Use this to override packages for the target platform.
pythonPkgsHostHost = overlay;
}
);
in
# Create a virtual environment containing our dependency specification
pythonSet.pythonPkgsHostHost.mkVirtualEnv "example-venv" {
# Depend on package
build = [ ];
}