Skip to main content

uv_virtualenv/
lib.rs

1use std::io;
2use std::path::{Path, PathBuf};
3
4use thiserror::Error;
5
6use uv_fs::Simplified;
7use uv_python::{Interpreter, PythonEnvironment};
8
9pub use virtualenv::{ClearNonVirtualenv, OnExisting, RemovalReason};
10
11mod virtualenv;
12
13#[derive(Debug, Error)]
14pub enum Error {
15    #[error(transparent)]
16    Io(#[from] io::Error),
17    #[error(
18        "Could not find a suitable Python executable for the virtual environment based on the interpreter: {0}"
19    )]
20    NotFound(String),
21    #[error(transparent)]
22    Python(#[from] uv_python::managed::Error),
23    #[error("A {name} already exists at: {}", path.user_display())]
24    Exists {
25        /// The type of environment (e.g., "virtual environment" or "directory").
26        name: &'static str,
27        /// The path to the existing environment.
28        path: PathBuf,
29    },
30    #[error("uv will not clear a directory that is not a virtual environment")]
31    ClearNonVirtualenv {
32        /// The non-virtual environment directory that would have been cleared.
33        path: PathBuf,
34    },
35    #[error("Virtual environment path is not valid UTF-8: {}", path.user_display())]
36    NonUtf8Path {
37        /// The non-UTF-8 virtual environment path.
38        path: PathBuf,
39    },
40}
41
42impl uv_errors::Hint for Error {
43    fn hints(&self) -> uv_errors::Hints<'_> {
44        match self {
45            Self::Exists { name, .. } => uv_errors::Hints::from(format!(
46                "Use the `--clear` flag or set `UV_VENV_CLEAR=1` to replace the existing {name}",
47            )),
48            Self::ClearNonVirtualenv { .. } => uv_errors::Hints::from(
49                "Use the `--force` flag to remove the existing directory anyway",
50            ),
51            _ => uv_errors::Hints::none(),
52        }
53    }
54}
55
56/// The value to use for the shell prompt when inside a virtual environment.
57#[derive(Debug)]
58pub enum Prompt {
59    /// Use the current directory name as the prompt.
60    CurrentDirectoryName,
61    /// Use the fixed string as the prompt.
62    Static(String),
63    /// Default to no prompt. The prompt is then set by the activator script
64    /// to the virtual environment's directory name.
65    None,
66}
67
68impl Prompt {
69    /// Determine the prompt value to be used from the command line arguments.
70    pub fn from_args(prompt: Option<String>) -> Self {
71        match prompt {
72            Some(prompt) if prompt == "." => Self::CurrentDirectoryName,
73            Some(prompt) => Self::Static(prompt),
74            None => Self::None,
75        }
76    }
77}
78
79/// Create a virtualenv.
80#[expect(clippy::fn_params_excessive_bools)]
81pub fn create_venv(
82    location: &Path,
83    interpreter: Interpreter,
84    prompt: Prompt,
85    system_site_packages: bool,
86    on_existing: OnExisting,
87    relocatable: bool,
88    seed: bool,
89    upgradeable: bool,
90) -> Result<PythonEnvironment, Error> {
91    // Create the virtualenv at the given location.
92    let virtualenv = virtualenv::create(
93        location,
94        &interpreter,
95        prompt,
96        system_site_packages,
97        on_existing,
98        relocatable,
99        seed,
100        upgradeable,
101    )?;
102
103    // Create the corresponding `PythonEnvironment`.
104    let interpreter = interpreter.with_virtualenv(virtualenv);
105    Ok(PythonEnvironment::from_interpreter(interpreter))
106}