diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 654d95519..366d1186f 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -365,212 +365,20 @@ async fn init_project( None }; - // Add a `requires-python` field to the `pyproject.toml` and return the corresponding interpreter. - let (requires_python, python_request) = if let Some(python_request) = python_request { - // (1) A request from the user or `.python-version` file - // This can be arbitrary, i.e., not a version — in which case we may need to resolve the - // interpreter - match python_request { - PythonRequest::Version(VersionRequest::MajorMinor(major, minor, variant)) => { - let requires_python = RequiresPython::greater_than_equal_version(&Version::new([ - u64::from(major), - u64::from(minor), - ])); - - let python_request = if pin_python { - Some(PythonRequest::Version(VersionRequest::MajorMinor( - major, minor, variant, - ))) - } else { - None - }; - - (requires_python, python_request) - } - PythonRequest::Version(VersionRequest::MajorMinorPatch( - major, - minor, - patch, - variant, - )) => { - let requires_python = RequiresPython::greater_than_equal_version(&Version::new([ - u64::from(major), - u64::from(minor), - u64::from(patch), - ])); - - let python_request = if pin_python { - Some(PythonRequest::Version(VersionRequest::MajorMinorPatch( - major, minor, patch, variant, - ))) - } else { - None - }; - - (requires_python, python_request) - } - ref python_request @ PythonRequest::Version(VersionRequest::Range( - ref specifiers, - variant, - )) => { - let requires_python = RequiresPython::from_specifiers(specifiers); - - let python_request = if pin_python { - let interpreter = PythonInstallation::find_or_download( - Some(python_request), - EnvironmentPreference::OnlySystem, - python_preference, - python_downloads, - client_builder, - cache, - Some(&reporter), - install_mirrors.python_install_mirror.as_deref(), - install_mirrors.pypy_install_mirror.as_deref(), - install_mirrors.python_downloads_json_url.as_deref(), - preview, - ) - .await? - .into_interpreter(); - - Some(PythonRequest::Version(VersionRequest::MajorMinor( - interpreter.python_major(), - interpreter.python_minor(), - variant, - ))) - } else { - None - }; - - (requires_python, python_request) - } - python_request => { - let interpreter = PythonInstallation::find_or_download( - Some(&python_request), - EnvironmentPreference::OnlySystem, - python_preference, - python_downloads, - client_builder, - cache, - Some(&reporter), - install_mirrors.python_install_mirror.as_deref(), - install_mirrors.pypy_install_mirror.as_deref(), - install_mirrors.python_downloads_json_url.as_deref(), - preview, - ) - .await? - .into_interpreter(); - - let requires_python = - RequiresPython::greater_than_equal_version(&interpreter.python_minor_version()); - - let python_request = if pin_python { - Some(PythonRequest::Version(VersionRequest::MajorMinor( - interpreter.python_major(), - interpreter.python_minor(), - PythonVariant::Default, - ))) - } else { - None - }; - - (requires_python, python_request) - } - } - } else if let Ok(virtualenv) = PythonEnvironment::from_root(path.join(".venv"), cache) { - // (2) An existing Python environment in the target directory - debug!("Using Python version from existing virtual environment in project"); - let interpreter = virtualenv.into_interpreter(); - - let requires_python = - RequiresPython::greater_than_equal_version(&interpreter.python_minor_version()); - - // Pin to the minor version. - let python_request = if pin_python { - Some(PythonRequest::Version(VersionRequest::MajorMinor( - interpreter.python_major(), - interpreter.python_minor(), - PythonVariant::Default, - ))) - } else { - None - }; - - (requires_python, python_request) - } else if let Some(requires_python) = workspace - .as_ref() - .map(|workspace| find_requires_python(workspace, &DependencyGroupsWithDefaults::none())) - .transpose()? - .flatten() - { - // (3) `requires-python` from the workspace - debug!("Using Python version from project workspace"); - let python_request = PythonRequest::Version(VersionRequest::Range( - requires_python.specifiers().clone(), - PythonVariant::Default, - )); - - // Pin to the minor version. - let python_request = if pin_python { - let interpreter = PythonInstallation::find_or_download( - Some(&python_request), - EnvironmentPreference::OnlySystem, - python_preference, - python_downloads, - client_builder, - cache, - Some(&reporter), - install_mirrors.python_install_mirror.as_deref(), - install_mirrors.pypy_install_mirror.as_deref(), - install_mirrors.python_downloads_json_url.as_deref(), - preview, - ) - .await? - .into_interpreter(); - - Some(PythonRequest::Version(VersionRequest::MajorMinor( - interpreter.python_major(), - interpreter.python_minor(), - PythonVariant::Default, - ))) - } else { - None - }; - - (requires_python, python_request) - } else { - // (4) Default to the system Python - let interpreter = PythonInstallation::find_or_download( - None, - EnvironmentPreference::OnlySystem, - python_preference, - python_downloads, - client_builder, - cache, - Some(&reporter), - install_mirrors.python_install_mirror.as_deref(), - install_mirrors.pypy_install_mirror.as_deref(), - install_mirrors.python_downloads_json_url.as_deref(), - preview, - ) - .await? - .into_interpreter(); - - let requires_python = - RequiresPython::greater_than_equal_version(&interpreter.python_minor_version()); - - // Pin to the minor version. - let python_request = if pin_python { - Some(PythonRequest::Version(VersionRequest::MajorMinor( - interpreter.python_major(), - interpreter.python_minor(), - PythonVariant::Default, - ))) - } else { - None - }; - - (requires_python, python_request) - }; + let (requires_python, python_pin) = determine_requires_python( + path, + pin_python, + install_mirrors, + client_builder, + python_preference, + python_downloads, + cache, + preview, + workspace.as_ref(), + &reporter, + python_request, + ) + .await?; project_kind.init( name, @@ -625,7 +433,7 @@ async fn init_project( )?; } // Write .python-version if it doesn't exist in the workspace or if the version differs - if let Some(python_request) = python_request { + if let Some(python_request) = python_pin { if PythonVersionFile::discover(path, &VersionFileDiscoveryOptions::default()) .await? .filter(|file| { @@ -645,7 +453,7 @@ async fn init_project( } } else { // Write .python-version if it doesn't exist in the project directory. - if let Some(python_request) = python_request { + if let Some(python_request) = python_pin { if PythonVersionFile::discover(path, &VersionFileDiscoveryOptions::default()) .await? .filter(|file| file.version().is_some()) @@ -663,6 +471,234 @@ async fn init_project( Ok(()) } +async fn determine_requires_python( + path: &Path, + pin_python: bool, + install_mirrors: PythonInstallMirrors, + client_builder: &BaseClientBuilder<'_>, + python_preference: PythonPreference, + python_downloads: PythonDownloads, + cache: &Cache, + preview: Preview, + workspace: Option<&Workspace>, + reporter: &PythonDownloadReporter, + python_request: Option, +) -> Result<(RequiresPython, Option)> { + // Add a `requires-python` field to the `pyproject.toml` and return the corresponding interpreter. + if let Some(python_request) = python_request { + // (1) A request from the user or `.python-version` file + // This can be arbitrary, i.e., not a version — in which case we may need to resolve the + // interpreter + let (requires_python, python_pin) = match &python_request { + PythonRequest::Version(VersionRequest::MajorMinor(major, minor, variant)) => { + let requires_python = RequiresPython::greater_than_equal_version(&Version::new([ + u64::from(*major), + u64::from(*minor), + ])); + + let python_pin = if pin_python { + Some(PythonRequest::Version(VersionRequest::MajorMinor( + *major, *minor, *variant, + ))) + } else { + None + }; + + (requires_python, python_pin) + } + PythonRequest::Version(VersionRequest::MajorMinorPatch( + major, + minor, + patch, + variant, + )) => { + let requires_python = RequiresPython::greater_than_equal_version(&Version::new([ + u64::from(*major), + u64::from(*minor), + u64::from(*patch), + ])); + + let python_pin = if pin_python { + Some(PythonRequest::Version(VersionRequest::MajorMinorPatch( + *major, *minor, *patch, *variant, + ))) + } else { + None + }; + + (requires_python, python_pin) + } + python_request @ PythonRequest::Version(VersionRequest::Range(specifiers, variant)) => { + let requires_python = RequiresPython::from_specifiers(specifiers); + + let python_pin = if pin_python { + let interpreter = PythonInstallation::find_or_download( + Some(python_request), + EnvironmentPreference::OnlySystem, + python_preference, + python_downloads, + client_builder, + cache, + Some(reporter), + install_mirrors.python_install_mirror.as_deref(), + install_mirrors.pypy_install_mirror.as_deref(), + install_mirrors.python_downloads_json_url.as_deref(), + preview, + ) + .await? + .into_interpreter(); + + Some(PythonRequest::Version(VersionRequest::MajorMinor( + interpreter.python_major(), + interpreter.python_minor(), + *variant, + ))) + } else { + None + }; + + (requires_python, python_pin) + } + python_request => { + let interpreter = PythonInstallation::find_or_download( + Some(python_request), + EnvironmentPreference::OnlySystem, + python_preference, + python_downloads, + client_builder, + cache, + Some(reporter), + install_mirrors.python_install_mirror.as_deref(), + install_mirrors.pypy_install_mirror.as_deref(), + install_mirrors.python_downloads_json_url.as_deref(), + preview, + ) + .await? + .into_interpreter(); + + let requires_python = + RequiresPython::greater_than_equal_version(&interpreter.python_minor_version()); + + let python_pin = if pin_python { + Some(PythonRequest::Version(VersionRequest::MajorMinor( + interpreter.python_major(), + interpreter.python_minor(), + PythonVariant::Default, + ))) + } else { + None + }; + + (requires_python, python_pin) + } + }; + + debug!("Using Python version `{requires_python}` from request `{python_request}`"); + + Ok((requires_python, python_pin)) + } else if let Ok(virtualenv) = PythonEnvironment::from_root(path.join(".venv"), cache) { + // (2) An existing Python environment in the target directory + let interpreter = virtualenv.into_interpreter(); + + let requires_python = + RequiresPython::greater_than_equal_version(&interpreter.python_minor_version()); + + // Pin to the minor version. + let python_pin = if pin_python { + Some(PythonRequest::Version(VersionRequest::MajorMinor( + interpreter.python_major(), + interpreter.python_minor(), + PythonVariant::Default, + ))) + } else { + None + }; + + debug!( + "Using Python version `{requires_python}` from existing virtual environment in project" + ); + + Ok((requires_python, python_pin)) + } else if let Some(requires_python) = workspace + .as_ref() + .map(|workspace| find_requires_python(workspace, &DependencyGroupsWithDefaults::none())) + .transpose()? + .flatten() + { + // (3) `requires-python` from the workspace + let python_request = PythonRequest::Version(VersionRequest::Range( + requires_python.specifiers().clone(), + PythonVariant::Default, + )); + + // Pin to the minor version. + let python_pin = if pin_python { + let interpreter = PythonInstallation::find_or_download( + Some(&python_request), + EnvironmentPreference::OnlySystem, + python_preference, + python_downloads, + client_builder, + cache, + Some(reporter), + install_mirrors.python_install_mirror.as_deref(), + install_mirrors.pypy_install_mirror.as_deref(), + install_mirrors.python_downloads_json_url.as_deref(), + preview, + ) + .await? + .into_interpreter(); + + Some(PythonRequest::Version(VersionRequest::MajorMinor( + interpreter.python_major(), + interpreter.python_minor(), + PythonVariant::Default, + ))) + } else { + None + }; + + debug!("Using Python version `{requires_python}` from project workspace"); + + Ok((requires_python, python_pin)) + } else { + // (4) Default to the system Python + let interpreter = PythonInstallation::find_or_download( + None, + EnvironmentPreference::OnlySystem, + python_preference, + python_downloads, + client_builder, + cache, + Some(reporter), + install_mirrors.python_install_mirror.as_deref(), + install_mirrors.pypy_install_mirror.as_deref(), + install_mirrors.python_downloads_json_url.as_deref(), + preview, + ) + .await? + .into_interpreter(); + + let requires_python = + RequiresPython::greater_than_equal_version(&interpreter.python_minor_version()); + + // Pin to the minor version. + let python_pin = if pin_python { + Some(PythonRequest::Version(VersionRequest::MajorMinor( + interpreter.python_major(), + interpreter.python_minor(), + PythonVariant::Default, + ))) + } else { + None + }; + + debug!("Using Python version `{requires_python}` from default interpreter"); + + Ok((requires_python, python_pin)) + } +} + /// The kind of entity to initialize (either a PEP 723 script or a Python project). #[derive(Debug, Copy, Clone)] pub(crate) enum InitKind {