Script metadata format#
conda-exec supports PEP 723 inline
script metadata for declaring dependencies directly inside a Python script.
When you run conda exec script.py, conda-exec parses any metadata block
in the file and creates a cached environment with the declared dependencies.
Block syntax#
Metadata is embedded in a comment block delimited by # /// script and
# ///. Each line inside the block must start with # (hash, space),
and the content is parsed as TOML.
# /// script
# requires-python = ">=3.12"
# dependencies = ["requests", "rich"]
#
# [tool.conda]
# channels = ["conda-forge", "bioconda"]
# dependencies = ["samtools>=1.19"]
# ///
The block can appear anywhere in the file. Only the first # /// script
block is used. Blank lines within the block use a bare # (no trailing
space required).
Standard fields#
These fields follow the PEP 723 specification and are compatible with other tools that support inline script metadata (such as uv and pipx).
requires-python#
Python version constraint as a PEP 440 version specifier.
# /// script
# requires-python = ">=3.11"
# ///
Translated to a conda python spec in the environment solve. For
example, requires-python = ">=3.11" becomes the spec python >=3.11.
After the environment is created, conda-exec validates that the resolved
Python version satisfies the constraint and reports a clear error if it
does not.
dependencies#
A list of PyPI package names, following PEP 508 syntax.
# /// script
# dependencies = ["requests>=2.28", "rich"]
# ///
Requires conda-pypi
to be installed. When PyPI dependencies are present, the conda-pypi
channel is added to the channel list automatically. If conda-pypi is
not installed, conda-exec raises a PyPIDependencyError.
Extension fields#
These fields are specific to conda-exec and live under the
[tool.conda] TOML table, following PEP 723’s convention for
tool-specific configuration.
[tool.conda].dependencies#
A list of conda package specs (match specs).
# /// script
# [tool.conda]
# dependencies = ["numpy>=1.24", "pandas", "scipy"]
# ///
These are passed directly to the conda solver. Any valid conda match
spec syntax is accepted (e.g. numpy>=1.24,<2, python-dateutil).
[tool.conda].channels#
A list of conda channels to search for packages.
# /// script
# [tool.conda]
# channels = ["conda-forge", "bioconda"]
# dependencies = ["samtools>=1.19"]
# ///
If no channels are specified (neither in the metadata nor via
--channel on the command line), conda-exec defaults to conda-forge.
Automatic Python spec#
If no dependency in the combined spec list (conda dependencies, PyPI
dependencies, and --with specs) starts with python, conda-exec
adds one automatically:
If
requires-pythonis set, the spec ispython <constraint>(e.g.python >=3.12).Otherwise, a bare
pythonspec is added, letting the solver pick the best available version.
This ensures that script environments always include a Python interpreter.
Field interactions#
The metadata fields combine with command-line options according to these rules:
Channels from the metadata and
--channelflags are merged. If the combined list is empty,conda-forgeis used as the default.Conda dependencies from the metadata and
--withspecs are merged into a single spec list for the solver.PyPI dependencies from
dependenciesare added to the spec list. Theconda-pypichannel is appended automatically when PyPI dependencies are present.If the script has no metadata block and no
--with/--channelflags, conda-exec skips environment creation entirely and runs the script with the current Python interpreter.
File size limit#
conda-exec skips metadata parsing for files larger than 10 MB
(MAX_SCRIPT_SIZE). Scripts exceeding this limit are treated as having
no metadata block.
Note
The 10 MB limit exists to prevent memory exhaustion when conda-exec is
pointed at large generated files or binaries that happen to have a .py
extension. In practice, Python scripts with inline metadata are far smaller
than this threshold.
Cache key computation#
Script environments use a cache key of the form script--{hash}, where
{hash} is the first 16 hex characters of the SHA-256 digest computed
from the dependency metadata. The inputs to the hash are:
Sorted conda dependencies (joined with
|)Sorted PyPI dependencies (joined with
|)Sorted channels (joined with
|)The
requires-pythonvalue (or empty string if not set)
These four parts are joined with || to form the hash input.
The hash is derived from the metadata content, not the file path or script body. This means:
Changing only the script code (without changing dependencies) reuses the same cached environment.
Two different scripts with identical dependency declarations share the same cached environment.
Changing any dependency, channel, or the
requires-pythonvalue produces a different cache key and a new environment.
Examples#
Conda dependencies only#
# /// script
# [tool.conda]
# channels = ["conda-forge"]
# dependencies = ["numpy>=1.24", "matplotlib"]
# ///
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(1000)
plt.hist(data, bins=30)
plt.savefig("histogram.png")
PyPI dependencies only#
Requires conda-pypi to be installed.
# /// script
# dependencies = ["httpx", "rich"]
# ///
import httpx
from rich import print
resp = httpx.get("https://httpbin.org/json")
print(resp.json())
Mixed conda and PyPI dependencies#
# /// script
# requires-python = ">=3.11"
# dependencies = ["rich"]
#
# [tool.conda]
# channels = ["conda-forge"]
# dependencies = ["numpy>=1.24"]
# ///
import numpy as np
from rich import print
print(f"numpy version: {np.__version__}")
Python version constraint only#
# /// script
# requires-python = ">=3.12"
# ///
import tomllib
from pathlib import Path
data = tomllib.loads(Path("pyproject.toml").read_text())
print(data["project"]["name"])
No metadata (runs with current Python)#
# No metadata block, no dependencies needed
print("Hello from conda exec!")
Compatibility with uv#
The standard PEP 723 fields (requires-python and dependencies) are
compatible with uv’s inline script
metadata support. A script using only these fields works with both
conda exec script.py and uv run script.py.
The [tool.conda] extension fields are ignored by uv (and other
PEP 723 consumers), so scripts that include conda-specific
configuration remain valid for other tools. They will simply not
install the conda-specific dependencies.
Tip
You can write scripts that work with both conda-exec and uv. Put
PyPI-only dependencies in the standard dependencies field and
conda-specific packages in [tool.conda].dependencies. Running the
script with uv run installs the PyPI packages; running with
conda exec installs both.