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 = [ ];
}