mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Merge c07e98de74
into f609e1ddaf
This commit is contained in:
commit
51a53fed84
5 changed files with 147 additions and 23 deletions
|
@ -52,6 +52,8 @@ pub enum PythonRequest {
|
||||||
/// Any Python installation
|
/// Any Python installation
|
||||||
Any,
|
Any,
|
||||||
/// A Python version without an implementation name e.g. `3.10` or `>=3.12,<3.13`
|
/// A Python version without an implementation name e.g. `3.10` or `>=3.12,<3.13`
|
||||||
|
Latest,
|
||||||
|
// Newest resolvable, stable Python version.
|
||||||
Version(VersionRequest),
|
Version(VersionRequest),
|
||||||
/// A path to a directory containing a Python installation, e.g. `.venv`
|
/// A path to a directory containing a Python installation, e.g. `.venv`
|
||||||
Directory(PathBuf),
|
Directory(PathBuf),
|
||||||
|
@ -181,6 +183,7 @@ pub enum VersionRequest {
|
||||||
Default,
|
Default,
|
||||||
/// Allow any Python version.
|
/// Allow any Python version.
|
||||||
Any,
|
Any,
|
||||||
|
Latest,
|
||||||
Major(u8, PythonVariant),
|
Major(u8, PythonVariant),
|
||||||
MajorMinor(u8, u8, PythonVariant),
|
MajorMinor(u8, u8, PythonVariant),
|
||||||
MajorMinorPatch(u8, u8, u8, PythonVariant),
|
MajorMinorPatch(u8, u8, u8, PythonVariant),
|
||||||
|
@ -624,6 +627,7 @@ fn find_all_minor(
|
||||||
match version_request {
|
match version_request {
|
||||||
&VersionRequest::Any
|
&VersionRequest::Any
|
||||||
| VersionRequest::Default
|
| VersionRequest::Default
|
||||||
|
| VersionRequest::Latest
|
||||||
| VersionRequest::Major(_, _)
|
| VersionRequest::Major(_, _)
|
||||||
| VersionRequest::Range(_, _) => {
|
| VersionRequest::Range(_, _) => {
|
||||||
let regex = if let Some(implementation) = implementation {
|
let regex = if let Some(implementation) = implementation {
|
||||||
|
@ -1135,6 +1139,19 @@ pub fn find_python_installations<'a>(
|
||||||
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
|
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
PythonRequest::Latest => Box::new({
|
||||||
|
debug!("Searching for latest Python interpreter in {sources}");
|
||||||
|
python_interpreters(&VersionRequest::Any, None, environments, preference, cache)
|
||||||
|
.filter_ok(|(_source, interpreter)| interpreter.python_version().pre().is_none())
|
||||||
|
.max_by(|a, b| match (a.as_ref(), b.as_ref()) {
|
||||||
|
(Ok((_, interpreter_a)), Ok((_, interpreter_b))) => {
|
||||||
|
interpreter_a.key().cmp(&interpreter_b.key())
|
||||||
|
}
|
||||||
|
_ => std::cmp::Ordering::Equal,
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1673,9 +1690,7 @@ impl PythonRequest {
|
||||||
// parsing errors are raised to the caller.
|
// parsing errors are raised to the caller.
|
||||||
if let Some(after_at) = rest.strip_prefix('@') {
|
if let Some(after_at) = rest.strip_prefix('@') {
|
||||||
if after_at == "latest" {
|
if after_at == "latest" {
|
||||||
// Handle `@latest` as a special case. It's still an error for now, but we plan to
|
return Ok(Some(VersionRequest::Latest));
|
||||||
// support it. TODO(zanieb): Add `PythonRequest::Latest`
|
|
||||||
return Err(Error::LatestVersionRequest);
|
|
||||||
}
|
}
|
||||||
return after_at.parse().map(Some);
|
return after_at.parse().map(Some);
|
||||||
}
|
}
|
||||||
|
@ -1710,7 +1725,7 @@ impl PythonRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
PythonRequest::Default | PythonRequest::Any => true,
|
PythonRequest::Default | PythonRequest::Any | PythonRequest::Latest => true,
|
||||||
PythonRequest::Version(version_request) => {
|
PythonRequest::Version(version_request) => {
|
||||||
version_request.matches_interpreter(interpreter)
|
version_request.matches_interpreter(interpreter)
|
||||||
}
|
}
|
||||||
|
@ -1799,7 +1814,7 @@ impl PythonRequest {
|
||||||
/// Whether this request opts-in to a pre-release Python version.
|
/// Whether this request opts-in to a pre-release Python version.
|
||||||
pub(crate) fn allows_prereleases(&self) -> bool {
|
pub(crate) fn allows_prereleases(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => false,
|
Self::Default | Self::Latest => false,
|
||||||
Self::Any => true,
|
Self::Any => true,
|
||||||
Self::Version(version) => version.allows_prereleases(),
|
Self::Version(version) => version.allows_prereleases(),
|
||||||
Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
|
Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
|
||||||
|
@ -1812,7 +1827,7 @@ impl PythonRequest {
|
||||||
/// Whether this request opts-in to an alternative Python implementation, e.g., PyPy.
|
/// Whether this request opts-in to an alternative Python implementation, e.g., PyPy.
|
||||||
pub(crate) fn allows_alternative_implementations(&self) -> bool {
|
pub(crate) fn allows_alternative_implementations(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => false,
|
Self::Default | Self::Latest => false,
|
||||||
Self::Any => true,
|
Self::Any => true,
|
||||||
Self::Version(_) => false,
|
Self::Version(_) => false,
|
||||||
Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
|
Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
|
||||||
|
@ -1833,6 +1848,7 @@ impl PythonRequest {
|
||||||
match self {
|
match self {
|
||||||
Self::Any => "any".to_string(),
|
Self::Any => "any".to_string(),
|
||||||
Self::Default => "default".to_string(),
|
Self::Default => "default".to_string(),
|
||||||
|
Self::Latest => "latest".to_string(),
|
||||||
Self::Version(version) => version.to_string(),
|
Self::Version(version) => version.to_string(),
|
||||||
Self::Directory(path) => path.display().to_string(),
|
Self::Directory(path) => path.display().to_string(),
|
||||||
Self::File(path) => path.display().to_string(),
|
Self::File(path) => path.display().to_string(),
|
||||||
|
@ -2238,7 +2254,7 @@ impl VersionRequest {
|
||||||
/// Return the major version segment of the request, if any.
|
/// Return the major version segment of the request, if any.
|
||||||
pub(crate) fn major(&self) -> Option<u8> {
|
pub(crate) fn major(&self) -> Option<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default | Self::Range(_, _) => None,
|
Self::Any | Self::Default | Self::Latest | Self::Range(_, _) => None,
|
||||||
Self::Major(major, _) => Some(*major),
|
Self::Major(major, _) => Some(*major),
|
||||||
Self::MajorMinor(major, _, _) => Some(*major),
|
Self::MajorMinor(major, _, _) => Some(*major),
|
||||||
Self::MajorMinorPatch(major, _, _, _) => Some(*major),
|
Self::MajorMinorPatch(major, _, _, _) => Some(*major),
|
||||||
|
@ -2249,7 +2265,7 @@ impl VersionRequest {
|
||||||
/// Return the minor version segment of the request, if any.
|
/// Return the minor version segment of the request, if any.
|
||||||
pub(crate) fn minor(&self) -> Option<u8> {
|
pub(crate) fn minor(&self) -> Option<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default | Self::Range(_, _) => None,
|
Self::Any | Self::Default | Self::Latest | Self::Range(_, _) => None,
|
||||||
Self::Major(_, _) => None,
|
Self::Major(_, _) => None,
|
||||||
Self::MajorMinor(_, minor, _) => Some(*minor),
|
Self::MajorMinor(_, minor, _) => Some(*minor),
|
||||||
Self::MajorMinorPatch(_, minor, _, _) => Some(*minor),
|
Self::MajorMinorPatch(_, minor, _, _) => Some(*minor),
|
||||||
|
@ -2260,7 +2276,7 @@ impl VersionRequest {
|
||||||
/// Return the patch version segment of the request, if any.
|
/// Return the patch version segment of the request, if any.
|
||||||
pub(crate) fn patch(&self) -> Option<u8> {
|
pub(crate) fn patch(&self) -> Option<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default | Self::Range(_, _) => None,
|
Self::Any | Self::Default | Self::Latest | Self::Range(_, _) => None,
|
||||||
Self::Major(_, _) => None,
|
Self::Major(_, _) => None,
|
||||||
Self::MajorMinor(_, _, _) => None,
|
Self::MajorMinor(_, _, _) => None,
|
||||||
Self::MajorMinorPatch(_, _, patch, _) => Some(*patch),
|
Self::MajorMinorPatch(_, _, patch, _) => Some(*patch),
|
||||||
|
@ -2273,7 +2289,7 @@ impl VersionRequest {
|
||||||
/// If not, an `Err` is returned with an explanatory message.
|
/// If not, an `Err` is returned with an explanatory message.
|
||||||
pub(crate) fn check_supported(&self) -> Result<(), String> {
|
pub(crate) fn check_supported(&self) -> Result<(), String> {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default => (),
|
Self::Any | Self::Latest | Self::Default => (),
|
||||||
Self::Major(major, _) => {
|
Self::Major(major, _) => {
|
||||||
if *major < 3 {
|
if *major < 3 {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
|
@ -2348,6 +2364,7 @@ impl VersionRequest {
|
||||||
pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
|
pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Any => true,
|
Self::Any => true,
|
||||||
|
Self::Latest => interpreter.python_version().pre().is_none(),
|
||||||
// Do not use free-threaded interpreters by default
|
// Do not use free-threaded interpreters by default
|
||||||
Self::Default => PythonVariant::Default.matches_interpreter(interpreter),
|
Self::Default => PythonVariant::Default.matches_interpreter(interpreter),
|
||||||
Self::Major(major, variant) => {
|
Self::Major(major, variant) => {
|
||||||
|
@ -2391,6 +2408,7 @@ impl VersionRequest {
|
||||||
pub(crate) fn matches_version(&self, version: &PythonVersion) -> bool {
|
pub(crate) fn matches_version(&self, version: &PythonVersion) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default => true,
|
Self::Any | Self::Default => true,
|
||||||
|
Self::Latest => version.pre().is_none(),
|
||||||
Self::Major(major, _) => version.major() == *major,
|
Self::Major(major, _) => version.major() == *major,
|
||||||
Self::MajorMinor(major, minor, _) => {
|
Self::MajorMinor(major, minor, _) => {
|
||||||
(version.major(), version.minor()) == (*major, *minor)
|
(version.major(), version.minor()) == (*major, *minor)
|
||||||
|
@ -2413,7 +2431,7 @@ impl VersionRequest {
|
||||||
/// avoid querying interpreters if it's clear it cannot fulfill the request.
|
/// avoid querying interpreters if it's clear it cannot fulfill the request.
|
||||||
fn matches_major_minor(&self, major: u8, minor: u8) -> bool {
|
fn matches_major_minor(&self, major: u8, minor: u8) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default => true,
|
Self::Any | Self::Latest | Self::Default => true,
|
||||||
Self::Major(self_major, _) => *self_major == major,
|
Self::Major(self_major, _) => *self_major == major,
|
||||||
Self::MajorMinor(self_major, self_minor, _) => {
|
Self::MajorMinor(self_major, self_minor, _) => {
|
||||||
(*self_major, *self_minor) == (major, minor)
|
(*self_major, *self_minor) == (major, minor)
|
||||||
|
@ -2460,6 +2478,7 @@ impl VersionRequest {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default => true,
|
Self::Any | Self::Default => true,
|
||||||
|
Self::Latest => prerelease.is_none(),
|
||||||
Self::Major(self_major, _) => *self_major == major,
|
Self::Major(self_major, _) => *self_major == major,
|
||||||
Self::MajorMinor(self_major, self_minor, _) => {
|
Self::MajorMinor(self_major, self_minor, _) => {
|
||||||
(*self_major, *self_minor) == (major, minor)
|
(*self_major, *self_minor) == (major, minor)
|
||||||
|
@ -2482,7 +2501,7 @@ impl VersionRequest {
|
||||||
/// Whether a patch version segment is present in the request.
|
/// Whether a patch version segment is present in the request.
|
||||||
fn has_patch(&self) -> bool {
|
fn has_patch(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default => false,
|
Self::Any | Self::Default | Self::Latest => false,
|
||||||
Self::Major(..) => false,
|
Self::Major(..) => false,
|
||||||
Self::MajorMinor(..) => false,
|
Self::MajorMinor(..) => false,
|
||||||
Self::MajorMinorPatch(..) => true,
|
Self::MajorMinorPatch(..) => true,
|
||||||
|
@ -2499,6 +2518,7 @@ impl VersionRequest {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => Self::Default,
|
Self::Default => Self::Default,
|
||||||
Self::Any => Self::Any,
|
Self::Any => Self::Any,
|
||||||
|
Self::Latest => Self::Latest,
|
||||||
Self::Major(major, variant) => Self::Major(major, variant),
|
Self::Major(major, variant) => Self::Major(major, variant),
|
||||||
Self::MajorMinor(major, minor, variant) => Self::MajorMinor(major, minor, variant),
|
Self::MajorMinor(major, minor, variant) => Self::MajorMinor(major, minor, variant),
|
||||||
Self::MajorMinorPatch(major, minor, _, variant) => {
|
Self::MajorMinorPatch(major, minor, _, variant) => {
|
||||||
|
@ -2514,7 +2534,7 @@ impl VersionRequest {
|
||||||
/// Whether this request should allow selection of pre-release versions.
|
/// Whether this request should allow selection of pre-release versions.
|
||||||
pub(crate) fn allows_prereleases(&self) -> bool {
|
pub(crate) fn allows_prereleases(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => false,
|
Self::Default | Self::Latest => false,
|
||||||
Self::Any => true,
|
Self::Any => true,
|
||||||
Self::Major(..) => false,
|
Self::Major(..) => false,
|
||||||
Self::MajorMinor(..) => false,
|
Self::MajorMinor(..) => false,
|
||||||
|
@ -2527,7 +2547,7 @@ impl VersionRequest {
|
||||||
/// Whether this request is for a free-threaded Python variant.
|
/// Whether this request is for a free-threaded Python variant.
|
||||||
pub(crate) fn is_freethreaded(&self) -> bool {
|
pub(crate) fn is_freethreaded(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default => false,
|
Self::Any | Self::Default | Self::Latest => false,
|
||||||
Self::Major(_, variant)
|
Self::Major(_, variant)
|
||||||
| Self::MajorMinor(_, _, variant)
|
| Self::MajorMinor(_, _, variant)
|
||||||
| Self::MajorMinorPatch(_, _, _, variant)
|
| Self::MajorMinorPatch(_, _, _, variant)
|
||||||
|
@ -2544,7 +2564,7 @@ impl VersionRequest {
|
||||||
// TODO(zanieb): Replace this entire function with a utility that casts this to a version
|
// TODO(zanieb): Replace this entire function with a utility that casts this to a version
|
||||||
// without using `VersionRequest::to_string`.
|
// without using `VersionRequest::to_string`.
|
||||||
match self {
|
match self {
|
||||||
Self::Any | Self::Default => self,
|
Self::Any | Self::Default | Self::Latest => self,
|
||||||
Self::Major(major, _) => Self::Major(major, PythonVariant::Default),
|
Self::Major(major, _) => Self::Major(major, PythonVariant::Default),
|
||||||
Self::MajorMinor(major, minor, _) => {
|
Self::MajorMinor(major, minor, _) => {
|
||||||
Self::MajorMinor(major, minor, PythonVariant::Default)
|
Self::MajorMinor(major, minor, PythonVariant::Default)
|
||||||
|
@ -2563,7 +2583,7 @@ impl VersionRequest {
|
||||||
pub(crate) fn variant(&self) -> Option<PythonVariant> {
|
pub(crate) fn variant(&self) -> Option<PythonVariant> {
|
||||||
match self {
|
match self {
|
||||||
Self::Any => None,
|
Self::Any => None,
|
||||||
Self::Default => Some(PythonVariant::Default),
|
Self::Default | Self::Latest => Some(PythonVariant::Default),
|
||||||
Self::Major(_, variant)
|
Self::Major(_, variant)
|
||||||
| Self::MajorMinor(_, _, variant)
|
| Self::MajorMinor(_, _, variant)
|
||||||
| Self::MajorMinorPatch(_, _, _, variant)
|
| Self::MajorMinorPatch(_, _, _, variant)
|
||||||
|
@ -2578,7 +2598,7 @@ impl FromStr for VersionRequest {
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
// Stripping the 't' suffix produces awkward error messages if the user tries a version
|
// Stripping the 't' suffix produces awkward error messages if the user tries a version
|
||||||
// like "latest". HACK: If the version is all letters, don't even try to parse it further.
|
// like "oldest". HACK: If the version is all letters, don't even try to parse it further.
|
||||||
if s.chars().all(char::is_alphabetic) {
|
if s.chars().all(char::is_alphabetic) {
|
||||||
return Err(Error::InvalidVersionRequest(s.to_string()));
|
return Err(Error::InvalidVersionRequest(s.to_string()));
|
||||||
}
|
}
|
||||||
|
@ -2718,6 +2738,7 @@ impl fmt::Display for VersionRequest {
|
||||||
match self {
|
match self {
|
||||||
Self::Any => f.write_str("any"),
|
Self::Any => f.write_str("any"),
|
||||||
Self::Default => f.write_str("default"),
|
Self::Default => f.write_str("default"),
|
||||||
|
Self::Latest => f.write_str("latest"),
|
||||||
Self::Major(major, PythonVariant::Default) => write!(f, "{major}"),
|
Self::Major(major, PythonVariant::Default) => write!(f, "{major}"),
|
||||||
Self::Major(major, PythonVariant::Freethreaded) => write!(f, "{major}t"),
|
Self::Major(major, PythonVariant::Freethreaded) => write!(f, "{major}t"),
|
||||||
Self::MajorMinor(major, minor, PythonVariant::Default) => write!(f, "{major}.{minor}"),
|
Self::MajorMinor(major, minor, PythonVariant::Default) => write!(f, "{major}.{minor}"),
|
||||||
|
@ -2746,6 +2767,7 @@ impl fmt::Display for PythonRequest {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => write!(f, "a default Python"),
|
Self::Default => write!(f, "a default Python"),
|
||||||
Self::Any => write!(f, "any Python"),
|
Self::Any => write!(f, "any Python"),
|
||||||
|
Self::Latest => write!(f, "latest stable Python"),
|
||||||
Self::Version(version) => write!(f, "Python {version}"),
|
Self::Version(version) => write!(f, "Python {version}"),
|
||||||
Self::Directory(path) => write!(f, "directory `{}`", path.user_display()),
|
Self::Directory(path) => write!(f, "directory `{}`", path.user_display()),
|
||||||
Self::File(path) => write!(f, "path `{}`", path.user_display()),
|
Self::File(path) => write!(f, "path `{}`", path.user_display()),
|
||||||
|
|
|
@ -316,7 +316,7 @@ impl PythonDownloadRequest {
|
||||||
prereleases: Some(true), // Explicitly allow pre-releases for PythonRequest::Any
|
prereleases: Some(true), // Explicitly allow pre-releases for PythonRequest::Any
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}),
|
}),
|
||||||
PythonRequest::Default => Some(Self::default()),
|
PythonRequest::Default | PythonRequest::Latest => Some(Self::default()),
|
||||||
// We can't download a managed installation for these request kinds
|
// We can't download a managed installation for these request kinds
|
||||||
PythonRequest::Directory(_)
|
PythonRequest::Directory(_)
|
||||||
| PythonRequest::ExecutableName(_)
|
| PythonRequest::ExecutableName(_)
|
||||||
|
|
|
@ -1061,6 +1061,106 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_python_latest() -> Result<()> {
|
||||||
|
let mut context = TestContext::new()?;
|
||||||
|
context.add_python_versions(&["3.8.10", "3.11.5", "3.9.18", "3.10.12"])?;
|
||||||
|
|
||||||
|
let python = context.run(|| {
|
||||||
|
find_python_installation(
|
||||||
|
&PythonRequest::Latest,
|
||||||
|
EnvironmentPreference::Any,
|
||||||
|
PythonPreference::OnlySystem,
|
||||||
|
&context.cache,
|
||||||
|
)
|
||||||
|
})??;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
python,
|
||||||
|
PythonInstallation {
|
||||||
|
source: PythonSource::SearchPath,
|
||||||
|
interpreter: _
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"We should find a python; got {python:?}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&python.interpreter().python_full_version().to_string(),
|
||||||
|
"3.11.5",
|
||||||
|
"We should find the latest version (3.11.5)"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_python_latest_with_prereleases() -> Result<()> {
|
||||||
|
let mut context = TestContext::new()?;
|
||||||
|
context.add_python_versions(&["3.10.1", "3.11.2", "3.12.0rc1", "3.13.0a1"])?;
|
||||||
|
|
||||||
|
let python = context.run(|| {
|
||||||
|
find_python_installation(
|
||||||
|
&PythonRequest::Latest,
|
||||||
|
EnvironmentPreference::Any,
|
||||||
|
PythonPreference::OnlySystem,
|
||||||
|
&context.cache,
|
||||||
|
)
|
||||||
|
})??;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&python.interpreter().python_full_version().to_string(),
|
||||||
|
"3.11.2",
|
||||||
|
"Latest should find the highest stable version"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_python_latest_no_pythons() -> Result<()> {
|
||||||
|
let context = TestContext::new()?;
|
||||||
|
// Don't add any Python versions
|
||||||
|
|
||||||
|
let result = context.run(|| {
|
||||||
|
find_python_installation(
|
||||||
|
&PythonRequest::Latest,
|
||||||
|
EnvironmentPreference::Any,
|
||||||
|
PythonPreference::OnlySystem,
|
||||||
|
&context.cache,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(result, Err(PythonNotFound { .. })),
|
||||||
|
"We should not find any python when none are available; got {result:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_python_latest_only_prereleases() -> Result<()> {
|
||||||
|
let mut context = TestContext::new()?;
|
||||||
|
context.add_python_versions(&["3.12.0rc1", "3.13.0a1", "3.11.0b2"])?;
|
||||||
|
|
||||||
|
let result = context.run(|| {
|
||||||
|
find_python_installation(
|
||||||
|
&PythonRequest::Latest,
|
||||||
|
EnvironmentPreference::Any,
|
||||||
|
PythonPreference::OnlySystem,
|
||||||
|
&context.cache,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(result, Err(PythonNotFound { .. })),
|
||||||
|
"We should not find any python when only prereleases are available; got {result:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn find_python_from_conda_prefix() -> Result<()> {
|
fn find_python_from_conda_prefix() -> Result<()> {
|
||||||
let context = TestContext::new()?;
|
let context = TestContext::new()?;
|
||||||
|
|
|
@ -473,6 +473,7 @@ impl ManagedPythonInstallation {
|
||||||
}
|
}
|
||||||
PythonRequest::Version(version) => version.matches_version(&self.version()),
|
PythonRequest::Version(version) => version.matches_version(&self.version()),
|
||||||
PythonRequest::Key(request) => request.satisfied_by_key(self.key()),
|
PythonRequest::Key(request) => request.satisfied_by_key(self.key()),
|
||||||
|
PythonRequest::Latest => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2039,14 +2039,15 @@ fn tool_run_python_at_version() {
|
||||||
// Request `@latest` (not yet supported)
|
// Request `@latest` (not yet supported)
|
||||||
uv_snapshot!(context.filters(), context.tool_run()
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
.arg("python@latest")
|
.arg("python@latest")
|
||||||
.arg("--version"), @r###"
|
.arg("--version"), @r"
|
||||||
success: false
|
success: true
|
||||||
exit_code: 2
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
Python 3.12.[X]
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: Requesting the 'latest' Python version is not yet supported
|
Resolved in [TIME]
|
||||||
"###);
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue