Skip to main content

phynx — Python as the Nix Evaluator

A Python program constructs derivations (.drv) directly and hands them to the nix store/daemon, which remains the build and caching engine. The nix language evaluator is embedded (nix C API) and used only as a library — to call into nixpkgs functions (mkShell, stdenv.mkDerivation, lib.*) when their output is needed. This is the Guix architecture with full access to nixpkgs kept. Full design rationale: docs/design.md.

from phynx import drv, store, nixpkgs

pkgs = nixpkgs()                    # ONE long-lived embedded evaluator

mytool = drv(                       # a .drv constructed directly, no nix-lang
    name="mytool-1.0",
    system="x86_64-linux",
    builder=pkgs.bash.out("bin/bash"),
    args=["-e", store.text("build.sh", "gcc -o $out/bin/mytool $src")],
    env={"src": store.file("./mytool.c")},
)

# full Python is legal — no restricted subset:
probes = [drv(name=f"probe-{index}", ...) for index in range(10)]

shell = pkgs.mkShell(buildInputs=[pkgs.hello, mytool])   # hook into nix-lang

store.build(shell)

Quick start: build your first derivation

Write a plain Python file; the module attribute named default is the build target:

# mybuild.py
from phynx import drv

default = drv(
    name="greeting",
    system="x86_64-linux",
    builder="/bin/sh",                      # the build sandbox provides /bin/sh
    args=["-c", "echo hello > $out"],       # nix sets $out to the output path
)

Build it (registers the .drv, realises it, prints the output path):

$ bin/phynx build mybuild.py
out	/nix/store/…-greeting
$ cat /nix/store/…-greeting
hello

Or skip the CLI and build from Python directly:

from phynx import drv, store

greeting = drv(name="greeting", system="x86_64-linux",
               builder="/bin/sh", args=["-c", "echo hello > $out"])
print(store.build(greeting))                # {'out': '/nix/store/…-greeting'}

A runnable version ships in the repo: bin/phynx build examples/hello_chain.py (derivation chain), bin/phynx shell examples/dev_shell.py (mkShell mixing nixpkgs and phynx derivations).

Requirements

  • nix ≥ 2.28 on PATH with the nix-command experimental feature and a working store/daemon. The embedded evaluator loads the C API shared libraries (libnixexprc.so, …) from the active nix installation; override the location with PHYNX_NIX_LIB_DIR.
  • For nixpkgs(): a resolvable nixpkgs flake registry entry (or pass nixpkgs(path="/path/to/nixpkgs")).
  • Python ≥ 3.12, no Python dependencies (stdlib + ctypes only).

The API

call effect
drv(name=, system=, builder=, args=, env=, outputs=, fixed=) construct + register a derivation; returns a Drv handle
store.text(name, contents) add an inline script/text to the store
store.file(path, name=None, mode="nar") add a local file/directory to the store
store.build(target) realise a Drv, .drv path, or evaluator value; returns {output: path}
nixpkgs(ref=..., path=..., config=...) the embedded evaluator's nixpkgs attrset (lazy)
eval_nix(expression) evaluate one nix expression
primop(function) wrap a Python callable as a nix value, passable into nix code
register_primop(function, name=) expose a Python callable as builtins.<name> (call before first eval)
FixedOutput(hash_hex=, hash_algorithm=, ingestion_method=) fixed-output spec for drv(fixed=...)
Session() an isolated session (own registry/evaluator); module-level calls use a default session

Handles compose across both worlds:

  • mytool.out, mytool.output("dev"), mytool.out("bin/mytool") — output references; stringify to store paths.
  • pkgs.hello used in drv(env=...) becomes a scanned dependency; pkgs.hello.as_drv() gives the explicit Drv handle.
  • a phynx Drv inside pkgs.mkShell(buildInputs=[...]) is injected as a real derivation value (import /nix/store/….drv under the hood).

Dependency tracking (string contexts)

Nix strings carry a context tracking derivation references; Python strings do not. phynx keeps a registry of every store path the session has touched (derivation outputs, store.file/store.text results, derivations pulled out of the evaluator) and scans builder/args/env strings for registered paths at serialization time, populating inputDrvs/inputSrcs. A store path that never passed through the session is not recognized — route local files through store.file and nixpkgs packages through pkgs.<name>.

Where store paths come from

nix derivation add verifies output paths but does not compute them for the caller, so phynx computes them itself (ATerm serialization + hashDerivationModulo + nix base32) — the same choice Guix and Tvix made. The algorithms are frozen by every store path in existence, and every registration is verified by nix, which recomputes the paths and rejects a mismatch: nix stays the authority, phynx only precomputes what nix checks.

CLI

phynx build script.py [--attr NAME]   # run the script, build the target
phynx shell script.py [--attr NAME]   # run the script, exec nix develop

The target is --attr NAME, else a module attribute named default, else the last derivation the script created. See examples/hello_chain.py and examples/dev_shell.py.

bin/phynx is a self-contained launcher for running straight from a checkout (no install needed): it puts src/ on PYTHONPATH, pins PHYNX_NIX_LIB_DIR from the active nix installation, picks the project virtualenv's python when present (any python3 works — stdlib only), and execs the CLI. Symlink it onto your PATH if you like.

Layout

module role
phynx/hashing.py nix base32, hash folding, store-path rules
phynx/aterm.py .drv ATerm serialization + parser
phynx/derivation.py Derivation/Drv/OutputRef, hashDerivationModulo, drv() engine
phynx/registry.py session path registry + reference scanning
phynx/backend.py store registration/build backends (CLI today, C API capable later)
phynx/capi.py ctypes bindings over the nix C API shared libraries
phynx/evaluator.py embedded evaluator, NixValue, marshalling, primops, nixpkgs hook
phynx/session.py session object + default-session facade
phynx/cli.py phynx build / phynx shell

Known limits

  • structuredAttrs, content-addressed (floating/deferred) and impure derivations are not yet representable through drv().
  • Strings assembled inside nix code lose their context when extracted to Python; pull the derivation value itself (or its outputs) across instead.
  • phynx operates on the store imperatively; it is not a flake citizen (see design §4.5 for the escape hatch).

Tests

uv run pytest

Unit tests cover hashing/ATerm/scanning; integration tests build real derivations against the local daemon and verify phynx's path computation against nixpkgs' own derivations (skipped when nix or nixpkgs is absent).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

phynx-0.1.0.tar.gz (40.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

phynx-0.1.0-py3-none-any.whl (35.2 kB view details)

Uploaded Python 3

File details

Details for the file phynx-0.1.0.tar.gz.

File metadata

  • Download URL: phynx-0.1.0.tar.gz
  • Upload date:
  • Size: 40.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.19

File hashes

Hashes for phynx-0.1.0.tar.gz
Algorithm Hash digest
SHA256 84e0e517e40f2ddff70ea6f67ac52a04c923c62a47f3a14b6fe83f7fe3fc36cf
MD5 eef96f84bf71e314e4c324945bb2e2ae
BLAKE2b-256 47af6c966761381a1caa7db406d13c3449578df4e05562dc65d18e4cc4bdb97e

See more details on using hashes here.

File details

Details for the file phynx-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: phynx-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 35.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.19

File hashes

Hashes for phynx-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4f909968438d57720c046cb6d7d7a459e4b7a480f8dd21c91b75d412ec19fff1
MD5 c8b38cca6e3aacade87dd120a07eaf9b
BLAKE2b-256 aebf2174de293e4b70f58555127b5d2963e527d52efa7cc4d12215775e58f3ec

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page