Skip to content

Commit 04fc3ef

Browse files
committed
Split uv venv operations into readonly and writable
Add a new type so that we can have methods on uv + venv that do not create or modify the virtual environment.
1 parent 8ed5834 commit 04fc3ef

File tree

2 files changed

+85
-47
lines changed

2 files changed

+85
-47
lines changed

rye/src/cli/list.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::path::PathBuf;
22

3-
use anyhow::{bail, Error};
3+
use anyhow::Error;
44
use clap::Parser;
55

6-
use crate::pyproject::{read_venv_marker, PyProject};
6+
use crate::pyproject::PyProject;
77
use crate::utils::{get_venv_python_bin, CommandOutput};
8-
use crate::uv::UvBuilder;
8+
use crate::uv::{UvBuilder, UvWithVenvCommon};
99

1010
/// Prints the currently installed packages.
1111
#[derive(Parser, Debug)]
@@ -17,13 +17,6 @@ pub struct Args {
1717

1818
pub fn execute(cmd: Args) -> Result<(), Error> {
1919
let project = PyProject::load_or_discover(cmd.pyproject.as_deref())?;
20-
let venv = project.venv_path();
21-
if venv.is_dir() {
22-
if read_venv_marker(&venv).is_some() {
23-
} else {
24-
bail!("virtualenv is not managed by rye.");
25-
}
26-
}
2720
let python = get_venv_python_bin(&project.venv_path());
2821
if !python.is_file() {
2922
warn!("Project is not synced, no virtualenv found. Run `rye sync`.");
@@ -32,12 +25,6 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
3225
let uv = UvBuilder::new()
3326
.with_output(CommandOutput::Normal)
3427
.ensure_exists()?;
35-
uv.venv(
36-
&project.venv_path(),
37-
&python,
38-
&project.venv_python_version()?,
39-
None,
40-
)?
41-
.freeze()?;
28+
uv.venv_read_only(&project.venv_path())?.freeze()?;
4229
Ok(())
4330
}

rye/src/uv.rs

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,17 @@ impl Uv {
312312
}
313313
}
314314

315+
/// Ensures a venv is exists or is created at the given path.
316+
/// Returns a UvWithVenv that can be used to run commands in the venv.
317+
pub fn venv_read_only(&self, venv_dir: &Path) -> Result<UvWithReadOnlyVenv, Error> {
318+
if venv_dir.is_dir() {
319+
Ok(UvWithReadOnlyVenv::new(self.clone(), venv_dir))
320+
} else {
321+
Err(anyhow!("Virtualenv not found"))
322+
.with_context(|| format!("path: `{}`", venv_dir.display()))
323+
}
324+
}
325+
315326
/// Get uv binary path
316327
///
317328
/// Warning: Always use self.cmd() when at all possible
@@ -406,13 +417,82 @@ impl Uv {
406417
}
407418
}
408419

409-
// Represents a venv generated and managed by uv
420+
/// Represents uv with any venv
421+
///
422+
/// Methods on this type are not allowed to create or modify the underlying virtualenv
423+
pub struct UvWithReadOnlyVenv {
424+
uv: Uv,
425+
venv_path: PathBuf,
426+
}
427+
428+
/// Represents a venv generated and managed by uv
410429
pub struct UvWithWritableVenv {
411430
uv: Uv,
412431
venv_path: PathBuf,
413432
py_version: PythonVersion,
414433
}
415434

435+
pub trait UvWithVenvCommon {
436+
fn cmd(&self) -> Command;
437+
fn venv_path(&self) -> &Path;
438+
439+
/// Returns a new command with the uv binary as the command to run.
440+
/// The command will have the correct proxy settings and verbosity level based on CommandOutput.
441+
/// The command will also have the VIRTUAL_ENV environment variable set to the venv path.
442+
fn venv_cmd(&self) -> Command {
443+
let mut cmd = self.cmd();
444+
cmd.env("VIRTUAL_ENV", self.venv_path());
445+
cmd
446+
}
447+
448+
/// Freezes the venv.
449+
fn freeze(&self) -> Result<(), Error> {
450+
let status = self
451+
.venv_cmd()
452+
.arg("pip")
453+
.arg("freeze")
454+
.status()
455+
.with_context(|| format!("unable to freeze venv at {}", self.venv_path().display()))?;
456+
457+
if !status.success() {
458+
return Err(anyhow!(
459+
"Failed to freeze venv at {}. uv exited with status: {}",
460+
self.venv_path().display(),
461+
status
462+
));
463+
}
464+
465+
Ok(())
466+
}
467+
}
468+
469+
impl UvWithVenvCommon for UvWithReadOnlyVenv {
470+
fn cmd(&self) -> Command {
471+
self.uv.cmd()
472+
}
473+
fn venv_path(&self) -> &Path {
474+
&self.venv_path
475+
}
476+
}
477+
478+
impl UvWithVenvCommon for UvWithWritableVenv {
479+
fn cmd(&self) -> Command {
480+
self.uv.cmd()
481+
}
482+
fn venv_path(&self) -> &Path {
483+
&self.venv_path
484+
}
485+
}
486+
487+
impl UvWithReadOnlyVenv {
488+
pub fn new(uv: Uv, venv_dir: &Path) -> Self {
489+
UvWithReadOnlyVenv {
490+
uv,
491+
venv_path: venv_dir.to_path_buf(),
492+
}
493+
}
494+
}
495+
416496
impl UvWithWritableVenv {
417497
pub fn new(uv: Uv, venv_dir: &Path, version: &PythonVersion) -> Self {
418498
UvWithWritableVenv {
@@ -422,15 +502,6 @@ impl UvWithWritableVenv {
422502
}
423503
}
424504

425-
/// Returns a new command with the uv binary as the command to run.
426-
/// The command will have the correct proxy settings and verbosity level based on CommandOutput.
427-
/// The command will also have the VIRTUAL_ENV environment variable set to the venv path.
428-
fn venv_cmd(&self) -> Command {
429-
let mut cmd = self.uv.cmd();
430-
cmd.env("VIRTUAL_ENV", &self.venv_path);
431-
cmd
432-
}
433-
434505
/// Writes a rye-venv.json for this venv.
435506
pub fn write_marker(&self) -> Result<(), Error> {
436507
write_venv_marker(&self.venv_path, &self.py_version)
@@ -473,26 +544,6 @@ impl UvWithWritableVenv {
473544
Ok(())
474545
}
475546

476-
/// Freezes the venv.
477-
pub fn freeze(&self) -> Result<(), Error> {
478-
let status = self
479-
.venv_cmd()
480-
.arg("pip")
481-
.arg("freeze")
482-
.status()
483-
.with_context(|| format!("unable to freeze venv at {}", self.venv_path.display()))?;
484-
485-
if !status.success() {
486-
return Err(anyhow!(
487-
"Failed to freeze venv at {}. uv exited with status: {}",
488-
self.venv_path.display(),
489-
status
490-
));
491-
}
492-
493-
Ok(())
494-
}
495-
496547
/// Installs the given requirement in the venv.
497548
///
498549
/// If you provide a list of extras, they will be installed as well.

0 commit comments

Comments
 (0)