Skip to content

Commit b679106

Browse files
evanroyreesclaude
andcommitted
Add uv directive for Python package management
Add a new `uv` process directive that allows Nextflow processes to declare Python dependencies managed by the uv package manager, following the same pattern as the existing `conda` and `spack` directives. The directive accepts package names, requirements.txt files, pyproject.toml files, or paths to existing virtual environments. Nextflow automatically creates, caches, and activates uv virtual environments for each unique set of dependencies. Includes CLI options (-with-uv / -without-uv), configuration scope (uv.enabled, uv.cacheDir, uv.pythonVersion, etc.), environment variables (NXF_UV_ENABLED, NXF_UV_CACHEDIR), lineage tracking, task hash integration, tests, and documentation. Signed-off-by: Evan Rees <evanroyrees@gmail.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cbc0a2d commit b679106

26 files changed

Lines changed: 1063 additions & 4 deletions

File tree

docs/reference/config.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,6 +1603,29 @@ The following settings are available:
16031603
`spack.parallelBuilds`
16041604
: The maximum number of parallel package builds (default: the number of available CPUs).
16051605

1606+
(config-uv)=
1607+
1608+
## `uv`
1609+
1610+
The `uv` scope controls the creation of Python virtual environments by the [uv](https://docs.astral.sh/uv/) package manager.
1611+
1612+
The following settings are available:
1613+
1614+
`uv.cacheDir`
1615+
: The path where uv virtual environments are stored. It should be accessible from all compute nodes when using a shared file system.
1616+
1617+
`uv.createTimeout`
1618+
: The amount of time to wait for the uv environment to be created before failing (default: `20 min`).
1619+
1620+
`uv.enabled`
1621+
: Execute tasks with uv virtual environments (default: `false`).
1622+
1623+
`uv.installOptions`
1624+
: Extra command line options for the `uv pip install` command. See the [uv documentation](https://docs.astral.sh/uv/) for more information.
1625+
1626+
`uv.pythonVersion`
1627+
: The Python version to use when creating virtual environments (e.g. `3.12`). If not specified, uv will use its default Python resolution.
1628+
16061629
(config-timeline)=
16071630

16081631
## `timeline`

docs/reference/env-vars.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,12 @@ The following environment variables control the configuration of the Nextflow ru
232232
:::
233233
: Enable the use of Spack recipes defined by using the {ref}`process-spack` directive. (default: `false`).
234234

235+
`NXF_UV_CACHEDIR`
236+
: Directory where uv virtual environments are stored. When using a computing cluster it must be a shared folder accessible from all compute nodes.
237+
238+
`NXF_UV_ENABLED`
239+
: Enable the use of uv environments defined by using the {ref}`process-uv` directive. (default: `false`).
240+
235241
`NXF_SYNTAX_PARSER`
236242
: :::{versionadded} 25.02.0-edge
237243
:::

docs/reference/process.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,35 @@ Multiple packages can be specified separating them with a blank space, e.g. `bwa
15741574

15751575
The `spack` directive also accepts a Spack environment file path or the path of an existing Spack environment. See {ref}`spack-page` for more information.
15761576

1577+
(process-uv)=
1578+
1579+
### uv
1580+
1581+
The `uv` directive defines the set of Python packages to be installed using the [uv](https://docs.astral.sh/uv/) package manager for each task. For example:
1582+
1583+
```nextflow
1584+
process hello {
1585+
uv 'numpy pandas matplotlib'
1586+
1587+
script:
1588+
"""
1589+
python my_script.py
1590+
"""
1591+
}
1592+
```
1593+
1594+
Nextflow automatically creates a uv virtual environment for each unique set of packages.
1595+
1596+
Multiple packages can be specified separating them with a blank space, e.g. `numpy pandas>=2.0 scikit-learn`.
1597+
1598+
The `uv` directive also accepts:
1599+
1600+
- A `requirements.txt` file path: `uv '/path/to/requirements.txt'`
1601+
- A `pyproject.toml` file path: `uv '/path/to/pyproject.toml'`
1602+
- The path of an existing virtual environment directory
1603+
1604+
See {ref}`uv-page` for more information.
1605+
15771606
(process-stageinmode)=
15781607

15791608
### stageInMode

docs/uv.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
(uv-page)=
2+
3+
# uv environments
4+
5+
[uv](https://docs.astral.sh/uv/) is an extremely fast Python package and project manager, written in Rust. It can install Python packages, manage virtual environments, and handle Python versions.
6+
7+
Nextflow has built-in support for uv that allows the configuration of workflow dependencies using Python packages, requirements files, or pyproject.toml files.
8+
9+
This allows Nextflow applications to use Python packages managed by uv, taking advantage of its speed and reliability for creating reproducible Python environments.
10+
11+
## Prerequisites
12+
13+
This feature requires the [uv](https://docs.astral.sh/uv/getting-started/installation/) package manager to be installed on your system.
14+
15+
## How it works
16+
17+
Nextflow automatically creates and activates uv virtual environments given the dependencies specified by each process.
18+
19+
Dependencies are specified by using the {ref}`process-uv` directive, providing either the names of the required Python packages, the path of a requirements file, the path of a pyproject.toml file, or the path of an existing virtual environment directory.
20+
21+
You can specify the directory where the uv environments are stored using the `uv.cacheDir` configuration property (see the {ref}`configuration page <config-uv>` for details). When using a computing cluster, make sure to use a shared file system path accessible from all compute nodes.
22+
23+
:::{warning}
24+
The uv environment feature is not supported by executors that use remote object storage as the work directory, e.g. AWS Batch.
25+
:::
26+
27+
### Enabling uv environments
28+
29+
The use of uv packages specified using the {ref}`process-uv` directive needs to be enabled explicitly by setting the option shown below in the pipeline configuration file (i.e. `nextflow.config`):
30+
31+
```groovy
32+
uv.enabled = true
33+
```
34+
35+
Alternatively, it can be specified by setting the variable `NXF_UV_ENABLED=true` in your environment or by using the `-with-uv` command line option.
36+
37+
### Use Python package names
38+
39+
Python package names can be specified using the `uv` directive. Multiple package names can be specified by separating them with a blank space. For example:
40+
41+
```nextflow
42+
process hello {
43+
uv 'numpy pandas matplotlib'
44+
45+
script:
46+
'''
47+
python my_script.py
48+
'''
49+
}
50+
```
51+
52+
Using the above definition, a uv virtual environment that includes NumPy, Pandas, and Matplotlib is created and activated when the process is executed.
53+
54+
The usual pip package syntax and naming conventions can be used. The version of a package can be specified using pip version specifiers like so: `numpy>=1.24 pandas==2.0.0`.
55+
56+
### Use requirements files
57+
58+
uv environments can also be defined using a requirements file. For example, given a `requirements.txt` file:
59+
60+
```
61+
numpy>=1.24.0
62+
pandas>=2.0
63+
scikit-learn
64+
matplotlib
65+
```
66+
67+
The environment for a process can be specified like so:
68+
69+
```nextflow
70+
process hello {
71+
uv '/path/to/requirements.txt'
72+
73+
script:
74+
'''
75+
python my_script.py
76+
'''
77+
}
78+
```
79+
80+
### Use pyproject.toml files
81+
82+
uv can also install dependencies from a `pyproject.toml` file:
83+
84+
```nextflow
85+
process hello {
86+
uv '/path/to/pyproject.toml'
87+
88+
script:
89+
'''
90+
python my_script.py
91+
'''
92+
}
93+
```
94+
95+
### Use existing environments
96+
97+
If you already have a uv virtual environment, you can use it directly by specifying the path:
98+
99+
```nextflow
100+
process hello {
101+
uv '/path/to/existing/venv'
102+
103+
script:
104+
'''
105+
python my_script.py
106+
'''
107+
}
108+
```
109+
110+
### Environment caching
111+
112+
Nextflow caches uv environments so that they are created only once for each unique set of packages. The cache directory can be configured using the `uv.cacheDir` setting or the `NXF_UV_CACHEDIR` environment variable.
113+
114+
### Python version
115+
116+
You can specify the Python version to use when creating virtual environments:
117+
118+
```groovy
119+
uv.pythonVersion = '3.12'
120+
```
121+
122+
### Advanced settings
123+
124+
The following settings are available in the `uv` scope of the Nextflow configuration:
125+
126+
- `uv.enabled`: Enable the use of uv environments (default: `false`)
127+
- `uv.cacheDir`: The path where uv environments are stored
128+
- `uv.createTimeout`: Timeout for environment creation (default: `20 min`)
129+
- `uv.installOptions`: Extra command line options for `uv pip install`
130+
- `uv.pythonVersion`: Python version for virtual environment creation

modules/nextflow/src/main/groovy/nextflow/Session.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import nextflow.script.ScriptRunner
6969
import nextflow.script.WorkflowMetadata
7070
import nextflow.script.dsl.ProcessConfigBuilder
7171
import nextflow.spack.SpackConfig
72+
import nextflow.uv.UvConfig
7273
import nextflow.trace.LogObserver
7374
import nextflow.trace.TraceObserver
7475
import nextflow.trace.TraceObserverFactory
@@ -1160,6 +1161,12 @@ class Session implements ISession {
11601161
return new SpackConfig(opts, getSystemEnv())
11611162
}
11621163

1164+
@Memoized
1165+
UvConfig getUvConfig() {
1166+
final opts = config.uv as Map ?: Collections.emptyMap()
1167+
return new UvConfig(opts, getSystemEnv())
1168+
}
1169+
11631170
/**
11641171
* Get the container engine configuration for the specified engine. If no engine is specified
11651172
* if returns the one enabled in the configuration file. If no configuration is found

modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ class CmdRun extends CmdBase implements HubOptions {
257257
@Parameter(names=['-without-spack'], description = 'Disable the use of Spack environments')
258258
Boolean withoutSpack
259259

260+
@Parameter(names=['-with-uv'], description = 'Use the specified uv environment packages or requirements file')
261+
String withUv
262+
263+
@Parameter(names=['-without-uv'], description = 'Disable the use of uv environments')
264+
Boolean withoutUv
265+
260266
@Parameter(names=['-offline'], description = 'Do not check for remote project updates')
261267
boolean offline = System.getenv('NXF_OFFLINE')=='true'
262268

@@ -321,6 +327,9 @@ class CmdRun extends CmdBase implements HubOptions {
321327
if( withSpack && withoutSpack )
322328
throw new AbortOperationException("Command line options `-with-spack` and `-without-spack` cannot be specified at the same time")
323329

330+
if( withUv && withoutUv )
331+
throw new AbortOperationException("Command line options `-with-uv` and `-without-uv` cannot be specified at the same time")
332+
324333
if( offline && latest )
325334
throw new AbortOperationException("Command line options `-latest` and `-offline` cannot be specified at the same time")
326335

modules/nextflow/src/main/groovy/nextflow/cli/Launcher.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ class Launcher {
302302
normalized << '-'
303303
}
304304

305+
else if( current == '-with-uv' && (i==args.size() || args[i].startsWith('-'))) {
306+
normalized << '-'
307+
}
308+
305309
else if( current == '-with-weblog' && (i==args.size() || args[i].startsWith('-'))) {
306310
normalized << '-'
307311
}

modules/nextflow/src/main/groovy/nextflow/config/ConfigBuilder.groovy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,19 @@ class ConfigBuilder {
625625
config.spack.enabled = true
626626
}
627627

628+
if( cmdRun.withoutUv && config.uv instanceof Map ) {
629+
// disable uv execution
630+
log.debug "Disabling execution with uv as requested by command-line option `-without-uv`"
631+
config.uv.enabled = false
632+
}
633+
634+
// -- apply the uv environment
635+
if( cmdRun.withUv ) {
636+
if( cmdRun.withUv != '-' )
637+
config.process.uv = cmdRun.withUv
638+
config.uv.enabled = true
639+
}
640+
628641
// -- sets the resume option
629642
if( cmdRun.resume )
630643
config.resume = cmdRun.resume

modules/nextflow/src/main/groovy/nextflow/executor/BashWrapperBuilder.groovy

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ class BashWrapperBuilder {
354354
binding.before_script = getBeforeScriptSnippet()
355355
binding.conda_activate = getCondaActivateSnippet()
356356
binding.spack_activate = getSpackActivateSnippet()
357+
binding.uv_activate = getUvActivateSnippet()
357358

358359
/*
359360
* add the task environment
@@ -573,6 +574,15 @@ class BashWrapperBuilder {
573574
return result
574575
}
575576

577+
private String getUvActivateSnippet() {
578+
if( !uvEnv )
579+
return null
580+
return """\
581+
# uv environment
582+
source ${Escape.path(uvEnv)}/bin/activate
583+
""".stripIndent()
584+
}
585+
576586
protected String getTraceCommand(String interpreter) {
577587
String result = "${interpreter} ${fileStr(scriptFile)}"
578588
if( input != null )

modules/nextflow/src/main/groovy/nextflow/processor/TaskArrayCollector.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class TaskArrayCollector {
5757
'containerOptions',
5858
// only needed when using Wave
5959
'conda',
60+
'uv',
6061
]
6162

6263
private TaskProcessor processor

0 commit comments

Comments
 (0)