mirror of
				https://github.com/astral-sh/uv.git
				synced 2025-10-31 12:06:13 +00:00 
			
		
		
		
	Create virtualenv if it doesn't exist in project API (#3499)
## Summary This doesn't yet respect `--python` or the `requires-python` in the project itself. Closes #3449.
This commit is contained in:
		
							parent
							
								
									5f8c3b7e45
								
							
						
					
					
						commit
						835ebe60c6
					
				
					 9 changed files with 83 additions and 21 deletions
				
			
		|  | @ -18,12 +18,24 @@ pub struct PythonEnvironment { | |||
| } | ||||
| 
 | ||||
| impl PythonEnvironment { | ||||
|     /// Create a [`PythonEnvironment`] for an existing virtual environment.
 | ||||
|     /// Create a [`PythonEnvironment`] for an existing virtual environment, detected from the
 | ||||
|     /// environment variables and filesystem.
 | ||||
|     pub fn from_virtualenv(cache: &Cache) -> Result<Self, Error> { | ||||
|         let Some(venv) = detect_virtualenv()? else { | ||||
|             return Err(Error::VenvNotFound); | ||||
|         }; | ||||
|         let venv = fs_err::canonicalize(venv)?; | ||||
|         Self::from_root(&venv, cache) | ||||
|     } | ||||
| 
 | ||||
|     /// Create a [`PythonEnvironment`] from the virtual environment at the given root.
 | ||||
|     pub fn from_root(root: &Path, cache: &Cache) -> Result<Self, Error> { | ||||
|         let venv = match fs_err::canonicalize(root) { | ||||
|             Ok(venv) => venv, | ||||
|             Err(err) if err.kind() == std::io::ErrorKind::NotFound => { | ||||
|                 return Err(Error::VenvDoesNotExist(root.to_path_buf())); | ||||
|             } | ||||
|             Err(err) => return Err(err.into()), | ||||
|         }; | ||||
|         let executable = virtualenv_python_executable(&venv); | ||||
|         let interpreter = Interpreter::query(&executable, cache)?; | ||||
| 
 | ||||
|  |  | |||
|  | @ -736,7 +736,7 @@ mod windows { | |||
|                     )); | ||||
|             assert_snapshot!( | ||||
|                 format_err(result), | ||||
|                 @"Failed to locate Python interpreter at `C:\\does\\not\\exists\\python3.12`" | ||||
|                 @"Failed to locate Python interpreter at: `C:\\does\\not\\exists\\python3.12`" | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | @ -793,6 +793,6 @@ mod tests { | |||
|                 "/does/not/exists/python3.12".to_string(), | ||||
|             )); | ||||
|         assert_snapshot!( | ||||
|             format_err(result), @"Failed to locate Python interpreter at `/does/not/exists/python3.12`"); | ||||
|             format_err(result), @"Failed to locate Python interpreter at: `/does/not/exists/python3.12`"); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ use std::process::ExitStatus; | |||
| 
 | ||||
| use thiserror::Error; | ||||
| 
 | ||||
| use uv_fs::Simplified; | ||||
| 
 | ||||
| pub use crate::environment::PythonEnvironment; | ||||
| pub use crate::find_python::{find_best_python, find_default_python, find_requested_python}; | ||||
| pub use crate::interpreter::Interpreter; | ||||
|  | @ -35,13 +37,15 @@ mod virtualenv; | |||
| 
 | ||||
| #[derive(Debug, Error)] | ||||
| pub enum Error { | ||||
|     #[error("Expected `{0}` to be a virtualenv, but `pyvenv.cfg` is missing")] | ||||
|     #[error("Expected `{}` to be a virtualenv, but `pyvenv.cfg` is missing", _0.user_display())] | ||||
|     MissingPyVenvCfg(PathBuf), | ||||
|     #[error("No versions of Python could be found. Is Python installed?")] | ||||
|     PythonNotFound, | ||||
|     #[error("Failed to locate a virtualenv or Conda environment (checked: `VIRTUAL_ENV`, `CONDA_PREFIX`, and `.venv`). Run `uv venv` to create a virtualenv.")] | ||||
|     VenvNotFound, | ||||
|     #[error("Failed to locate Python interpreter at `{0}`")] | ||||
|     #[error("Virtualenv does not exist at: `{}`", _0.user_display())] | ||||
|     VenvDoesNotExist(PathBuf), | ||||
|     #[error("Failed to locate Python interpreter at: `{0}`")] | ||||
|     RequestedPythonNotFound(String), | ||||
|     #[error(transparent)] | ||||
|     Io(#[from] io::Error), | ||||
|  |  | |||
|  | @ -120,7 +120,7 @@ mod tests { | |||
|                 )); | ||||
|         assert_snapshot!( | ||||
|             format_err(result), | ||||
|             @"Failed to locate Python interpreter at `C:\\does\\not\\exists\\python3.12`" | ||||
|             @"Failed to locate Python interpreter at: `C:\\does\\not\\exists\\python3.12`" | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ use uv_cache::Cache; | |||
| use uv_client::{BaseClientBuilder, RegistryClientBuilder}; | ||||
| use uv_configuration::{ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy}; | ||||
| use uv_dispatch::BuildDispatch; | ||||
| use uv_interpreter::PythonEnvironment; | ||||
| use uv_requirements::{ExtrasSpecification, RequirementsSpecification}; | ||||
| use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder}; | ||||
| use uv_types::{BuildIsolation, HashStrategy, InFlight}; | ||||
|  | @ -29,9 +28,6 @@ pub(crate) async fn lock( | |||
|         warn_user!("`uv lock` is experimental and may change without warning."); | ||||
|     } | ||||
| 
 | ||||
|     // TODO(charlie): If the environment doesn't exist, create it.
 | ||||
|     let venv = PythonEnvironment::from_virtualenv(cache)?; | ||||
| 
 | ||||
|     // Find the project requirements.
 | ||||
|     let Some(project) = Project::find(std::env::current_dir()?)? else { | ||||
|         return Err(anyhow::anyhow!( | ||||
|  | @ -39,6 +35,9 @@ pub(crate) async fn lock( | |||
|         )); | ||||
|     }; | ||||
| 
 | ||||
|     // Discover or create the virtual environment.
 | ||||
|     let venv = project::init(&project, cache, printer)?; | ||||
| 
 | ||||
|     // TODO(zanieb): Support client configuration
 | ||||
|     let client_builder = BaseClientBuilder::default(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,8 +13,9 @@ use uv_cache::Cache; | |||
| use uv_client::RegistryClient; | ||||
| use uv_configuration::{Constraints, NoBinary, Overrides, Reinstall}; | ||||
| use uv_dispatch::BuildDispatch; | ||||
| use uv_fs::Simplified; | ||||
| use uv_installer::{Downloader, Plan, Planner, SitePackages}; | ||||
| use uv_interpreter::{Interpreter, PythonEnvironment}; | ||||
| use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment}; | ||||
| use uv_requirements::{ | ||||
|     ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSpecification, | ||||
|     SourceTreeResolver, | ||||
|  | @ -25,6 +26,7 @@ use uv_resolver::{ | |||
| }; | ||||
| use uv_types::{EmptyInstalledPackages, HashStrategy, InFlight}; | ||||
| 
 | ||||
| use crate::commands::project::discovery::Project; | ||||
| use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter}; | ||||
| use crate::commands::{elapsed, ChangeEvent, ChangeEventKind}; | ||||
| use crate::printer::Printer; | ||||
|  | @ -45,6 +47,12 @@ pub(crate) enum Error { | |||
|     #[error(transparent)] | ||||
|     Platform(#[from] platform_tags::PlatformError), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     Interpreter(#[from] uv_interpreter::Error), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     Virtualenv(#[from] uv_virtualenv::Error), | ||||
| 
 | ||||
|     #[error(transparent)] | ||||
|     Hash(#[from] uv_types::HashStrategyError), | ||||
| 
 | ||||
|  | @ -61,6 +69,47 @@ pub(crate) enum Error { | |||
|     Anyhow(#[from] anyhow::Error), | ||||
| } | ||||
| 
 | ||||
| /// Initialize a virtual environment for the current project.
 | ||||
| pub(crate) fn init( | ||||
|     project: &Project, | ||||
|     cache: &Cache, | ||||
|     printer: Printer, | ||||
| ) -> Result<PythonEnvironment, Error> { | ||||
|     let venv = project.root().join(".venv"); | ||||
| 
 | ||||
|     // Discover or create the virtual environment.
 | ||||
|     // TODO(charlie): If the environment isn't compatible with `--python`, recreate it.
 | ||||
|     match PythonEnvironment::from_root(&venv, cache) { | ||||
|         Ok(venv) => Ok(venv), | ||||
|         Err(uv_interpreter::Error::VenvDoesNotExist(_)) => { | ||||
|             // TODO(charlie): Respect `--python`; if unset, respect `Requires-Python`.
 | ||||
|             let interpreter = find_default_python(cache)?; | ||||
| 
 | ||||
|             writeln!( | ||||
|                 printer.stderr(), | ||||
|                 "Using Python {} interpreter at: {}", | ||||
|                 interpreter.python_version(), | ||||
|                 interpreter.sys_executable().user_display().cyan() | ||||
|             )?; | ||||
| 
 | ||||
|             writeln!( | ||||
|                 printer.stderr(), | ||||
|                 "Creating virtualenv at: {}", | ||||
|                 venv.user_display().cyan() | ||||
|             )?; | ||||
| 
 | ||||
|             Ok(uv_virtualenv::create_venv( | ||||
|                 &venv, | ||||
|                 interpreter, | ||||
|                 uv_virtualenv::Prompt::None, | ||||
|                 false, | ||||
|                 false, | ||||
|             )?) | ||||
|         } | ||||
|         Err(e) => Err(e.into()), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Resolve a set of requirements, similar to running `pip compile`.
 | ||||
| #[allow(clippy::too_many_arguments)] | ||||
| pub(crate) async fn resolve( | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ pub(crate) async fn run( | |||
|             )); | ||||
|         }; | ||||
| 
 | ||||
|         let venv = PythonEnvironment::from_virtualenv(cache)?; | ||||
|         let venv = project::init(&project, cache, printer)?; | ||||
| 
 | ||||
|         // Install the project requirements.
 | ||||
|         Some(update_environment(venv, &project.requirements(), preview, cache, printer).await?) | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ use uv_client::RegistryClientBuilder; | |||
| use uv_configuration::{ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy}; | ||||
| use uv_dispatch::BuildDispatch; | ||||
| use uv_installer::SitePackages; | ||||
| use uv_interpreter::PythonEnvironment; | ||||
| use uv_resolver::{FlatIndex, InMemoryIndex, Lock}; | ||||
| use uv_types::{BuildIsolation, HashStrategy, InFlight}; | ||||
| use uv_warnings::warn_user; | ||||
|  | @ -27,11 +26,6 @@ pub(crate) async fn sync( | |||
|         warn_user!("`uv sync` is experimental and may change without warning."); | ||||
|     } | ||||
| 
 | ||||
|     // TODO(charlie): If the environment doesn't exist, create it.
 | ||||
|     let venv = PythonEnvironment::from_virtualenv(cache)?; | ||||
|     let markers = venv.interpreter().markers(); | ||||
|     let tags = venv.interpreter().tags()?; | ||||
| 
 | ||||
|     // Find the project requirements.
 | ||||
|     let Some(project) = Project::find(std::env::current_dir()?)? else { | ||||
|         return Err(anyhow::anyhow!( | ||||
|  | @ -39,6 +33,11 @@ pub(crate) async fn sync( | |||
|         )); | ||||
|     }; | ||||
| 
 | ||||
|     // Discover or create the virtual environment.
 | ||||
|     let venv = project::init(&project, cache, printer)?; | ||||
|     let markers = venv.interpreter().markers(); | ||||
|     let tags = venv.interpreter().tags()?; | ||||
| 
 | ||||
|     // Read the lockfile.
 | ||||
|     let resolution = { | ||||
|         let encoded = fs_err::tokio::read_to_string(project.root().join("uv.lock")).await?; | ||||
|  |  | |||
|  | @ -108,8 +108,7 @@ fn missing_venv() -> Result<()> { | |||
|     ----- stdout ----- | ||||
| 
 | ||||
|     ----- stderr ----- | ||||
|     error: failed to canonicalize path `[VENV]/` | ||||
|       Caused by: No such file or directory (os error 2) | ||||
|     error: Virtualenv does not exist at: `[VENV]/` | ||||
|     "###);
 | ||||
| 
 | ||||
|     assert!(predicates::path::missing().eval(&context.venv)); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Charlie Marsh
						Charlie Marsh