Skip to content
Point72Public

Repository files navigation

nepenthe logo, a greek-style pitcher jar with blue liquid

Forget your environment sorrows

Build Status codecov License PyPI

nepenthe builds, versions, and distributes large shared conda/PyPI environments. It solves a dependency manifest with rattler and records the result in a portable lockfile. Publish that lock to a versioned registry so other machines can install the same packages without re-solving.

  • Manifests describe dependencies and compose environments.
  • Rust solver uses rattler without a Python interpreter.
  • Multi-platform locks store linux-64, osx-arm64, and win-64 environments in one rattler_lock file, solved from one host.
  • Storage backends read and write specs using file://, s3://, and https://; package channels can use servers such as Artifactory.
  • Versioned registry tracks each environment independently and stores immutable, content-addressed locks.
  • Installation links packages into a prefix with rattler; conda, mamba, and micromamba are not required.
  • Conda export converts a lock to an @EXPLICIT spec for conda create --file.
  • Credentials are supplied at use time and never written into artifacts.

The CLI and Python API support building, publishing, and installing environments. Use pack / unpack bundles for air-gapped machines and pyproject.toml integration to check a project's dependencies against its shared environment.

Why nepenthe?

Frozen environments keep package versions consistent across machines and over time. Maintaining every pin by hand makes upgrades difficult and can lead to dependency hell.

With nepenthe, you specify dependencies and the version constraints you need. Each release solves that list and freezes the result. Consumers install those packages without re-solving.


An unpinned list of dependencies is solved once into a fully-pinned environment.

To update an environment, edit its root dependencies and build a new release. Teams can choose when to adopt it.

Editing the root dependency list over time (arrow down the left) produces a new fully-pinned environment at each step (right).

Unlike per-project lockfiles (pixi, uv) and re-solved manifests (environment.yml), nepenthe distributes versioned, immutable environments for multiple repos to share. See Motivation for the full comparison.

Documentation

For Read
Why it exists: the problem and alternatives Motivation
Users: concepts and how-to guides The wiki: Concepts, Manifests, Channels & Artifactory, Registry, Installing, Consuming in a Project, Packing, Python API, Backends
Getting started Installation
Contributors: how it's built Architecture

Quickstart

Create a manifest with the dependencies and version constraints you need:

# environment.yaml
project:
  name: demo
  channels: [conda-forge]
  platforms: [linux-64]
  python: ["3.11"]

dependencies:
  - numpy >=2
  - pip

environments:
  app: []

Build a fully pinned lockfile without a registry or conda:

nepenthe build --manifest environment.yaml --env app --output-dir .
# → app-py3.11.lock      the reproducible artifact: every install gives these exact packages

Export the lock as an @EXPLICIT spec for installation with conda or mamba:

nepenthe export --env app --lock app-py3.11.lock --platform linux-64 -o app.txt
# → app.txt              @EXPLICIT list of package URLs

Install from the spec with conda, or publish the lock and install with nepenthe:

# with conda/mamba, from the spec — nepenthe not needed on this machine
conda create --name app --file app.txt

# …or publish the lock to a versioned registry and install with nepenthe — no conda:
nepenthe build --manifest environment.yaml --env app \
  --registry file:///srv/nepenthe --version 1.0.0
nepenthe create app --registry file:///srv/nepenthe --python 3.11 --prefix ./envs/app
eval "$(nepenthe activate --prefix ./envs/app)"

The CLI is a single multicall binary: np and npb are symlinks to nepenthe (dispatch on the invoked name). np behaves exactly like nepenthe, and npb is a shortcut for nepenthe build (so the build step above is just npb --manifest environment.yaml --env app --output-dir .).

The Python API provides the same operations without requiring conda:

import nepenthe

# producer: solve "app" and publish its lock as v1.0.0
nepenthe.build("environment.yaml", "app", registry="file:///srv/nepenthe", version="1.0.0")

# consumer: install the latest published lock into a prefix
nepenthe.create("app", "file:///srv/nepenthe", "./envs/app")
print(nepenthe.activate("./envs/app"))

A repo can pin its shared environment in pyproject.toml and check that its dependencies are compatible:

[tool.nepenthe]
environment = "app"
registry = "file:///srv/nepenthe"
version = "1.0.0"
nepenthe sync      # install the referenced environment into .venv
nepenthe check     # verify [project.dependencies] are compatible (fails on conflicts)

See Installing Environments for the complete CLI reference, Consuming in a Project for the pyproject.toml workflow, Python API for the bindings, Manifests for the manifest format, and the runnable rust/examples/ for the producer side.

Contributing

Contributions are welcome under the Apache 2.0 license. See Contributing and Build from Source.

License

Apache-2.0. See LICENSE for details.