diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index a71709254451..84ba940b3fc3 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -776,12 +776,32 @@ pub fn find_python_installations<'a>( .map(FindPythonResult::Ok) }) }), - PythonRequest::Version(version) => Box::new({ - debug!("Searching for {request} in {preference}"); - python_interpreters(Some(version), None, environments, preference, cache) + PythonRequest::Version(version) => { + if let Err(err) = version.check_supported() { + return Box::new(std::iter::once(Err(Error::InvalidVersionRequest(err)))); + }; + Box::new({ + debug!("Searching for {request} in {preference}"); + python_interpreters(Some(version), None, environments, preference, cache) + .filter(|result| match result { + Err(_) => true, + Ok((_source, interpreter)) => version.matches_interpreter(interpreter), + }) + .map(|result| { + result + .map(PythonInstallation::from_tuple) + .map(FindPythonResult::Ok) + }) + }) + } + PythonRequest::Implementation(implementation) => Box::new({ + debug!("Searching for a {request} interpreter in {preference}"); + python_interpreters(None, Some(implementation), environments, preference, cache) .filter(|result| match result { Err(_) => true, - Ok((_source, interpreter)) => version.matches_interpreter(interpreter), + Ok((_source, interpreter)) => interpreter + .implementation_name() + .eq_ignore_ascii_case(implementation.into()), }) .map(|result| { result @@ -789,64 +809,61 @@ pub fn find_python_installations<'a>( .map(FindPythonResult::Ok) }) }), - PythonRequest::Implementation(implementation) => Box::new({ - debug!("Searching for a {request} interpreter in {preference}"); - python_interpreters(None, Some(implementation), environments, preference, cache) + PythonRequest::ImplementationVersion(implementation, version) => { + if let Err(err) = version.check_supported() { + return Box::new(std::iter::once(Err(Error::InvalidVersionRequest(err)))); + }; + Box::new({ + debug!("Searching for {request} in {preference}"); + python_interpreters( + Some(version), + Some(implementation), + environments, + preference, + cache, + ) .filter(|result| match result { Err(_) => true, - Ok((_source, interpreter)) => interpreter - .implementation_name() - .eq_ignore_ascii_case(implementation.into()), + Ok((_source, interpreter)) => { + version.matches_interpreter(interpreter) + && interpreter + .implementation_name() + .eq_ignore_ascii_case(implementation.into()) + } }) .map(|result| { result .map(PythonInstallation::from_tuple) .map(FindPythonResult::Ok) }) - }), - PythonRequest::ImplementationVersion(implementation, version) => Box::new({ - debug!("Searching for {request} in {preference}"); - python_interpreters( - Some(version), - Some(implementation), - environments, - preference, - cache, - ) - .filter(|result| match result { - Err(_) => true, - Ok((_source, interpreter)) => { - version.matches_interpreter(interpreter) - && interpreter - .implementation_name() - .eq_ignore_ascii_case(implementation.into()) - } - }) - .map(|result| { - result - .map(PythonInstallation::from_tuple) - .map(FindPythonResult::Ok) - }) - }), - PythonRequest::Key(request) => Box::new({ - debug!("Searching for {request} in {preference}"); - python_interpreters( - request.version(), - request.implementation(), - environments, - preference, - cache, - ) - .filter(|result| match result { - Err(_) => true, - Ok((_source, interpreter)) => request.satisfied_by_interpreter(interpreter), }) - .map(|result| { - result - .map(PythonInstallation::from_tuple) - .map(FindPythonResult::Ok) + } + PythonRequest::Key(request) => { + if let Some(version) = request.version() { + if let Err(err) = version.check_supported() { + return Box::new(std::iter::once(Err(Error::InvalidVersionRequest(err)))); + }; + }; + Box::new({ + debug!("Searching for {request} in {preference}"); + python_interpreters( + request.version(), + request.implementation(), + environments, + preference, + cache, + ) + .filter(|result| match result { + Err(_) => true, + Ok((_source, interpreter)) => request.satisfied_by_interpreter(interpreter), + }) + .map(|result| { + result + .map(PythonInstallation::from_tuple) + .map(FindPythonResult::Ok) + }) }) - }), + } } } @@ -1446,6 +1463,37 @@ impl VersionRequest { .flatten() } + pub(crate) fn check_supported(&self) -> Result<(), String> { + match self { + Self::Any => (), + Self::Major(major) => { + if *major < 3 { + return Err(format!( + "Python <3 is not supported but {major} was requested." + )); + } + } + Self::MajorMinor(major, minor) => { + if (*major, *minor) < (3, 7) { + return Err(format!( + "Python <3.7 is not supported but {major}.{minor} was requested." + )); + } + } + Self::MajorMinorPatch(major, minor, patch) => { + if (*major, *minor) < (3, 7) { + return Err(format!( + "Python <3.7 is not supported but {major}.{minor}.{patch} was requested." + )); + } + } + // TODO(zanieb): We could do some checking here to see if the range can be satisfied + Self::Range(_) => (), + } + + Ok(()) + } + /// Check if a interpreter matches the requested Python version. pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool { match self { diff --git a/crates/uv/tests/python_find.rs b/crates/uv/tests/python_find.rs index bafdb9fd658b..1021f9f60364 100644 --- a/crates/uv/tests/python_find.rs +++ b/crates/uv/tests/python_find.rs @@ -403,7 +403,17 @@ fn python_find_unsupported_version() { ----- stdout ----- ----- stderr ----- - error: No interpreter found for Python 3.6 in virtual environments or system path + error: Invalid version request: Python <3.7 is not supported but 3.6 was requested. + "###); + + // Request a low version with a patch + uv_snapshot!(context.filters(), context.python_find().arg("3.6.9"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Invalid version request: Python <3.7 is not supported but 3.6.9 was requested. "###); // Request a really low version @@ -413,7 +423,17 @@ fn python_find_unsupported_version() { ----- stdout ----- ----- stderr ----- - error: No interpreter found for Python 2.6 in virtual environments or system path + error: Invalid version request: Python <3.7 is not supported but 2.6 was requested. + "###); + + // Request a really low version with a patch + uv_snapshot!(context.filters(), context.python_find().arg("2.6.8"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Invalid version request: Python <3.7 is not supported but 2.6.8 was requested. "###); // Request a future version @@ -425,4 +445,14 @@ fn python_find_unsupported_version() { ----- stderr ----- error: No interpreter found for Python 4.2 in virtual environments or system path "###); + + // Request a low version with a range + uv_snapshot!(context.filters(), context.python_find().arg("<3.0"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python <3.0 in virtual environments or system path + "###); }