Run scripts with dependencies#

conda-exec can run Python scripts that declare their dependencies inline using the PEP 723 metadata format.

Add a PEP 723 # /// script metadata block to the Python file itself, usually near the top before imports or executable code. The lines inside the block are TOML, with the leading # comment prefix stripped before parsing.

The examples below show complete metadata blocks you can copy into a script.

Conda-only dependencies#

Use TOML syntax and the [tool.conda] table to declare conda packages:

# /// script
# [tool.conda]
# channels = ["conda-forge", "bioconda"]
# dependencies = ["samtools>=1.19", "htslib"]
# ///
conda exec script.py

PyPI dependencies#

Use the standard PEP 723 dependencies field for PyPI packages:

# /// script
# dependencies = ["requests>=2.31", "rich"]
# ///
conda exec script.py

Warning

PyPI dependencies require conda-pypi to be installed. Without it, conda-exec cannot resolve PyPI packages and will exit with an error.

conda-exec adds the conda-pypi channel automatically.

Install conda-pypi if you haven’t already:

conda install -n base -c conda-forge conda-pypi

Mixed conda and PyPI dependencies#

Combine both in a single script:

# /// script
# requires-python = ">=3.12"
# dependencies = ["requests"]
#
# [tool.conda]
# channels = ["conda-forge", "bioconda"]
# dependencies = ["samtools>=1.19"]
# ///

Both conda and PyPI requirements are included in one environment solve. conda-pypi exposes PyPI packages through a conda channel so the solver can handle the combined dependency set.

Pin the Python version#

Use requires-python to constrain which Python version is installed:

# /// script
# requires-python = ">=3.12"
# dependencies = ["click"]
# ///

This adds a python >=3.12 spec to the environment solve.

Add extra dependencies from the CLI#

Add dependencies and channels for one run from the command line:

conda exec --with numpy -c defaults script.py

CLI extras (--with and -c) are merged with the script’s declared dependency input. They create a separate cache entry and bypass discovered lock data for that run.

Scripts without metadata#

Note

Scripts without a # /// script block run directly with the current Python interpreter. No environment is created, and no packages are installed.

Scripts without metadata are passed through as-is:

conda exec hello.py

Make scripts directly executable#

Add a shebang line to run scripts without typing conda exec or ce:

#!/usr/bin/env ce
# /// script
# [tool.conda]
# channels = ["conda-forge"]
# dependencies = ["numpy"]
# ///

import numpy as np
print(np.random.default_rng().random(5))
chmod +x demo.py
./demo.py

The ce standalone command is the recommended shebang target because it works on all platforms. Using conda exec in a shebang does not work because the kernel cannot split multi-word interpreter arguments portably.

Note

On macOS and recent Linux kernels, #!/usr/bin/env -S conda exec works via the -S (split string) flag, but this is not portable to all systems. Use #!/usr/bin/env ce for maximum compatibility.

Force re-creation#

If the cached script environment is stale:

conda exec --refresh script.py

Reproduce exact environments#

Use --lock when the resolved package versions should be recorded:

conda exec --lock script.py

With the default rattler-lock-v6 exporter, this writes script.py.conda-exec.lock. Future conda exec script.py runs use the lock data instead of solving from metadata when possible.

For single-file distribution, embed generated lock data in the script:

conda exec --lock --embed script.py

See Lock script environments for sidecar, embedded, and multi-platform lock workflows.