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).

Metadata

Release files for phynx 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for phynx 0.1.0
File Size Uploaded Provenance Actions
phynx-0.1.0.tar.gz 40.6 kB Without provenance Details for phynx-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for phynx 0.1.0
File Interpreter ABI Platform Actions
phynx-0.1.0-py3-none-any.whl Python 3 none any Details for phynx-0.1.0-py3-none-any.whl

Total release size: 75.7 kB

Release files / phynx-0.1.0.tar.gz

Download URL phynx-0.1.0.tar.gz
Size 40.6 kB
Tags Source
SHA-256 checksum
How to use checksums
84e0e517e40f2ddff70ea6f67ac52a04c923c62a47f3a14b6fe83f7fe3fc36cf
BLAKE2b-256 checksum
How to use checksums
47af6c966761381a1caa7db406d13c3449578df4e05562dc65d18e4cc4bdb97e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.19

Release files / phynx-0.1.0-py3-none-any.whl

Download URL phynx-0.1.0-py3-none-any.whl
Size 35.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4f909968438d57720c046cb6d7d7a459e4b7a480f8dd21c91b75d412ec19fff1
BLAKE2b-256 checksum
How to use checksums
aebf2174de293e4b70f58555127b5d2963e527d52efa7cc4d12215775e58f3ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.19

Security for phynx 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

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