Skip to content

Commit

Permalink
Split uv venv operations into readonly and writable
Browse files Browse the repository at this point in the history
Add a new type so that we can have methods on uv + venv that do not
create or modify the virtual environment.
  • Loading branch information
bluss committed Aug 25, 2024
1 parent 8ed5834 commit 04fc3ef
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 47 deletions.
21 changes: 4 additions & 17 deletions rye/src/cli/list.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::path::PathBuf;

use anyhow::{bail, Error};
use anyhow::Error;
use clap::Parser;

use crate::pyproject::{read_venv_marker, PyProject};
use crate::pyproject::PyProject;
use crate::utils::{get_venv_python_bin, CommandOutput};
use crate::uv::UvBuilder;
use crate::uv::{UvBuilder, UvWithVenvCommon};

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

pub fn execute(cmd: Args) -> Result<(), Error> {
let project = PyProject::load_or_discover(cmd.pyproject.as_deref())?;
let venv = project.venv_path();
if venv.is_dir() {
if read_venv_marker(&venv).is_some() {
} else {
bail!("virtualenv is not managed by rye.");
}
}
let python = get_venv_python_bin(&project.venv_path());
if !python.is_file() {
warn!("Project is not synced, no virtualenv found. Run `rye sync`.");
Expand All @@ -32,12 +25,6 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
let uv = UvBuilder::new()
.with_output(CommandOutput::Normal)
.ensure_exists()?;
uv.venv(
&project.venv_path(),
&python,
&project.venv_python_version()?,
None,
)?
.freeze()?;
uv.venv_read_only(&project.venv_path())?.freeze()?;
Ok(())
}
111 changes: 81 additions & 30 deletions rye/src/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,17 @@ impl Uv {
}
}

/// Ensures a venv is exists or is created at the given path.
/// Returns a UvWithVenv that can be used to run commands in the venv.
pub fn venv_read_only(&self, venv_dir: &Path) -> Result<UvWithReadOnlyVenv, Error> {
if venv_dir.is_dir() {
Ok(UvWithReadOnlyVenv::new(self.clone(), venv_dir))
} else {
Err(anyhow!("Virtualenv not found"))
.with_context(|| format!("path: `{}`", venv_dir.display()))
}
}

/// Get uv binary path
///
/// Warning: Always use self.cmd() when at all possible
Expand Down Expand Up @@ -406,13 +417,82 @@ impl Uv {
}
}

// Represents a venv generated and managed by uv
/// Represents uv with any venv
///
/// Methods on this type are not allowed to create or modify the underlying virtualenv
pub struct UvWithReadOnlyVenv {
uv: Uv,
venv_path: PathBuf,
}

/// Represents a venv generated and managed by uv
pub struct UvWithWritableVenv {
uv: Uv,
venv_path: PathBuf,
py_version: PythonVersion,
}

pub trait UvWithVenvCommon {
fn cmd(&self) -> Command;
fn venv_path(&self) -> &Path;

/// Returns a new command with the uv binary as the command to run.
/// The command will have the correct proxy settings and verbosity level based on CommandOutput.
/// The command will also have the VIRTUAL_ENV environment variable set to the venv path.
fn venv_cmd(&self) -> Command {
let mut cmd = self.cmd();
cmd.env("VIRTUAL_ENV", self.venv_path());
cmd
}

/// Freezes the venv.
fn freeze(&self) -> Result<(), Error> {
let status = self
.venv_cmd()
.arg("pip")
.arg("freeze")
.status()
.with_context(|| format!("unable to freeze venv at {}", self.venv_path().display()))?;

if !status.success() {
return Err(anyhow!(
"Failed to freeze venv at {}. uv exited with status: {}",
self.venv_path().display(),
status
));
}

Ok(())
}
}

impl UvWithVenvCommon for UvWithReadOnlyVenv {
fn cmd(&self) -> Command {
self.uv.cmd()
}
fn venv_path(&self) -> &Path {
&self.venv_path
}
}

impl UvWithVenvCommon for UvWithWritableVenv {
fn cmd(&self) -> Command {
self.uv.cmd()
}
fn venv_path(&self) -> &Path {
&self.venv_path
}
}

impl UvWithReadOnlyVenv {
pub fn new(uv: Uv, venv_dir: &Path) -> Self {
UvWithReadOnlyVenv {
uv,
venv_path: venv_dir.to_path_buf(),
}
}
}

impl UvWithWritableVenv {
pub fn new(uv: Uv, venv_dir: &Path, version: &PythonVersion) -> Self {
UvWithWritableVenv {
Expand All @@ -422,15 +502,6 @@ impl UvWithWritableVenv {
}
}

/// Returns a new command with the uv binary as the command to run.
/// The command will have the correct proxy settings and verbosity level based on CommandOutput.
/// The command will also have the VIRTUAL_ENV environment variable set to the venv path.
fn venv_cmd(&self) -> Command {
let mut cmd = self.uv.cmd();
cmd.env("VIRTUAL_ENV", &self.venv_path);
cmd
}

/// Writes a rye-venv.json for this venv.
pub fn write_marker(&self) -> Result<(), Error> {
write_venv_marker(&self.venv_path, &self.py_version)
Expand Down Expand Up @@ -473,26 +544,6 @@ impl UvWithWritableVenv {
Ok(())
}

/// Freezes the venv.
pub fn freeze(&self) -> Result<(), Error> {
let status = self
.venv_cmd()
.arg("pip")
.arg("freeze")
.status()
.with_context(|| format!("unable to freeze venv at {}", self.venv_path.display()))?;

if !status.success() {
return Err(anyhow!(
"Failed to freeze venv at {}. uv exited with status: {}",
self.venv_path.display(),
status
));
}

Ok(())
}

/// Installs the given requirement in the venv.
///
/// If you provide a list of extras, they will be installed as well.
Expand Down

0 comments on commit 04fc3ef

Please sign in to comment.