1use itertools::{Either, Itertools};
2use regex::Regex;
3use rustc_hash::{FxBuildHasher, FxHashSet};
4use same_file::is_same_file;
5use std::borrow::Cow;
6use std::env::consts::EXE_SUFFIX;
7use std::fmt::{self, Debug, Formatter};
8use std::{env, io, iter};
9use std::{path::Path, path::PathBuf, str::FromStr};
10use thiserror::Error;
11use tracing::{debug, instrument, trace};
12use uv_cache::Cache;
13use uv_client::BaseClientBuilder;
14use uv_distribution_types::RequiresPython;
15use uv_fs::Simplified;
16use uv_fs::which::is_executable;
17use uv_pep440::{
18 LowerBound, Prerelease, UpperBound, Version, VersionSpecifier, VersionSpecifiers,
19 release_specifiers_to_ranges,
20};
21use uv_static::EnvVars;
22use uv_warnings::{warn_user_once, write_warning_chain};
23use which::{which, which_all};
24
25use crate::downloads::{ManagedPythonDownloadList, PlatformRequest, PythonDownloadRequest};
26use crate::implementation::ImplementationName;
27use crate::installation::{PythonInstallation, PythonInstallationKey};
28use crate::interpreter::Error as InterpreterError;
29use crate::interpreter::{StatusCodeError, UnexpectedResponseError};
30use crate::managed::{ManagedPythonInstallations, PythonMinorVersionLink};
31#[cfg(windows)]
32use crate::microsoft_store::find_microsoft_store_pythons;
33use crate::python_version::python_build_versions_from_env;
34use crate::virtualenv::Error as VirtualEnvError;
35use crate::virtualenv::{
36 CondaEnvironmentKind, conda_environment_from_env, virtualenv_from_env,
37 virtualenv_from_working_dir, virtualenv_python_executable,
38};
39#[cfg(windows)]
40use crate::windows_registry::{WindowsPython, registry_pythons};
41use crate::{BrokenLink, Interpreter, PythonVersion};
42
43#[derive(Debug, Clone, Eq, Default)]
47pub enum PythonRequest {
48 #[default]
53 Default,
54 Any,
56 Version(VersionRequest),
58 Directory(PathBuf),
60 File(PathBuf),
62 ExecutableName(String),
64 Implementation(ImplementationName),
66 ImplementationVersion(ImplementationName, VersionRequest),
68 Key(PythonDownloadRequest),
71}
72
73impl PartialEq for PythonRequest {
74 fn eq(&self, other: &Self) -> bool {
75 self.to_canonical_string() == other.to_canonical_string()
76 }
77}
78
79impl std::hash::Hash for PythonRequest {
80 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
81 self.to_canonical_string().hash(state);
82 }
83}
84
85impl<'a> serde::Deserialize<'a> for PythonRequest {
86 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
87 where
88 D: serde::Deserializer<'a>,
89 {
90 let s = <Cow<'_, str>>::deserialize(deserializer)?;
91 Ok(Self::parse(&s))
92 }
93}
94
95impl serde::Serialize for PythonRequest {
96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97 where
98 S: serde::Serializer,
99 {
100 let s = self.to_canonical_string();
101 serializer.serialize_str(&s)
102 }
103}
104
105#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
106#[serde(deny_unknown_fields, rename_all = "kebab-case")]
107#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
108#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
109pub enum PythonPreference {
110 OnlyManaged,
112 #[default]
113 Managed,
118 System,
122 OnlySystem,
124}
125
126#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
127#[serde(deny_unknown_fields, rename_all = "kebab-case")]
128#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
129#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
130pub enum PythonDownloads {
131 #[default]
133 #[serde(alias = "auto")]
134 Automatic,
135 Manual,
137 Never,
139}
140
141impl FromStr for PythonDownloads {
142 type Err = String;
143
144 fn from_str(s: &str) -> Result<Self, Self::Err> {
145 match s.to_ascii_lowercase().as_str() {
146 "auto" | "automatic" | "true" | "1" => Ok(Self::Automatic),
147 "manual" => Ok(Self::Manual),
148 "never" | "false" | "0" => Ok(Self::Never),
149 _ => Err(format!("Invalid value for `python-download`: '{s}'")),
150 }
151 }
152}
153
154impl From<bool> for PythonDownloads {
155 fn from(value: bool) -> Self {
156 if value { Self::Automatic } else { Self::Never }
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
161pub enum EnvironmentPreference {
162 #[default]
164 OnlyVirtual,
165 ExplicitSystem,
167 OnlySystem,
169 Any,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Default)]
174pub(crate) struct DiscoveryPreferences {
175 python_preference: PythonPreference,
176 environment_preference: EnvironmentPreference,
177}
178
179#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
180pub enum PythonVariant {
181 #[default]
182 Default,
183 Debug,
184 Freethreaded,
185 FreethreadedDebug,
186 Gil,
187 GilDebug,
188}
189
190#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
192pub enum VersionRequest {
193 #[default]
195 Default,
196 Any,
198 Major(u8, PythonVariant),
199 MajorMinor(u8, u8, PythonVariant),
200 MajorMinorPatch(u8, u8, u8, PythonVariant),
201 MajorMinorPrerelease(u8, u8, Prerelease, PythonVariant),
202 MajorMinorPatchPrerelease(u8, u8, u8, Prerelease, PythonVariant),
203 Range(VersionSpecifiers, PythonVariant),
204}
205
206type FindPythonResult = Result<PythonInstallation, PythonNotFound>;
210
211#[derive(Clone, Debug, Error)]
215pub struct PythonNotFound {
216 pub(crate) request: PythonRequest,
217 pub(crate) python_preference: PythonPreference,
218 pub(crate) environment_preference: EnvironmentPreference,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)]
223pub enum PythonSource {
224 ProvidedPath,
226 ActiveEnvironment,
228 CondaPrefix,
230 BaseCondaPrefix,
232 DiscoveredEnvironment,
234 SearchPath,
236 SearchPathFirst,
238 Registry,
240 MicrosoftStore,
242 Managed,
244 ParentInterpreter,
246}
247
248#[derive(Error, Debug)]
249pub enum Error {
250 #[error(transparent)]
251 Io(#[from] io::Error),
252
253 #[error("Failed to inspect Python interpreter from {} at `{}` ", _2, _1.user_display())]
255 Query(
256 #[source] Box<crate::interpreter::Error>,
257 PathBuf,
258 PythonSource,
259 ),
260
261 #[error("Failed to discover managed Python installations")]
264 ManagedPython(#[from] crate::managed::Error),
265
266 #[error(transparent)]
268 VirtualEnv(#[from] crate::virtualenv::Error),
269
270 #[cfg(windows)]
271 #[error("Failed to query installed Python versions from the Windows registry")]
272 RegistryError(#[from] windows::core::Error),
273
274 #[error(transparent)]
275 InvalidEnvironmentVariable(#[from] uv_static::InvalidEnvironmentVariable),
276
277 #[error("Invalid version request: {0}")]
279 InvalidVersionRequest(String),
280
281 #[error("Requesting the 'latest' Python version is not yet supported")]
283 LatestVersionRequest,
284
285 #[error("Interpreter discovery for `{0}` requires `{1}` but only `{2}` is allowed")]
287 SourceNotAllowed(PythonRequest, PythonSource, PythonPreference),
288
289 #[error(transparent)]
290 BuildVersion(#[from] crate::python_version::BuildVersionError),
291}
292
293impl uv_errors::Hint for Error {
294 fn hints(&self) -> uv_errors::Hints<'_> {
295 match self {
296 Self::Query(err, _, _) => err.hints(),
297 _ => uv_errors::Hints::none(),
298 }
299 }
300}
301
302fn python_executables_from_virtual_environments<'a>()
311-> impl Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a {
312 let from_active_environment = iter::once_with(|| {
313 virtualenv_from_env()
314 .into_iter()
315 .map(virtualenv_python_executable)
316 .map(|path| Ok((PythonSource::ActiveEnvironment, path)))
317 })
318 .flatten();
319
320 let from_conda_environment = iter::once_with(move || {
322 conda_environment_from_env(CondaEnvironmentKind::Child)
323 .into_iter()
324 .map(virtualenv_python_executable)
325 .map(|path| Ok((PythonSource::CondaPrefix, path)))
326 })
327 .flatten();
328
329 let from_discovered_environment = iter::once_with(|| {
330 virtualenv_from_working_dir()
331 .map(|path| {
332 path.map(virtualenv_python_executable)
333 .map(|path| (PythonSource::DiscoveredEnvironment, path))
334 .into_iter()
335 })
336 .map_err(Error::from)
337 })
338 .flatten_ok();
339
340 from_active_environment
341 .chain(from_conda_environment)
342 .chain(from_discovered_environment)
343}
344
345fn python_executables_from_installed<'a>(
364 version: &'a VersionRequest,
365 implementation: Option<&'a ImplementationName>,
366 platform: PlatformRequest,
367 preference: PythonPreference,
368) -> Box<dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a> {
369 let from_managed_installations = iter::once_with(move || {
370 ManagedPythonInstallations::from_settings(None)
371 .map_err(Error::from)
372 .and_then(|installed_installations| {
373 debug!(
374 "Searching for managed installations at `{}`",
375 installed_installations.root().user_display()
376 );
377 let installations = ManagedPythonInstallations::find_matching_current_platform()?;
378
379 let build_versions = python_build_versions_from_env()?;
380
381 Ok(installations
384 .into_iter()
385 .filter(move |installation| {
386 if !version.matches_version(&installation.version()) {
387 debug!("Skipping managed installation `{installation}`: does not satisfy `{version}`");
388 return false;
389 }
390 if !platform.matches(installation.platform()) {
391 debug!("Skipping managed installation `{installation}`: does not satisfy requested platform `{platform}`");
392 return false;
393 }
394
395 if let Some(requested_build) = build_versions.get(&installation.implementation()) {
396 let Some(installation_build) = installation.build() else {
397 debug!(
398 "Skipping managed installation `{installation}`: a build version was requested but is not recorded for this installation"
399 );
400 return false;
401 };
402 if installation_build != requested_build {
403 debug!(
404 "Skipping managed installation `{installation}`: requested build version `{requested_build}` does not match installation build version `{installation_build}`"
405 );
406 return false;
407 }
408 }
409
410 true
411 })
412 .inspect(|installation| debug!("Found managed installation `{installation}`"))
413 .map(move |installation| {
414 let executable = version
417 .patch()
418 .is_none()
419 .then(|| {
420 PythonMinorVersionLink::from_installation(
421 &installation,
422 )
423 .filter(PythonMinorVersionLink::exists)
424 .map(
425 |minor_version_link| {
426 minor_version_link.symlink_executable.clone()
427 },
428 )
429 })
430 .flatten()
431 .unwrap_or_else(|| installation.executable(false));
432 (PythonSource::Managed, executable)
433 })
434 )
435 })
436 })
437 .flatten_ok();
438
439 let from_search_path = iter::once_with(move || {
440 python_executables_from_search_path(version, implementation)
441 .enumerate()
442 .map(|(i, path)| {
443 if i == 0 {
444 Ok((PythonSource::SearchPathFirst, path))
445 } else {
446 Ok((PythonSource::SearchPath, path))
447 }
448 })
449 })
450 .flatten();
451
452 #[cfg(windows)]
453 let from_windows_registry: Box<
454 dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a,
455 > = match uv_static::parse_boolish_environment_variable(EnvVars::UV_PYTHON_NO_REGISTRY) {
456 Ok(Some(true)) => Box::new(iter::empty()),
457 Ok(Some(false) | None) => Box::new(
458 iter::once_with(move || {
459 let version_filter = move |entry: &WindowsPython| {
461 if let Some(found) = &entry.version {
462 if found.string.chars().filter(|c| *c == '.').count() == 1 {
464 version.matches_major_minor(found.major(), found.minor())
465 } else {
466 version.matches_version(found)
467 }
468 } else {
469 true
470 }
471 };
472
473 registry_pythons()
474 .map(|entries| {
475 entries
476 .into_iter()
477 .filter(version_filter)
478 .map(|entry| (PythonSource::Registry, entry.path))
479 .chain(
480 find_microsoft_store_pythons()
481 .filter(version_filter)
482 .map(|entry| (PythonSource::MicrosoftStore, entry.path)),
483 )
484 })
485 .map_err(Error::from)
486 })
487 .flatten_ok(),
488 ),
489 Err(err) => Box::new(iter::once(Err(Error::from(err)))),
490 };
491
492 #[cfg(not(windows))]
493 let from_windows_registry: Box<
494 dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a,
495 > = Box::new(iter::empty());
496
497 match preference {
498 PythonPreference::OnlyManaged => {
499 if std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED).is_ok() {
503 Box::new(from_managed_installations.chain(from_search_path))
504 } else {
505 Box::new(from_managed_installations)
506 }
507 }
508 PythonPreference::Managed => Box::new(
509 from_managed_installations
510 .chain(from_search_path)
511 .chain(from_windows_registry),
512 ),
513 PythonPreference::System => Box::new(
514 from_search_path
515 .chain(from_windows_registry)
516 .chain(from_managed_installations),
517 ),
518 PythonPreference::OnlySystem => Box::new(from_search_path.chain(from_windows_registry)),
519 }
520}
521
522fn python_executables<'a>(
532 version: &'a VersionRequest,
533 implementation: Option<&'a ImplementationName>,
534 platform: PlatformRequest,
535 environments: EnvironmentPreference,
536 preference: PythonPreference,
537) -> Box<dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a> {
538 let from_parent_interpreter = iter::once_with(|| {
540 env::var_os(EnvVars::UV_INTERNAL__PARENT_INTERPRETER)
541 .into_iter()
542 .map(|path| Ok((PythonSource::ParentInterpreter, PathBuf::from(path))))
543 })
544 .flatten();
545
546 let from_base_conda_environment = iter::once_with(move || {
548 conda_environment_from_env(CondaEnvironmentKind::Base)
549 .into_iter()
550 .map(virtualenv_python_executable)
551 .map(|path| Ok((PythonSource::BaseCondaPrefix, path)))
552 })
553 .flatten();
554
555 let from_virtual_environments = python_executables_from_virtual_environments();
556 let from_installed =
557 python_executables_from_installed(version, implementation, platform, preference);
558
559 match environments {
563 EnvironmentPreference::OnlyVirtual => {
564 Box::new(from_parent_interpreter.chain(from_virtual_environments))
565 }
566 EnvironmentPreference::ExplicitSystem | EnvironmentPreference::Any => Box::new(
567 from_parent_interpreter
568 .chain(from_virtual_environments)
569 .chain(from_base_conda_environment)
570 .chain(from_installed),
571 ),
572 EnvironmentPreference::OnlySystem => Box::new(
573 from_parent_interpreter
574 .chain(from_base_conda_environment)
575 .chain(from_installed),
576 ),
577 }
578}
579
580fn python_executables_from_search_path<'a>(
592 version: &'a VersionRequest,
593 implementation: Option<&'a ImplementationName>,
594) -> impl Iterator<Item = PathBuf> + 'a {
595 let search_path = env::var_os(EnvVars::UV_PYTHON_SEARCH_PATH)
597 .unwrap_or(env::var_os(EnvVars::PATH).unwrap_or_default());
598
599 let possible_names: Vec<_> = version
600 .executable_names(implementation)
601 .into_iter()
602 .map(|name| name.to_string())
603 .collect();
604
605 trace!(
606 "Searching PATH for executables: {}",
607 possible_names.join(", ")
608 );
609
610 let search_dirs: Vec<_> = env::split_paths(&search_path).collect();
614 let mut seen_dirs = FxHashSet::with_capacity_and_hasher(search_dirs.len(), FxBuildHasher);
615 search_dirs
616 .into_iter()
617 .filter(|dir| dir.is_dir())
618 .flat_map(move |dir| {
619 let dir_clone = dir.clone();
621 trace!(
622 "Checking `PATH` directory for interpreters: {}",
623 dir.display()
624 );
625 same_file::Handle::from_path(&dir)
626 .map(|handle| seen_dirs.insert(handle))
629 .inspect(|fresh_dir| {
630 if !fresh_dir {
631 trace!("Skipping already seen directory: {}", dir.display());
632 }
633 })
634 .unwrap_or(true)
636 .then(|| {
637 possible_names
638 .clone()
639 .into_iter()
640 .flat_map(move |name| {
641 which::which_in_global(&*name, Some(&dir))
643 .into_iter()
644 .flatten()
645 .collect::<Vec<_>>()
648 })
649 .chain(find_all_minor(implementation, version, &dir_clone))
650 .filter(|path| !is_windows_store_shim(path))
651 .inspect(|path| {
652 trace!("Found possible Python executable: {}", path.display());
653 })
654 .chain(
655 cfg!(windows)
657 .then(move || {
658 which::which_in_global("python.bat", Some(&dir_clone))
659 .into_iter()
660 .flatten()
661 .collect::<Vec<_>>()
662 })
663 .into_iter()
664 .flatten(),
665 )
666 })
667 .into_iter()
668 .flatten()
669 })
670}
671
672fn find_all_minor(
677 implementation: Option<&ImplementationName>,
678 version_request: &VersionRequest,
679 dir: &Path,
680) -> impl Iterator<Item = PathBuf> + use<> {
681 match version_request {
682 &VersionRequest::Any
683 | VersionRequest::Default
684 | VersionRequest::Major(_, _)
685 | VersionRequest::Range(_, _) => {
686 let regex = if let Some(implementation) = implementation {
687 Regex::new(&format!(
688 r"^({}|python3)\.(?<minor>\d\d?)t?{}$",
689 regex::escape(&implementation.to_string()),
690 regex::escape(EXE_SUFFIX)
691 ))
692 .unwrap()
693 } else {
694 Regex::new(&format!(
695 r"^python3\.(?<minor>\d\d?)t?{}$",
696 regex::escape(EXE_SUFFIX)
697 ))
698 .unwrap()
699 };
700 let all_minors = fs_err::read_dir(dir)
701 .into_iter()
702 .flatten()
703 .flatten()
704 .map(|entry| entry.path())
705 .filter(move |path| {
706 let Some(filename) = path.file_name() else {
707 return false;
708 };
709 let Some(filename) = filename.to_str() else {
710 return false;
711 };
712 let Some(captures) = regex.captures(filename) else {
713 return false;
714 };
715
716 let minor = captures["minor"].parse().ok();
718 if let Some(minor) = minor {
719 if minor < 6 {
721 return false;
722 }
723 if !version_request.matches_major_minor(3, minor) {
725 return false;
726 }
727 }
728 true
729 })
730 .filter(|path| is_executable(path))
731 .collect::<Vec<_>>();
732 Either::Left(all_minors.into_iter())
733 }
734 VersionRequest::MajorMinor(_, _, _)
735 | VersionRequest::MajorMinorPatch(_, _, _, _)
736 | VersionRequest::MajorMinorPrerelease(_, _, _, _)
737 | VersionRequest::MajorMinorPatchPrerelease(_, _, _, _, _) => Either::Right(iter::empty()),
738 }
739}
740
741fn python_installations<'a>(
751 version: &'a VersionRequest,
752 implementation: Option<&'a ImplementationName>,
753 platform: PlatformRequest,
754 environments: EnvironmentPreference,
755 preference: PythonPreference,
756 cache: &'a Cache,
757) -> impl Iterator<Item = Result<PythonInstallation, Error>> + 'a {
758 let installations = python_installations_from_executables(
759 python_executables(version, implementation, platform, environments, preference).filter_ok(
763 move |(source, path)| {
764 source_satisfies_environment_preference(*source, path, environments)
765 },
766 ),
767 cache,
768 )
769 .filter_ok(move |installation| {
770 interpreter_satisfies_environment_preference(
771 installation.source,
772 &installation.interpreter,
773 environments,
774 )
775 })
776 .filter_ok(move |installation| {
777 let request = version.clone().into_request_for_source(installation.source);
778 if request.matches_interpreter(&installation.interpreter) {
779 true
780 } else {
781 debug!(
782 "Skipping interpreter at `{}` from {}: does not satisfy request `{request}`",
783 installation.interpreter.sys_executable().user_display(),
784 installation.source,
785 );
786 false
787 }
788 })
789 .filter_ok(move |installation| preference.allows_installation(installation));
790
791 if std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED).is_ok() {
792 Either::Left(installations.map_ok(|mut installation| {
793 if installation.interpreter.is_managed() {
796 installation.source = PythonSource::Managed;
797 }
798 installation
799 }))
800 } else {
801 Either::Right(installations)
802 }
803}
804
805fn python_installations_from_executables<'a>(
807 executables: impl Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a,
808 cache: &'a Cache,
809) -> impl Iterator<Item = Result<PythonInstallation, Error>> + 'a {
810 executables.map(|result| match result {
811 Ok((source, path)) => Interpreter::query(&path, cache)
812 .map(|interpreter| PythonInstallation {
813 source,
814 interpreter,
815 })
816 .inspect(|installation| {
817 debug!(
818 "Found `{}` at `{}` ({source})",
819 installation.key(),
820 path.display()
821 );
822 })
823 .map_err(|err| Error::Query(Box::new(err), path, source))
824 .inspect_err(|err| debug!("{err}")),
825 Err(err) => Err(err),
826 })
827}
828
829fn interpreter_satisfies_environment_preference(
836 source: PythonSource,
837 interpreter: &Interpreter,
838 preference: EnvironmentPreference,
839) -> bool {
840 match (
841 preference,
842 interpreter.is_virtualenv() || (matches!(source, PythonSource::CondaPrefix)),
844 ) {
845 (EnvironmentPreference::Any, _) => true,
846 (EnvironmentPreference::OnlyVirtual, true) => true,
847 (EnvironmentPreference::OnlyVirtual, false) => {
848 debug!(
849 "Ignoring Python interpreter at `{}`: only virtual environments allowed",
850 interpreter.sys_executable().display()
851 );
852 false
853 }
854 (EnvironmentPreference::ExplicitSystem, true) => true,
855 (EnvironmentPreference::ExplicitSystem, false) => {
856 if matches!(
857 source,
858 PythonSource::ProvidedPath | PythonSource::ParentInterpreter
859 ) {
860 debug!(
861 "Allowing explicitly requested system Python interpreter at `{}`",
862 interpreter.sys_executable().display()
863 );
864 true
865 } else {
866 debug!(
867 "Ignoring Python interpreter at `{}`: system interpreter not explicitly requested",
868 interpreter.sys_executable().display()
869 );
870 false
871 }
872 }
873 (EnvironmentPreference::OnlySystem, true) => {
874 debug!(
875 "Ignoring Python interpreter at `{}`: system interpreter required",
876 interpreter.sys_executable().display()
877 );
878 false
879 }
880 (EnvironmentPreference::OnlySystem, false) => true,
881 }
882}
883
884fn source_satisfies_environment_preference(
891 source: PythonSource,
892 interpreter_path: &Path,
893 preference: EnvironmentPreference,
894) -> bool {
895 match preference {
896 EnvironmentPreference::Any => true,
897 EnvironmentPreference::OnlyVirtual => {
898 if source.is_maybe_virtualenv() {
899 true
900 } else {
901 debug!(
902 "Ignoring Python interpreter at `{}`: only virtual environments allowed",
903 interpreter_path.display()
904 );
905 false
906 }
907 }
908 EnvironmentPreference::ExplicitSystem => {
909 if source.is_maybe_virtualenv() {
910 true
911 } else {
912 debug!(
913 "Ignoring Python interpreter at `{}`: system interpreter not explicitly requested",
914 interpreter_path.display()
915 );
916 false
917 }
918 }
919 EnvironmentPreference::OnlySystem => {
920 if source.is_maybe_system() {
921 true
922 } else {
923 debug!(
924 "Ignoring Python interpreter at `{}`: system interpreter required",
925 interpreter_path.display()
926 );
927 false
928 }
929 }
930 }
931}
932
933impl Error {
937 pub fn is_critical(&self) -> bool {
938 match self {
939 Self::Query(err, _, source) => match &**err {
942 InterpreterError::Encode(_)
943 | InterpreterError::Io(_)
944 | InterpreterError::SpawnFailed { .. } => true,
945 InterpreterError::UnexpectedResponse(UnexpectedResponseError { path, .. })
946 | InterpreterError::StatusCode(StatusCodeError { path, .. }) => {
947 debug!(
948 "Skipping bad interpreter at {} from {source}: {err}",
949 path.display()
950 );
951 false
952 }
953 InterpreterError::QueryScript { path, err } => {
954 debug!(
955 "Skipping bad interpreter at {} from {source}: {err}",
956 path.display()
957 );
958 false
959 }
960 #[cfg(windows)]
961 InterpreterError::CorruptWindowsPackage { path, err } => {
962 debug!(
963 "Skipping bad interpreter at {} from {source}: {err}",
964 path.display()
965 );
966 false
967 }
968 InterpreterError::PermissionDenied { path, err } => {
969 debug!(
970 "Skipping unexecutable interpreter at {} from {source}: {err}",
971 path.display()
972 );
973 false
974 }
975 InterpreterError::NotFound(path)
976 | InterpreterError::BrokenLink(BrokenLink { path, .. }) => {
977 if matches!(source, PythonSource::ActiveEnvironment)
980 && uv_fs::is_virtualenv_executable(path)
981 {
982 true
983 } else {
984 trace!("Skipping missing interpreter at {}", path.display());
985 false
986 }
987 }
988 },
989 Self::VirtualEnv(VirtualEnvError::MissingPyVenvCfg(path)) => {
990 trace!("Skipping broken virtualenv at {}", path.display());
991 false
992 }
993 _ => true,
994 }
995 }
996}
997
998fn python_installation_from_executable(
1000 path: &PathBuf,
1001 cache: &Cache,
1002) -> Result<PythonInstallation, crate::interpreter::Error> {
1003 Ok(PythonInstallation {
1004 source: PythonSource::ProvidedPath,
1005 interpreter: Interpreter::query(path, cache)?,
1006 })
1007}
1008
1009fn python_installation_from_directory(
1011 path: &PathBuf,
1012 cache: &Cache,
1013) -> Result<PythonInstallation, crate::interpreter::Error> {
1014 let executable = virtualenv_python_executable(path);
1015 python_installation_from_executable(&executable, cache)
1016}
1017
1018fn python_installations_with_executable_name<'a>(
1020 name: &'a str,
1021 cache: &'a Cache,
1022) -> impl Iterator<Item = Result<PythonInstallation, Error>> + 'a {
1023 python_installations_from_executables(
1024 which_all(name)
1025 .into_iter()
1026 .flat_map(|inner| inner.map(|path| Ok((PythonSource::SearchPath, path)))),
1027 cache,
1028 )
1029}
1030
1031pub fn find_python_installations<'a>(
1033 request: &'a PythonRequest,
1034 environments: EnvironmentPreference,
1035 preference: PythonPreference,
1036 cache: &'a Cache,
1037) -> Box<dyn Iterator<Item = Result<FindPythonResult, Error>> + 'a> {
1038 let sources = DiscoveryPreferences {
1039 python_preference: preference,
1040 environment_preference: environments,
1041 }
1042 .sources(request);
1043
1044 match request {
1045 PythonRequest::File(path) => Box::new(iter::once({
1046 if preference.allows_source(PythonSource::ProvidedPath) {
1047 debug!("Checking for Python interpreter at {request}");
1048 match python_installation_from_executable(path, cache) {
1049 Ok(installation) => Ok(Ok(installation)),
1050 Err(InterpreterError::NotFound(_) | InterpreterError::BrokenLink(_)) => {
1051 Ok(Err(PythonNotFound {
1052 request: request.clone(),
1053 python_preference: preference,
1054 environment_preference: environments,
1055 }))
1056 }
1057 Err(err) => Err(Error::Query(
1058 Box::new(err),
1059 path.clone(),
1060 PythonSource::ProvidedPath,
1061 )),
1062 }
1063 } else {
1064 Err(Error::SourceNotAllowed(
1065 request.clone(),
1066 PythonSource::ProvidedPath,
1067 preference,
1068 ))
1069 }
1070 })),
1071 PythonRequest::Directory(path) => Box::new(iter::once({
1072 if preference.allows_source(PythonSource::ProvidedPath) {
1073 debug!("Checking for Python interpreter in {request}");
1074 match python_installation_from_directory(path, cache) {
1075 Ok(installation) => Ok(Ok(installation)),
1076 Err(InterpreterError::NotFound(_) | InterpreterError::BrokenLink(_)) => {
1077 Ok(Err(PythonNotFound {
1078 request: request.clone(),
1079 python_preference: preference,
1080 environment_preference: environments,
1081 }))
1082 }
1083 Err(err) => Err(Error::Query(
1084 Box::new(err),
1085 path.clone(),
1086 PythonSource::ProvidedPath,
1087 )),
1088 }
1089 } else {
1090 Err(Error::SourceNotAllowed(
1091 request.clone(),
1092 PythonSource::ProvidedPath,
1093 preference,
1094 ))
1095 }
1096 })),
1097 PythonRequest::ExecutableName(name) => {
1098 if preference.allows_source(PythonSource::SearchPath) {
1099 debug!("Searching for Python interpreter with {request}");
1100 Box::new(
1101 python_installations_with_executable_name(name, cache)
1102 .filter_ok(move |installation| {
1103 interpreter_satisfies_environment_preference(
1104 installation.source,
1105 &installation.interpreter,
1106 environments,
1107 )
1108 })
1109 .map_ok(Ok),
1110 )
1111 } else {
1112 Box::new(iter::once(Err(Error::SourceNotAllowed(
1113 request.clone(),
1114 PythonSource::SearchPath,
1115 preference,
1116 ))))
1117 }
1118 }
1119 PythonRequest::Any => Box::new({
1120 debug!("Searching for any Python interpreter in {sources}");
1121 python_installations(
1122 &VersionRequest::Any,
1123 None,
1124 PlatformRequest::default(),
1125 environments,
1126 preference,
1127 cache,
1128 )
1129 .map_ok(Ok)
1130 }),
1131 PythonRequest::Default => Box::new({
1132 debug!("Searching for default Python interpreter in {sources}");
1133 python_installations(
1134 &VersionRequest::Default,
1135 None,
1136 PlatformRequest::default(),
1137 environments,
1138 preference,
1139 cache,
1140 )
1141 .map_ok(Ok)
1142 }),
1143 PythonRequest::Version(version) => {
1144 if let Err(err) = version.check_supported() {
1145 return Box::new(iter::once(Err(Error::InvalidVersionRequest(err))));
1146 }
1147 Box::new({
1148 debug!("Searching for {request} in {sources}");
1149 python_installations(
1150 version,
1151 None,
1152 PlatformRequest::default(),
1153 environments,
1154 preference,
1155 cache,
1156 )
1157 .map_ok(Ok)
1158 })
1159 }
1160 PythonRequest::Implementation(implementation) => Box::new({
1161 debug!("Searching for a {request} interpreter in {sources}");
1162 python_installations(
1163 &VersionRequest::Default,
1164 Some(implementation),
1165 PlatformRequest::default(),
1166 environments,
1167 preference,
1168 cache,
1169 )
1170 .filter_ok(|installation| implementation.matches_interpreter(&installation.interpreter))
1171 .map_ok(Ok)
1172 }),
1173 PythonRequest::ImplementationVersion(implementation, version) => {
1174 if let Err(err) = version.check_supported() {
1175 return Box::new(iter::once(Err(Error::InvalidVersionRequest(err))));
1176 }
1177 Box::new({
1178 debug!("Searching for {request} in {sources}");
1179 python_installations(
1180 version,
1181 Some(implementation),
1182 PlatformRequest::default(),
1183 environments,
1184 preference,
1185 cache,
1186 )
1187 .filter_ok(|installation| {
1188 implementation.matches_interpreter(&installation.interpreter)
1189 })
1190 .map_ok(Ok)
1191 })
1192 }
1193 PythonRequest::Key(request) => {
1194 if let Some(version) = request.version() {
1195 if let Err(err) = version.check_supported() {
1196 return Box::new(iter::once(Err(Error::InvalidVersionRequest(err))));
1197 }
1198 }
1199
1200 Box::new({
1201 debug!("Searching for {request} in {sources}");
1202 python_installations(
1203 request.version().unwrap_or(&VersionRequest::Default),
1204 request.implementation(),
1205 request.platform(),
1206 environments,
1207 preference,
1208 cache,
1209 )
1210 .filter_ok(move |installation| {
1211 request.satisfied_by_interpreter(&installation.interpreter)
1212 })
1213 .map_ok(Ok)
1214 })
1215 }
1216 }
1217}
1218
1219pub(crate) fn find_python_installation(
1224 request: &PythonRequest,
1225 environments: EnvironmentPreference,
1226 preference: PythonPreference,
1227 cache: &Cache,
1228) -> Result<FindPythonResult, Error> {
1229 let installations = find_python_installations(request, environments, preference, cache);
1230 let mut first_prerelease = None;
1231 let mut first_debug = None;
1232 let mut first_managed = None;
1233 let mut first_error = None;
1234 for result in installations {
1235 if !result.as_ref().err().is_none_or(Error::is_critical) {
1237 if first_error.is_none() {
1239 if let Err(err) = result {
1240 first_error = Some(err);
1241 }
1242 }
1243 continue;
1244 }
1245
1246 let Ok(Ok(ref installation)) = result else {
1248 return result;
1249 };
1250
1251 let has_default_executable_name = installation.interpreter.has_default_executable_name()
1257 && matches!(
1258 installation.source,
1259 PythonSource::SearchPath | PythonSource::SearchPathFirst
1260 );
1261
1262 if installation.python_version().pre().is_some()
1265 && !request.allows_prereleases()
1266 && !installation.source.allows_prereleases()
1267 && !has_default_executable_name
1268 {
1269 debug!("Skipping pre-release installation {}", installation.key());
1270 if first_prerelease.is_none() {
1271 first_prerelease = Some(installation.clone());
1272 }
1273 continue;
1274 }
1275
1276 if installation.key().variant().is_debug()
1279 && !request.allows_debug()
1280 && !installation.source.allows_debug()
1281 && !has_default_executable_name
1282 {
1283 debug!("Skipping debug installation {}", installation.key());
1284 if first_debug.is_none() {
1285 first_debug = Some(installation.clone());
1286 }
1287 continue;
1288 }
1289
1290 if installation.is_alternative_implementation()
1295 && !request.allows_alternative_implementations()
1296 && !installation.source.allows_alternative_implementations()
1297 && !has_default_executable_name
1298 {
1299 debug!("Skipping alternative implementation {}", installation.key());
1300 continue;
1301 }
1302
1303 if matches!(preference, PythonPreference::System) && installation.is_managed() {
1306 debug!(
1307 "Skipping managed installation {}: system installation preferred",
1308 installation.key()
1309 );
1310 if first_managed.is_none() {
1311 first_managed = Some(installation.clone());
1312 }
1313 continue;
1314 }
1315
1316 return result;
1318 }
1319
1320 if let Some(installation) = first_managed {
1323 debug!(
1324 "Allowing managed installation {}: no system installations",
1325 installation.key()
1326 );
1327 return Ok(Ok(installation));
1328 }
1329
1330 if let Some(installation) = first_debug {
1333 debug!(
1334 "Allowing debug installation {}: no non-debug installations",
1335 installation.key()
1336 );
1337 return Ok(Ok(installation));
1338 }
1339
1340 if let Some(installation) = first_prerelease {
1342 debug!(
1343 "Allowing pre-release installation {}: no stable installations",
1344 installation.key()
1345 );
1346 return Ok(Ok(installation));
1347 }
1348
1349 if let Some(err) = first_error {
1352 return Err(err);
1353 }
1354
1355 Ok(Err(PythonNotFound {
1356 request: request.clone(),
1357 environment_preference: environments,
1358 python_preference: preference,
1359 }))
1360}
1361
1362#[instrument(skip_all, fields(request))]
1376pub(crate) async fn find_best_python_installation(
1377 request: &PythonRequest,
1378 environments: EnvironmentPreference,
1379 preference: PythonPreference,
1380 downloads_enabled: bool,
1381 client_builder: &BaseClientBuilder<'_>,
1382 cache: &Cache,
1383 reporter: Option<&dyn crate::downloads::Reporter>,
1384 python_install_mirror: Option<&str>,
1385 pypy_install_mirror: Option<&str>,
1386 python_downloads_json_url: Option<&str>,
1387) -> Result<PythonInstallation, crate::Error> {
1388 debug!("Starting Python discovery for {request}");
1389 let original_request = request;
1390
1391 let mut previous_fetch_failed = false;
1392 let mut download_state = None;
1393
1394 let request_without_patch = match request {
1395 PythonRequest::Version(version) => {
1396 if version.has_patch() {
1397 Some(PythonRequest::Version(version.clone().without_patch()))
1398 } else {
1399 None
1400 }
1401 }
1402 PythonRequest::ImplementationVersion(implementation, version) => Some(
1403 PythonRequest::ImplementationVersion(*implementation, version.clone().without_patch()),
1404 ),
1405 _ => None,
1406 };
1407
1408 for (attempt, request) in iter::once(original_request)
1409 .chain(request_without_patch.iter())
1410 .chain(iter::once(&PythonRequest::Default))
1411 .enumerate()
1412 {
1413 debug!(
1414 "Looking for {request}{}",
1415 if request != original_request {
1416 format!(" attempt {attempt} (fallback after failing to find: {original_request})")
1417 } else {
1418 String::new()
1419 }
1420 );
1421 let result = find_python_installation(request, environments, preference, cache);
1422 let error = match result {
1423 Ok(Ok(installation)) => {
1424 warn_on_unsupported_python(installation.interpreter());
1425 return Ok(installation);
1426 }
1427 Ok(Err(error)) => error.into(),
1429 Err(error) if !error.is_critical() => error.into(),
1430 Err(error) => return Err(error.into()),
1431 };
1432
1433 if downloads_enabled
1435 && !previous_fetch_failed
1436 && let Some(download_request) = PythonDownloadRequest::from_request(request)
1437 {
1438 let (client, retry_policy, download_list) =
1439 if let Some(download_state) = &mut download_state {
1440 download_state
1441 } else {
1442 let download_list_client = client_builder.build()?;
1443 let download_list = ManagedPythonDownloadList::new(
1444 &download_list_client,
1445 python_downloads_json_url,
1446 )
1447 .await?;
1448 let retry_policy = client_builder.retry_policy();
1449
1450 let client = client_builder.clone().retries(0).build()?;
1453 download_state.insert((client, retry_policy, download_list))
1454 };
1455
1456 let download = download_request
1457 .clone()
1458 .fill()
1459 .map(|request| download_list.find(&request));
1460
1461 let result = match download {
1462 Ok(Ok(download)) => PythonInstallation::fetch(
1463 download,
1464 client,
1465 retry_policy,
1466 cache,
1467 reporter,
1468 python_install_mirror,
1469 pypy_install_mirror,
1470 )
1471 .await
1472 .map(Some),
1473 Ok(Err(crate::downloads::Error::NoDownloadFound(_))) => Ok(None),
1474 Ok(Err(error)) => Err(error.into()),
1475 Err(error) => Err(error.into()),
1476 };
1477 if let Ok(Some(installation)) = result {
1478 return Ok(installation);
1479 }
1480 if let Err(error) = result {
1488 if matches!(request, PythonRequest::Default | PythonRequest::Any) {
1492 return Err(error);
1493 }
1494
1495 let error = anyhow::Error::from(error).context(format!(
1496 "A managed Python download is available for {request}, but an error occurred when attempting to download it."
1497 ));
1498 write_warning_chain(error.as_ref()).expect("writing to stderr should not fail");
1499 previous_fetch_failed = true;
1500 }
1501 }
1502
1503 if matches!(request, PythonRequest::Default | PythonRequest::Any) {
1509 return Err(match error {
1510 crate::Error::MissingPython(err, _) => PythonNotFound {
1511 request: original_request.clone(),
1513 python_preference: err.python_preference,
1514 environment_preference: err.environment_preference,
1515 }
1516 .into(),
1517 other => other,
1518 });
1519 }
1520 }
1521
1522 unreachable!("The loop should have terminated when it reached PythonRequest::Default");
1523}
1524
1525fn warn_on_unsupported_python(interpreter: &Interpreter) {
1527 if interpreter.python_tuple() < (3, 8) {
1529 warn_user_once!(
1530 "uv is only compatible with Python >=3.8, found Python {}",
1531 interpreter.python_version()
1532 );
1533 }
1534}
1535
1536#[cfg(windows)]
1553fn is_windows_store_shim(path: &Path) -> bool {
1554 use std::os::windows::fs::MetadataExt;
1555 use std::os::windows::prelude::OsStrExt;
1556 use windows::Win32::Foundation::CloseHandle;
1557 use windows::Win32::Storage::FileSystem::{
1558 CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS,
1559 FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_MODE, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
1560 OPEN_EXISTING,
1561 };
1562 use windows::Win32::System::IO::DeviceIoControl;
1563 use windows::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT;
1564 use windows::core::PCWSTR;
1565
1566 if !path.is_absolute() {
1568 return false;
1569 }
1570
1571 let mut components = path.components().rev();
1574
1575 if !components
1577 .next()
1578 .and_then(|component| component.as_os_str().to_str())
1579 .is_some_and(|component| {
1580 component.starts_with("python")
1581 && std::path::Path::new(component)
1582 .extension()
1583 .is_some_and(|ext| ext.eq_ignore_ascii_case("exe"))
1584 })
1585 {
1586 return false;
1587 }
1588
1589 if components
1591 .next()
1592 .is_none_or(|component| component.as_os_str() != "WindowsApps")
1593 {
1594 return false;
1595 }
1596
1597 if components
1599 .next()
1600 .is_none_or(|component| component.as_os_str() != "Microsoft")
1601 {
1602 return false;
1603 }
1604
1605 let Ok(md) = fs_err::symlink_metadata(path) else {
1607 return false;
1608 };
1609 if md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT.0 == 0 {
1610 return false;
1611 }
1612
1613 let mut path_encoded = path
1614 .as_os_str()
1615 .encode_wide()
1616 .chain(std::iter::once(0))
1617 .collect::<Vec<_>>();
1618
1619 #[allow(unsafe_code)]
1621 let reparse_handle = unsafe {
1622 CreateFileW(
1623 PCWSTR(path_encoded.as_mut_ptr()),
1624 0,
1625 FILE_SHARE_MODE(0),
1626 None,
1627 OPEN_EXISTING,
1628 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
1629 None,
1630 )
1631 };
1632
1633 let Ok(reparse_handle) = reparse_handle else {
1634 return false;
1635 };
1636
1637 let mut buf = [0u16; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize];
1638 let mut bytes_returned = 0;
1639
1640 #[allow(unsafe_code, clippy::cast_possible_truncation)]
1642 let success = unsafe {
1643 DeviceIoControl(
1644 reparse_handle,
1645 FSCTL_GET_REPARSE_POINT,
1646 None,
1647 0,
1648 Some(buf.as_mut_ptr().cast()),
1649 buf.len() as u32 * 2,
1650 Some(&raw mut bytes_returned),
1651 None,
1652 )
1653 .is_ok()
1654 };
1655
1656 #[allow(unsafe_code)]
1658 unsafe {
1659 let _ = CloseHandle(reparse_handle);
1660 }
1661
1662 if !success {
1664 return false;
1665 }
1666
1667 let reparse_point = String::from_utf16_lossy(&buf[..bytes_returned as usize]);
1668 reparse_point.contains("\\AppInstallerPythonRedirector.exe")
1669}
1670
1671#[cfg(not(windows))]
1675fn is_windows_store_shim(_path: &Path) -> bool {
1676 false
1677}
1678
1679impl PythonVariant {
1680 fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
1681 match self {
1682 Self::Default => {
1683 if (interpreter.python_major(), interpreter.python_minor()) >= (3, 14) {
1686 true
1689 } else {
1690 !interpreter.gil_disabled()
1693 }
1694 }
1695 Self::Debug => interpreter.debug_enabled(),
1696 Self::Freethreaded => interpreter.gil_disabled(),
1697 Self::FreethreadedDebug => interpreter.gil_disabled() && interpreter.debug_enabled(),
1698 Self::Gil => !interpreter.gil_disabled(),
1699 Self::GilDebug => !interpreter.gil_disabled() && interpreter.debug_enabled(),
1700 }
1701 }
1702
1703 pub fn executable_suffix(self) -> &'static str {
1707 match self {
1708 Self::Default => "",
1709 Self::Debug => "d",
1710 Self::Freethreaded => "t",
1711 Self::FreethreadedDebug => "td",
1712 Self::Gil => "",
1713 Self::GilDebug => "d",
1714 }
1715 }
1716
1717 pub fn display_suffix(self) -> &'static str {
1719 match self {
1720 Self::Default => "",
1721 Self::Debug => "+debug",
1722 Self::Freethreaded => "+freethreaded",
1723 Self::FreethreadedDebug => "+freethreaded+debug",
1724 Self::Gil => "+gil",
1725 Self::GilDebug => "+gil+debug",
1726 }
1727 }
1728
1729 pub(crate) fn lib_suffix(self) -> &'static str {
1732 match self {
1733 Self::Default | Self::Debug | Self::Gil | Self::GilDebug => "",
1734 Self::Freethreaded | Self::FreethreadedDebug => "t",
1735 }
1736 }
1737
1738 fn is_freethreaded(self) -> bool {
1739 match self {
1740 Self::Default | Self::Debug | Self::Gil | Self::GilDebug => false,
1741 Self::Freethreaded | Self::FreethreadedDebug => true,
1742 }
1743 }
1744
1745 pub fn is_debug(self) -> bool {
1746 match self {
1747 Self::Default | Self::Freethreaded | Self::Gil => false,
1748 Self::Debug | Self::FreethreadedDebug | Self::GilDebug => true,
1749 }
1750 }
1751}
1752impl PythonRequest {
1753 pub fn from_requires_python(requires_python: &RequiresPython) -> Option<Self> {
1755 let specifiers = requires_python.specifiers().clone();
1756 if specifiers.is_empty() {
1757 return None;
1758 }
1759
1760 Some(Self::Version(VersionRequest::from_specifiers(
1761 specifiers,
1762 PythonVariant::Default,
1763 )))
1764 }
1765
1766 pub fn parse(value: &str) -> Self {
1774 let lowercase_value = &value.to_ascii_lowercase();
1775
1776 if lowercase_value == "any" {
1778 return Self::Any;
1779 }
1780 if lowercase_value == "default" {
1781 return Self::Default;
1782 }
1783
1784 let abstract_version_prefixes = ["python", ""];
1786 let all_implementation_names =
1787 ImplementationName::long_names().chain(ImplementationName::short_names());
1788 if let Ok(Some(request)) = Self::parse_versions_and_implementations(
1791 abstract_version_prefixes,
1792 all_implementation_names,
1793 lowercase_value,
1794 ) {
1795 return request;
1796 }
1797
1798 let value_as_path = PathBuf::from(value);
1799 if value_as_path.is_dir() {
1801 return Self::Directory(value_as_path);
1802 }
1803 if value_as_path.is_file() {
1805 return Self::File(value_as_path);
1806 }
1807
1808 #[cfg(windows)]
1810 if value_as_path.extension().is_none() {
1811 let value_as_path = value_as_path.with_extension(EXE_SUFFIX);
1812 if value_as_path.is_file() {
1813 return Self::File(value_as_path);
1814 }
1815 }
1816
1817 #[cfg(test)]
1822 if value_as_path.is_relative() {
1823 if let Ok(current_dir) = crate::current_dir() {
1824 let relative = current_dir.join(&value_as_path);
1825 if relative.is_dir() {
1826 return Self::Directory(relative);
1827 }
1828 if relative.is_file() {
1829 return Self::File(relative);
1830 }
1831 }
1832 }
1833 if value.contains(std::path::MAIN_SEPARATOR) {
1836 return Self::File(value_as_path);
1837 }
1838 if cfg!(windows) && value.contains('/') {
1841 return Self::File(value_as_path);
1842 }
1843 if let Ok(request) = PythonDownloadRequest::from_str(value) {
1844 return Self::Key(request);
1845 }
1846 Self::ExecutableName(value.to_string())
1849 }
1850
1851 pub fn try_from_tool_name(value: &str) -> Result<Option<Self>, Error> {
1865 let lowercase_value = &value.to_ascii_lowercase();
1866 let abstract_version_prefixes = if cfg!(windows) {
1868 &["python", "pythonw"][..]
1869 } else {
1870 &["python"][..]
1871 };
1872 if abstract_version_prefixes.contains(&lowercase_value.as_str()) {
1874 return Ok(Some(Self::Default));
1875 }
1876 Self::parse_versions_and_implementations(
1877 abstract_version_prefixes.iter().copied(),
1878 ImplementationName::long_names(),
1879 lowercase_value,
1880 )
1881 }
1882
1883 fn parse_versions_and_implementations<'a>(
1892 abstract_version_prefixes: impl IntoIterator<Item = &'a str>,
1894 implementation_names: impl IntoIterator<Item = &'a str>,
1896 lowercase_value: &str,
1898 ) -> Result<Option<Self>, Error> {
1899 for prefix in abstract_version_prefixes {
1900 if let Some(version_request) =
1901 Self::try_split_prefix_and_version(prefix, lowercase_value)?
1902 {
1903 return Ok(Some(Self::Version(version_request)));
1907 }
1908 }
1909 for implementation in implementation_names {
1910 if lowercase_value == implementation {
1911 return Ok(Some(Self::Implementation(
1912 ImplementationName::from_str(implementation).unwrap(),
1915 )));
1916 }
1917 if let Some(version_request) =
1918 Self::try_split_prefix_and_version(implementation, lowercase_value)?
1919 {
1920 return Ok(Some(Self::ImplementationVersion(
1922 ImplementationName::from_str(implementation).unwrap(),
1924 version_request,
1925 )));
1926 }
1927 }
1928 Ok(None)
1929 }
1930
1931 fn try_split_prefix_and_version(
1942 prefix: &str,
1943 lowercase_value: &str,
1944 ) -> Result<Option<VersionRequest>, Error> {
1945 if lowercase_value.starts_with('@') {
1946 return Err(Error::InvalidVersionRequest(lowercase_value.to_string()));
1947 }
1948 let Some(rest) = lowercase_value.strip_prefix(prefix) else {
1949 return Ok(None);
1950 };
1951 if rest.is_empty() {
1953 return Ok(None);
1954 }
1955 if let Some(after_at) = rest.strip_prefix('@') {
1958 if after_at == "latest" {
1959 return Err(Error::LatestVersionRequest);
1962 }
1963 return after_at.parse().map(Some);
1964 }
1965 Ok(rest.parse().ok())
1968 }
1969
1970 pub fn includes_patch(&self) -> bool {
1972 match self {
1973 Self::Default => false,
1974 Self::Any => false,
1975 Self::Version(version_request) => version_request.patch().is_some(),
1976 Self::Directory(..) => false,
1977 Self::File(..) => false,
1978 Self::ExecutableName(..) => false,
1979 Self::Implementation(..) => false,
1980 Self::ImplementationVersion(_, version) => version.patch().is_some(),
1981 Self::Key(request) => request
1982 .version
1983 .as_ref()
1984 .is_some_and(|request| request.patch().is_some()),
1985 }
1986 }
1987
1988 pub fn includes_prerelease(&self) -> bool {
1990 match self {
1991 Self::Default => false,
1992 Self::Any => false,
1993 Self::Version(version_request) => version_request.prerelease().is_some(),
1994 Self::Directory(..) => false,
1995 Self::File(..) => false,
1996 Self::ExecutableName(..) => false,
1997 Self::Implementation(..) => false,
1998 Self::ImplementationVersion(_, version) => version.prerelease().is_some(),
1999 Self::Key(request) => request
2000 .version
2001 .as_ref()
2002 .is_some_and(|request| request.prerelease().is_some()),
2003 }
2004 }
2005
2006 pub fn satisfied(&self, interpreter: &Interpreter, cache: &Cache) -> bool {
2008 fn is_same_executable(path1: &Path, path2: &Path) -> bool {
2010 path1 == path2 || is_same_file(path1, path2).unwrap_or(false)
2011 }
2012
2013 match self {
2014 Self::Default | Self::Any => true,
2015 Self::Version(version_request) => version_request.matches_interpreter(interpreter),
2016 Self::Directory(directory) => {
2017 is_same_executable(directory, interpreter.sys_prefix())
2019 || is_same_executable(
2020 virtualenv_python_executable(directory).as_path(),
2021 interpreter.sys_executable(),
2022 )
2023 }
2024 Self::File(file) => {
2025 if is_same_executable(interpreter.sys_executable(), file) {
2027 return true;
2028 }
2029 if interpreter
2031 .sys_base_executable()
2032 .is_some_and(|sys_base_executable| {
2033 is_same_executable(sys_base_executable, file)
2034 })
2035 {
2036 return true;
2037 }
2038 if cfg!(windows) {
2043 if let Ok(file_interpreter) = Interpreter::query(file, cache) {
2044 if let (Some(file_base), Some(interpreter_base)) = (
2045 file_interpreter.sys_base_executable(),
2046 interpreter.sys_base_executable(),
2047 ) {
2048 if is_same_executable(file_base, interpreter_base) {
2049 return true;
2050 }
2051 }
2052 }
2053 }
2054 false
2055 }
2056 Self::ExecutableName(name) => {
2057 if interpreter
2059 .sys_executable()
2060 .file_name()
2061 .is_some_and(|filename| filename == name.as_str())
2062 {
2063 return true;
2064 }
2065 if interpreter
2067 .sys_base_executable()
2068 .and_then(|executable| executable.file_name())
2069 .is_some_and(|file_name| file_name == name.as_str())
2070 {
2071 return true;
2072 }
2073 if which(name)
2076 .ok()
2077 .as_ref()
2078 .and_then(|executable| executable.file_name())
2079 .is_some_and(|file_name| file_name == name.as_str())
2080 {
2081 return true;
2082 }
2083 false
2084 }
2085 Self::Implementation(implementation) => interpreter
2086 .implementation_name()
2087 .eq_ignore_ascii_case(implementation.into()),
2088 Self::ImplementationVersion(implementation, version) => {
2089 version.matches_interpreter(interpreter)
2090 && interpreter
2091 .implementation_name()
2092 .eq_ignore_ascii_case(implementation.into())
2093 }
2094 Self::Key(request) => request.satisfied_by_interpreter(interpreter),
2095 }
2096 }
2097
2098 pub(crate) fn allows_prereleases(&self) -> bool {
2100 match self {
2101 Self::Default => false,
2102 Self::Any => true,
2103 Self::Version(version) => version.allows_prereleases(),
2104 Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
2105 Self::Implementation(_) => false,
2106 Self::ImplementationVersion(_, _) => true,
2107 Self::Key(request) => request.allows_prereleases(),
2108 }
2109 }
2110
2111 fn allows_debug(&self) -> bool {
2113 match self {
2114 Self::Default => false,
2115 Self::Any => true,
2116 Self::Version(version) => version.is_debug(),
2117 Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
2118 Self::Implementation(_) => false,
2119 Self::ImplementationVersion(_, _) => true,
2120 Self::Key(request) => request.allows_debug(),
2121 }
2122 }
2123
2124 fn allows_alternative_implementations(&self) -> bool {
2126 match self {
2127 Self::Default => false,
2128 Self::Any => true,
2129 Self::Version(_) => false,
2130 Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
2131 Self::Implementation(implementation)
2132 | Self::ImplementationVersion(implementation, _) => {
2133 !matches!(implementation, ImplementationName::CPython)
2134 }
2135 Self::Key(request) => request.allows_alternative_implementations(),
2136 }
2137 }
2138
2139 pub(crate) fn is_explicit_system(&self) -> bool {
2140 matches!(self, Self::File(_) | Self::Directory(_))
2141 }
2142
2143 pub fn to_canonical_string(&self) -> String {
2147 match self {
2148 Self::Any => "any".to_string(),
2149 Self::Default => "default".to_string(),
2150 Self::Version(version) => version.to_string(),
2151 Self::Directory(path) => path.display().to_string(),
2152 Self::File(path) => path.display().to_string(),
2153 Self::ExecutableName(name) => name.clone(),
2154 Self::Implementation(implementation) => implementation.to_string(),
2155 Self::ImplementationVersion(implementation, version) => {
2156 format!("{implementation}@{version}")
2157 }
2158 Self::Key(request) => request.to_string(),
2159 }
2160 }
2161
2162 pub fn as_pep440_version(&self) -> Option<Version> {
2166 match self {
2167 Self::Version(v) | Self::ImplementationVersion(_, v) => v.as_pep440_version(),
2168 Self::Key(download_request) => download_request
2169 .version()
2170 .and_then(VersionRequest::as_pep440_version),
2171 Self::Default
2172 | Self::Any
2173 | Self::Directory(_)
2174 | Self::File(_)
2175 | Self::ExecutableName(_)
2176 | Self::Implementation(_) => None,
2177 }
2178 }
2179
2180 fn as_version_specifiers(&self) -> Option<VersionSpecifiers> {
2186 match self {
2187 Self::Version(version) | Self::ImplementationVersion(_, version) => {
2188 version.as_version_specifiers()
2189 }
2190 Self::Key(download_request) => download_request
2191 .version()
2192 .and_then(VersionRequest::as_version_specifiers),
2193 Self::Default
2194 | Self::Any
2195 | Self::Directory(_)
2196 | Self::File(_)
2197 | Self::ExecutableName(_)
2198 | Self::Implementation(_) => None,
2199 }
2200 }
2201
2202 pub fn intersects_requires_python(&self, requires_python: &RequiresPython) -> bool {
2208 let Some(specifiers) = self.as_version_specifiers() else {
2209 return true;
2210 };
2211
2212 let request_range = release_specifiers_to_ranges(specifiers);
2213 let requires_python_range =
2214 release_specifiers_to_ranges(requires_python.specifiers().clone());
2215 !request_range
2216 .intersection(&requires_python_range)
2217 .is_empty()
2218 }
2219}
2220
2221impl PythonSource {
2222 pub fn is_managed(self) -> bool {
2223 matches!(self, Self::Managed)
2224 }
2225
2226 fn allows_prereleases(self) -> bool {
2228 match self {
2229 Self::Managed | Self::Registry | Self::MicrosoftStore => false,
2230 Self::SearchPath
2231 | Self::SearchPathFirst
2232 | Self::CondaPrefix
2233 | Self::BaseCondaPrefix
2234 | Self::ProvidedPath
2235 | Self::ParentInterpreter
2236 | Self::ActiveEnvironment
2237 | Self::DiscoveredEnvironment => true,
2238 }
2239 }
2240
2241 fn allows_debug(self) -> bool {
2243 match self {
2244 Self::Managed | Self::Registry | Self::MicrosoftStore => false,
2245 Self::SearchPath
2246 | Self::SearchPathFirst
2247 | Self::CondaPrefix
2248 | Self::BaseCondaPrefix
2249 | Self::ProvidedPath
2250 | Self::ParentInterpreter
2251 | Self::ActiveEnvironment
2252 | Self::DiscoveredEnvironment => true,
2253 }
2254 }
2255
2256 fn allows_alternative_implementations(self) -> bool {
2258 match self {
2259 Self::Managed
2260 | Self::Registry
2261 | Self::SearchPath
2262 | Self::SearchPathFirst
2265 | Self::MicrosoftStore => false,
2266 Self::CondaPrefix
2267 | Self::BaseCondaPrefix
2268 | Self::ProvidedPath
2269 | Self::ParentInterpreter
2270 | Self::ActiveEnvironment
2271 | Self::DiscoveredEnvironment => true,
2272 }
2273 }
2274
2275 fn is_maybe_virtualenv(self) -> bool {
2287 match self {
2288 Self::ProvidedPath
2289 | Self::ActiveEnvironment
2290 | Self::DiscoveredEnvironment
2291 | Self::CondaPrefix
2292 | Self::BaseCondaPrefix
2293 | Self::ParentInterpreter
2294 | Self::SearchPathFirst => true,
2295 Self::Managed | Self::SearchPath | Self::Registry | Self::MicrosoftStore => false,
2296 }
2297 }
2298
2299 fn is_explicit(self) -> bool {
2302 match self {
2303 Self::ProvidedPath
2304 | Self::ParentInterpreter
2305 | Self::ActiveEnvironment
2306 | Self::CondaPrefix => true,
2307 Self::Managed
2308 | Self::DiscoveredEnvironment
2309 | Self::SearchPath
2310 | Self::SearchPathFirst
2311 | Self::Registry
2312 | Self::MicrosoftStore
2313 | Self::BaseCondaPrefix => false,
2314 }
2315 }
2316
2317 fn is_maybe_system(self) -> bool {
2319 match self {
2320 Self::CondaPrefix
2321 | Self::BaseCondaPrefix
2322 | Self::ParentInterpreter
2323 | Self::ProvidedPath
2324 | Self::Managed
2325 | Self::SearchPath
2326 | Self::SearchPathFirst
2327 | Self::Registry
2328 | Self::MicrosoftStore => true,
2329 Self::ActiveEnvironment | Self::DiscoveredEnvironment => false,
2330 }
2331 }
2332}
2333
2334impl PythonPreference {
2335 fn allows_source(self, source: PythonSource) -> bool {
2336 if !matches!(
2338 source,
2339 PythonSource::Managed | PythonSource::SearchPath | PythonSource::Registry
2340 ) {
2341 return true;
2342 }
2343
2344 match self {
2345 Self::OnlyManaged => matches!(source, PythonSource::Managed),
2346 Self::Managed | Self::System => matches!(
2347 source,
2348 PythonSource::Managed | PythonSource::SearchPath | PythonSource::Registry
2349 ),
2350 Self::OnlySystem => {
2351 matches!(source, PythonSource::SearchPath | PythonSource::Registry)
2352 }
2353 }
2354 }
2355
2356 pub(crate) fn allows_managed(self) -> bool {
2357 match self {
2358 Self::OnlySystem => false,
2359 Self::Managed | Self::System | Self::OnlyManaged => true,
2360 }
2361 }
2362
2363 fn allows_interpreter(self, interpreter: &Interpreter) -> bool {
2368 match self {
2369 Self::OnlyManaged => interpreter.is_managed(),
2370 Self::OnlySystem => !interpreter.is_managed(),
2371 Self::Managed | Self::System => true,
2372 }
2373 }
2374
2375 pub fn allows_installation(self, installation: &PythonInstallation) -> bool {
2383 let source = installation.source;
2384 let interpreter = &installation.interpreter;
2385
2386 match self {
2387 Self::OnlyManaged => {
2388 if self.allows_interpreter(interpreter) {
2389 true
2390 } else if source.is_explicit() {
2391 debug!(
2392 "Allowing unmanaged Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
2393 interpreter.sys_executable().display()
2394 );
2395 true
2396 } else {
2397 debug!(
2398 "Ignoring Python interpreter at `{}`: only managed interpreters allowed",
2399 interpreter.sys_executable().display()
2400 );
2401 false
2402 }
2403 }
2404 Self::Managed | Self::System => true,
2406 Self::OnlySystem => {
2407 if self.allows_interpreter(interpreter) {
2408 true
2409 } else if source.is_explicit() {
2410 debug!(
2411 "Allowing managed Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
2412 interpreter.sys_executable().display()
2413 );
2414 true
2415 } else {
2416 debug!(
2417 "Ignoring Python interpreter at `{}`: only system interpreters allowed",
2418 interpreter.sys_executable().display()
2419 );
2420 false
2421 }
2422 }
2423 }
2424 }
2425
2426 #[must_use]
2431 pub fn with_system_flag(self, system: bool) -> Self {
2432 match self {
2433 Self::OnlyManaged => self,
2438 Self::Managed => {
2439 if system {
2440 Self::System
2441 } else {
2442 self
2443 }
2444 }
2445 Self::System => self,
2446 Self::OnlySystem => self,
2447 }
2448 }
2449}
2450
2451impl PythonDownloads {
2452 pub fn is_automatic(self) -> bool {
2453 matches!(self, Self::Automatic)
2454 }
2455}
2456
2457impl EnvironmentPreference {
2458 pub fn from_system_flag(system: bool, mutable: bool) -> Self {
2459 match (system, mutable) {
2460 (true, _) => Self::OnlySystem,
2462 (false, true) => Self::ExplicitSystem,
2464 (false, false) => Self::Any,
2466 }
2467 }
2468}
2469
2470#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
2471pub(crate) struct ExecutableName {
2472 implementation: Option<ImplementationName>,
2473 major: Option<u8>,
2474 minor: Option<u8>,
2475 patch: Option<u8>,
2476 prerelease: Option<Prerelease>,
2477 variant: PythonVariant,
2478}
2479
2480#[derive(Debug, Clone, PartialEq, Eq)]
2481struct ExecutableNameComparator<'a> {
2482 name: ExecutableName,
2483 request: &'a VersionRequest,
2484 implementation: Option<&'a ImplementationName>,
2485}
2486
2487impl Ord for ExecutableNameComparator<'_> {
2488 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2492 let name_ordering = if self.implementation.is_some() {
2495 std::cmp::Ordering::Greater
2496 } else {
2497 std::cmp::Ordering::Less
2498 };
2499 if self.name.implementation.is_none() && other.name.implementation.is_some() {
2500 return name_ordering.reverse();
2501 }
2502 if self.name.implementation.is_some() && other.name.implementation.is_none() {
2503 return name_ordering;
2504 }
2505 let ordering = self.name.implementation.cmp(&other.name.implementation);
2507 if ordering != std::cmp::Ordering::Equal {
2508 return ordering;
2509 }
2510 let ordering = self.name.major.cmp(&other.name.major);
2511 let is_default_request =
2512 matches!(self.request, VersionRequest::Any | VersionRequest::Default);
2513 if ordering != std::cmp::Ordering::Equal {
2514 return if is_default_request {
2515 ordering.reverse()
2516 } else {
2517 ordering
2518 };
2519 }
2520 let ordering = self.name.minor.cmp(&other.name.minor);
2521 if ordering != std::cmp::Ordering::Equal {
2522 return if is_default_request {
2523 ordering.reverse()
2524 } else {
2525 ordering
2526 };
2527 }
2528 let ordering = self.name.patch.cmp(&other.name.patch);
2529 if ordering != std::cmp::Ordering::Equal {
2530 return if is_default_request {
2531 ordering.reverse()
2532 } else {
2533 ordering
2534 };
2535 }
2536 let ordering = self.name.prerelease.cmp(&other.name.prerelease);
2537 if ordering != std::cmp::Ordering::Equal {
2538 return if is_default_request {
2539 ordering.reverse()
2540 } else {
2541 ordering
2542 };
2543 }
2544 let ordering = self.name.variant.cmp(&other.name.variant);
2545 if ordering != std::cmp::Ordering::Equal {
2546 return if is_default_request {
2547 ordering.reverse()
2548 } else {
2549 ordering
2550 };
2551 }
2552 ordering
2553 }
2554}
2555
2556impl PartialOrd for ExecutableNameComparator<'_> {
2557 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2558 Some(self.cmp(other))
2559 }
2560}
2561
2562impl ExecutableName {
2563 #[must_use]
2564 fn with_implementation(mut self, implementation: ImplementationName) -> Self {
2565 self.implementation = Some(implementation);
2566 self
2567 }
2568
2569 #[must_use]
2570 fn with_major(mut self, major: u8) -> Self {
2571 self.major = Some(major);
2572 self
2573 }
2574
2575 #[must_use]
2576 fn with_minor(mut self, minor: u8) -> Self {
2577 self.minor = Some(minor);
2578 self
2579 }
2580
2581 #[must_use]
2582 fn with_patch(mut self, patch: u8) -> Self {
2583 self.patch = Some(patch);
2584 self
2585 }
2586
2587 #[must_use]
2588 fn with_prerelease(mut self, prerelease: Prerelease) -> Self {
2589 self.prerelease = Some(prerelease);
2590 self
2591 }
2592
2593 #[must_use]
2594 fn with_variant(mut self, variant: PythonVariant) -> Self {
2595 self.variant = variant;
2596 self
2597 }
2598
2599 fn into_comparator<'a>(
2600 self,
2601 request: &'a VersionRequest,
2602 implementation: Option<&'a ImplementationName>,
2603 ) -> ExecutableNameComparator<'a> {
2604 ExecutableNameComparator {
2605 name: self,
2606 request,
2607 implementation,
2608 }
2609 }
2610}
2611
2612impl fmt::Display for ExecutableName {
2613 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2614 if let Some(implementation) = self.implementation {
2615 write!(f, "{implementation}")?;
2616 } else {
2617 f.write_str("python")?;
2618 }
2619 if let Some(major) = self.major {
2620 write!(f, "{major}")?;
2621 if let Some(minor) = self.minor {
2622 write!(f, ".{minor}")?;
2623 if let Some(patch) = self.patch {
2624 write!(f, ".{patch}")?;
2625 }
2626 }
2627 }
2628 if let Some(prerelease) = &self.prerelease {
2629 write!(f, "{prerelease}")?;
2630 }
2631 f.write_str(self.variant.executable_suffix())?;
2632 f.write_str(EXE_SUFFIX)?;
2633 Ok(())
2634 }
2635}
2636
2637impl VersionRequest {
2638 pub fn from_specifiers(specifiers: VersionSpecifiers, variant: PythonVariant) -> Self {
2643 if let [specifier] = specifiers.iter().as_slice() {
2644 if specifier.operator() == &uv_pep440::Operator::Equal {
2645 if let Ok(request) = Self::from_str(&specifier.version().to_string()) {
2646 return request;
2647 }
2648 }
2649 }
2650 Self::Range(specifiers, variant)
2651 }
2652
2653 #[must_use]
2655 pub fn only_minor(self) -> Self {
2656 match self {
2657 Self::Any => self,
2658 Self::Default => self,
2659 Self::Range(specifiers, variant) => Self::Range(
2660 specifiers
2661 .into_iter()
2662 .map(|s| s.only_minor_release())
2663 .collect(),
2664 variant,
2665 ),
2666 Self::Major(..) => self,
2667 Self::MajorMinor(..) => self,
2668 Self::MajorMinorPatch(major, minor, _, variant)
2669 | Self::MajorMinorPrerelease(major, minor, _, variant)
2670 | Self::MajorMinorPatchPrerelease(major, minor, _, _, variant) => {
2671 Self::MajorMinor(major, minor, variant)
2672 }
2673 }
2674 }
2675
2676 pub(crate) fn executable_names(
2678 &self,
2679 implementation: Option<&ImplementationName>,
2680 ) -> Vec<ExecutableName> {
2681 let prerelease = match self {
2682 Self::MajorMinorPrerelease(_, _, prerelease, _)
2683 | Self::MajorMinorPatchPrerelease(_, _, _, prerelease, _) => {
2684 Some(prerelease)
2686 }
2687 _ => None,
2688 };
2689
2690 let mut names = Vec::new();
2692 names.push(ExecutableName::default());
2693
2694 if let Some(major) = self.major() {
2696 names.push(ExecutableName::default().with_major(major));
2698 if let Some(minor) = self.minor() {
2699 names.push(
2701 ExecutableName::default()
2702 .with_major(major)
2703 .with_minor(minor),
2704 );
2705 if let Some(patch) = self.patch() {
2706 names.push(
2708 ExecutableName::default()
2709 .with_major(major)
2710 .with_minor(minor)
2711 .with_patch(patch),
2712 );
2713 }
2714 }
2715 } else {
2716 names.push(ExecutableName::default().with_major(3));
2718 }
2719
2720 if let Some(prerelease) = prerelease {
2721 for i in 0..names.len() {
2723 let name = names[i];
2724 if name.minor.is_none() {
2725 continue;
2728 }
2729 names.push(name.with_prerelease(*prerelease));
2730 }
2731 }
2732
2733 if let Some(implementation) = implementation {
2735 for i in 0..names.len() {
2736 let name = names[i].with_implementation(*implementation);
2737 names.push(name);
2738 }
2739 } else {
2740 if matches!(self, Self::Any) {
2742 for i in 0..names.len() {
2743 for implementation in ImplementationName::iter_all() {
2744 let name = names[i].with_implementation(implementation);
2745 names.push(name);
2746 }
2747 }
2748 }
2749 }
2750
2751 if let Some(variant) = self.variant() {
2753 if variant != PythonVariant::Default {
2754 for i in 0..names.len() {
2755 let name = names[i].with_variant(variant);
2756 names.push(name);
2757 }
2758 }
2759 }
2760
2761 names.sort_unstable_by_key(|name| name.into_comparator(self, implementation));
2762 names.reverse();
2763
2764 names
2765 }
2766
2767 fn major(&self) -> Option<u8> {
2769 match self {
2770 Self::Any | Self::Default | Self::Range(_, _) => None,
2771 Self::Major(major, _) => Some(*major),
2772 Self::MajorMinor(major, _, _) => Some(*major),
2773 Self::MajorMinorPatch(major, _, _, _) => Some(*major),
2774 Self::MajorMinorPrerelease(major, _, _, _) => Some(*major),
2775 Self::MajorMinorPatchPrerelease(major, _, _, _, _) => Some(*major),
2776 }
2777 }
2778
2779 fn minor(&self) -> Option<u8> {
2781 match self {
2782 Self::Any | Self::Default | Self::Range(_, _) => None,
2783 Self::Major(_, _) => None,
2784 Self::MajorMinor(_, minor, _) => Some(*minor),
2785 Self::MajorMinorPatch(_, minor, _, _) => Some(*minor),
2786 Self::MajorMinorPrerelease(_, minor, _, _) => Some(*minor),
2787 Self::MajorMinorPatchPrerelease(_, minor, _, _, _) => Some(*minor),
2788 }
2789 }
2790
2791 fn patch(&self) -> Option<u8> {
2793 match self {
2794 Self::Any | Self::Default | Self::Range(_, _) => None,
2795 Self::Major(_, _) => None,
2796 Self::MajorMinor(_, _, _) => None,
2797 Self::MajorMinorPatch(_, _, patch, _) => Some(*patch),
2798 Self::MajorMinorPrerelease(_, _, _, _) => None,
2799 Self::MajorMinorPatchPrerelease(_, _, patch, _, _) => Some(*patch),
2800 }
2801 }
2802
2803 fn prerelease(&self) -> Option<&Prerelease> {
2805 match self {
2806 Self::Any | Self::Default | Self::Range(_, _) => None,
2807 Self::Major(_, _) => None,
2808 Self::MajorMinor(_, _, _) => None,
2809 Self::MajorMinorPatch(_, _, _, _) => None,
2810 Self::MajorMinorPrerelease(_, _, prerelease, _) => Some(prerelease),
2811 Self::MajorMinorPatchPrerelease(_, _, _, prerelease, _) => Some(prerelease),
2812 }
2813 }
2814
2815 fn check_supported(&self) -> Result<(), String> {
2819 match self {
2820 Self::Any | Self::Default => (),
2821 Self::Major(major, _) => {
2822 if *major < 3 {
2823 return Err(format!(
2824 "Python <3 is not supported but {major} was requested."
2825 ));
2826 }
2827 }
2828 Self::MajorMinor(major, minor, _) => {
2829 if (*major, *minor) < (3, 6) {
2830 return Err(format!(
2831 "Python <3.6 is not supported but {major}.{minor} was requested."
2832 ));
2833 }
2834 }
2835 Self::MajorMinorPatch(major, minor, patch, _) => {
2836 if (*major, *minor) < (3, 6) {
2837 return Err(format!(
2838 "Python <3.6 is not supported but {major}.{minor}.{patch} was requested."
2839 ));
2840 }
2841 }
2842 Self::MajorMinorPrerelease(major, minor, prerelease, _) => {
2843 if (*major, *minor) < (3, 6) {
2844 return Err(format!(
2845 "Python <3.6 is not supported but {major}.{minor}{prerelease} was requested."
2846 ));
2847 }
2848 }
2849 Self::MajorMinorPatchPrerelease(major, minor, patch, prerelease, _) => {
2850 if (*major, *minor) < (3, 6) {
2851 return Err(format!(
2852 "Python <3.6 is not supported but {major}.{minor}.{patch}{prerelease} was requested."
2853 ));
2854 }
2855 }
2856 Self::Range(_, _) => (),
2858 }
2859
2860 if self.is_freethreaded() {
2861 if let Self::MajorMinor(major, minor, _) = self.clone().without_patch() {
2862 if (major, minor) < (3, 13) {
2863 return Err(format!(
2864 "Python <3.13 does not support free-threading but {self} was requested."
2865 ));
2866 }
2867 }
2868 }
2869
2870 Ok(())
2871 }
2872
2873 #[must_use]
2879 fn into_request_for_source(self, source: PythonSource) -> Self {
2880 match self {
2881 Self::Default => match source {
2882 PythonSource::ParentInterpreter
2883 | PythonSource::CondaPrefix
2884 | PythonSource::BaseCondaPrefix
2885 | PythonSource::ProvidedPath
2886 | PythonSource::DiscoveredEnvironment
2887 | PythonSource::ActiveEnvironment => Self::Any,
2888 PythonSource::SearchPath
2889 | PythonSource::SearchPathFirst
2890 | PythonSource::Registry
2891 | PythonSource::MicrosoftStore
2892 | PythonSource::Managed => Self::Default,
2893 },
2894 _ => self,
2895 }
2896 }
2897
2898 pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
2900 match self {
2901 Self::Any => true,
2902 Self::Default => PythonVariant::Default.matches_interpreter(interpreter),
2904 Self::Major(major, variant) => {
2905 interpreter.python_major() == *major && variant.matches_interpreter(interpreter)
2906 }
2907 Self::MajorMinor(major, minor, variant) => {
2908 (interpreter.python_major(), interpreter.python_minor()) == (*major, *minor)
2909 && variant.matches_interpreter(interpreter)
2910 }
2911 Self::MajorMinorPatch(major, minor, patch, variant) => {
2912 (
2913 interpreter.python_major(),
2914 interpreter.python_minor(),
2915 interpreter.python_patch(),
2916 ) == (*major, *minor, *patch)
2917 && interpreter.python_version().pre().is_none()
2920 && variant.matches_interpreter(interpreter)
2921 }
2922 Self::Range(specifiers, variant) => {
2923 let version = if specifiers
2926 .iter()
2927 .any(uv_pep440::VersionSpecifier::any_prerelease)
2928 {
2929 Cow::Borrowed(interpreter.python_version())
2930 } else {
2931 Cow::Owned(interpreter.python_version().only_release())
2932 };
2933 specifiers.contains(&version) && variant.matches_interpreter(interpreter)
2934 }
2935 Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
2936 let version = interpreter.python_version();
2937 let Some(interpreter_prerelease) = version.pre() else {
2938 return false;
2939 };
2940 (
2941 interpreter.python_major(),
2942 interpreter.python_minor(),
2943 interpreter_prerelease,
2944 ) == (*major, *minor, *prerelease)
2945 && variant.matches_interpreter(interpreter)
2946 }
2947 Self::MajorMinorPatchPrerelease(major, minor, patch, prerelease, variant) => {
2948 let version = interpreter.python_version();
2949 let Some(interpreter_prerelease) = version.pre() else {
2950 return false;
2951 };
2952 (
2953 interpreter.python_major(),
2954 interpreter.python_minor(),
2955 interpreter.python_patch(),
2956 interpreter_prerelease,
2957 ) == (*major, *minor, *patch, *prerelease)
2958 && variant.matches_interpreter(interpreter)
2959 }
2960 }
2961 }
2962
2963 fn matches_version(&self, version: &PythonVersion) -> bool {
2968 match self {
2969 Self::Any | Self::Default => true,
2970 Self::Major(major, _) => version.major() == *major,
2971 Self::MajorMinor(major, minor, _) => {
2972 (version.major(), version.minor()) == (*major, *minor)
2973 }
2974 Self::MajorMinorPatch(major, minor, patch, _) => {
2975 (version.major(), version.minor(), version.patch())
2976 == (*major, *minor, Some(*patch))
2977 }
2978 Self::Range(specifiers, _) => {
2979 let version = if specifiers
2982 .iter()
2983 .any(uv_pep440::VersionSpecifier::any_prerelease)
2984 {
2985 Cow::Borrowed(&version.version)
2986 } else {
2987 Cow::Owned(version.version.only_release())
2988 };
2989 specifiers.contains(&version)
2990 }
2991 Self::MajorMinorPrerelease(major, minor, prerelease, _) => {
2992 (version.major(), version.minor(), version.pre())
2993 == (*major, *minor, Some(*prerelease))
2994 }
2995 Self::MajorMinorPatchPrerelease(major, minor, patch, prerelease, _) => {
2996 (
2997 version.major(),
2998 version.minor(),
2999 version.patch(),
3000 version.pre(),
3001 ) == (*major, *minor, Some(*patch), Some(*prerelease))
3002 }
3003 }
3004 }
3005
3006 fn matches_major_minor(&self, major: u8, minor: u8) -> bool {
3011 match self {
3012 Self::Any | Self::Default => true,
3013 Self::Major(self_major, _) => *self_major == major,
3014 Self::MajorMinor(self_major, self_minor, _) => {
3015 (*self_major, *self_minor) == (major, minor)
3016 }
3017 Self::MajorMinorPatch(self_major, self_minor, _, _) => {
3018 (*self_major, *self_minor) == (major, minor)
3019 }
3020 Self::Range(specifiers, _) => {
3021 let range = release_specifiers_to_ranges(specifiers.clone());
3022 let Some((lower, upper)) = range.bounding_range() else {
3023 return true;
3024 };
3025 let version = Version::new([u64::from(major), u64::from(minor)]);
3026
3027 let lower = LowerBound::new(lower.cloned());
3028 if !lower.major_minor().contains(&version) {
3029 return false;
3030 }
3031
3032 let upper = UpperBound::new(upper.cloned());
3033 if !upper.major_minor().contains(&version) {
3034 return false;
3035 }
3036
3037 true
3038 }
3039 Self::MajorMinorPrerelease(self_major, self_minor, _, _) => {
3040 (*self_major, *self_minor) == (major, minor)
3041 }
3042 Self::MajorMinorPatchPrerelease(self_major, self_minor, _, _, _) => {
3043 (*self_major, *self_minor) == (major, minor)
3044 }
3045 }
3046 }
3047
3048 pub(crate) fn matches_major_minor_patch_prerelease(
3054 &self,
3055 major: u8,
3056 minor: u8,
3057 patch: u8,
3058 prerelease: Option<Prerelease>,
3059 ) -> bool {
3060 match self {
3061 Self::Any | Self::Default => true,
3062 Self::Major(self_major, _) => *self_major == major,
3063 Self::MajorMinor(self_major, self_minor, _) => {
3064 (*self_major, *self_minor) == (major, minor)
3065 }
3066 Self::MajorMinorPatch(self_major, self_minor, self_patch, _) => {
3067 (*self_major, *self_minor, *self_patch) == (major, minor, patch)
3068 && prerelease.is_none()
3071 }
3072 Self::Range(specifiers, _) => specifiers.contains(
3073 &Version::new([u64::from(major), u64::from(minor), u64::from(patch)])
3074 .with_pre(prerelease),
3075 ),
3076 Self::MajorMinorPrerelease(self_major, self_minor, self_prerelease, _) => {
3077 (*self_major, *self_minor, 0, Some(*self_prerelease))
3079 == (major, minor, patch, prerelease)
3080 }
3081 Self::MajorMinorPatchPrerelease(
3082 self_major,
3083 self_minor,
3084 self_patch,
3085 self_prerelease,
3086 _,
3087 ) => {
3088 (
3089 *self_major,
3090 *self_minor,
3091 *self_patch,
3092 Some(*self_prerelease),
3093 ) == (major, minor, patch, prerelease)
3094 }
3095 }
3096 }
3097
3098 pub(crate) fn matches_installation_key(&self, key: &PythonInstallationKey) -> bool {
3103 self.matches_major_minor_patch_prerelease(key.major, key.minor, key.patch, key.prerelease())
3104 }
3105
3106 fn has_patch(&self) -> bool {
3108 match self {
3109 Self::Any | Self::Default => false,
3110 Self::Major(..) => false,
3111 Self::MajorMinor(..) => false,
3112 Self::MajorMinorPatch(..) => true,
3113 Self::MajorMinorPrerelease(..) => false,
3114 Self::MajorMinorPatchPrerelease(..) => true,
3115 Self::Range(_, _) => false,
3116 }
3117 }
3118
3119 #[must_use]
3123 fn without_patch(self) -> Self {
3124 match self {
3125 Self::Default => Self::Default,
3126 Self::Any => Self::Any,
3127 Self::Major(major, variant) => Self::Major(major, variant),
3128 Self::MajorMinor(major, minor, variant) => Self::MajorMinor(major, minor, variant),
3129 Self::MajorMinorPatch(major, minor, _, variant) => {
3130 Self::MajorMinor(major, minor, variant)
3131 }
3132 Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
3133 Self::MajorMinorPrerelease(major, minor, prerelease, variant)
3134 }
3135 Self::MajorMinorPatchPrerelease(major, minor, _, prerelease, variant) => {
3136 Self::MajorMinorPrerelease(major, minor, prerelease, variant)
3137 }
3138 Self::Range(_, _) => self,
3139 }
3140 }
3141
3142 pub(crate) fn allows_prereleases(&self) -> bool {
3144 match self {
3145 Self::Default => false,
3146 Self::Any => true,
3147 Self::Major(..) => false,
3148 Self::MajorMinor(..) => false,
3149 Self::MajorMinorPatch(..) => false,
3150 Self::MajorMinorPrerelease(..) => true,
3151 Self::MajorMinorPatchPrerelease(..) => true,
3152 Self::Range(specifiers, _) => specifiers.iter().any(VersionSpecifier::any_prerelease),
3153 }
3154 }
3155
3156 pub(crate) fn is_debug(&self) -> bool {
3158 match self {
3159 Self::Any | Self::Default => false,
3160 Self::Major(_, variant)
3161 | Self::MajorMinor(_, _, variant)
3162 | Self::MajorMinorPatch(_, _, _, variant)
3163 | Self::MajorMinorPrerelease(_, _, _, variant)
3164 | Self::MajorMinorPatchPrerelease(_, _, _, _, variant)
3165 | Self::Range(_, variant) => variant.is_debug(),
3166 }
3167 }
3168
3169 fn is_freethreaded(&self) -> bool {
3171 match self {
3172 Self::Any | Self::Default => false,
3173 Self::Major(_, variant)
3174 | Self::MajorMinor(_, _, variant)
3175 | Self::MajorMinorPatch(_, _, _, variant)
3176 | Self::MajorMinorPrerelease(_, _, _, variant)
3177 | Self::MajorMinorPatchPrerelease(_, _, _, _, variant)
3178 | Self::Range(_, variant) => variant.is_freethreaded(),
3179 }
3180 }
3181
3182 pub(crate) fn variant(&self) -> Option<PythonVariant> {
3184 match self {
3185 Self::Any => None,
3186 Self::Default => Some(PythonVariant::Default),
3187 Self::Major(_, variant)
3188 | Self::MajorMinor(_, _, variant)
3189 | Self::MajorMinorPatch(_, _, _, variant)
3190 | Self::MajorMinorPrerelease(_, _, _, variant)
3191 | Self::MajorMinorPatchPrerelease(_, _, _, _, variant)
3192 | Self::Range(_, variant) => Some(*variant),
3193 }
3194 }
3195
3196 fn as_pep440_version(&self) -> Option<Version> {
3200 match self {
3201 Self::Default | Self::Any | Self::Range(_, _) => None,
3202 Self::Major(major, _) => Some(Version::new([u64::from(*major)])),
3203 Self::MajorMinor(major, minor, _) => {
3204 Some(Version::new([u64::from(*major), u64::from(*minor)]))
3205 }
3206 Self::MajorMinorPatch(major, minor, patch, _) => Some(Version::new([
3207 u64::from(*major),
3208 u64::from(*minor),
3209 u64::from(*patch),
3210 ])),
3211 Self::MajorMinorPrerelease(major, minor, prerelease, _) => Some(
3213 Version::new([u64::from(*major), u64::from(*minor), 0]).with_pre(Some(*prerelease)),
3214 ),
3215 Self::MajorMinorPatchPrerelease(major, minor, patch, prerelease, _) => Some(
3216 Version::new([u64::from(*major), u64::from(*minor), u64::from(*patch)])
3217 .with_pre(Some(*prerelease)),
3218 ),
3219 }
3220 }
3221
3222 fn as_version_specifiers(&self) -> Option<VersionSpecifiers> {
3228 match self {
3229 Self::Default | Self::Any => None,
3230 Self::Major(major, _) => Some(VersionSpecifiers::from(
3231 VersionSpecifier::equals_star_version(Version::new([u64::from(*major)])),
3232 )),
3233 Self::MajorMinor(major, minor, _) => Some(VersionSpecifiers::from(
3234 VersionSpecifier::equals_star_version(Version::new([
3235 u64::from(*major),
3236 u64::from(*minor),
3237 ])),
3238 )),
3239 Self::MajorMinorPatch(major, minor, patch, _) => {
3240 Some(VersionSpecifiers::from(VersionSpecifier::equals_version(
3241 Version::new([u64::from(*major), u64::from(*minor), u64::from(*patch)]),
3242 )))
3243 }
3244 Self::MajorMinorPrerelease(major, minor, prerelease, _) => {
3245 Some(VersionSpecifiers::from(VersionSpecifier::equals_version(
3246 Version::new([u64::from(*major), u64::from(*minor), 0])
3247 .with_pre(Some(*prerelease)),
3248 )))
3249 }
3250 Self::MajorMinorPatchPrerelease(major, minor, patch, prerelease, _) => {
3251 Some(VersionSpecifiers::from(VersionSpecifier::equals_version(
3252 Version::new([u64::from(*major), u64::from(*minor), u64::from(*patch)])
3253 .with_pre(Some(*prerelease)),
3254 )))
3255 }
3256 Self::Range(specifiers, _) => Some(specifiers.clone()),
3257 }
3258 }
3259}
3260
3261impl FromStr for VersionRequest {
3262 type Err = Error;
3263
3264 fn from_str(s: &str) -> Result<Self, Self::Err> {
3265 fn parse_variant(s: &str) -> Result<(&str, PythonVariant), Error> {
3268 if s.chars().all(char::is_alphabetic) {
3270 return Err(Error::InvalidVersionRequest(s.to_string()));
3271 }
3272
3273 let Some(mut start) = s.rfind(|c: char| c.is_numeric()) else {
3274 return Ok((s, PythonVariant::Default));
3275 };
3276
3277 start += 1;
3279
3280 if start + 1 > s.len() {
3282 return Ok((s, PythonVariant::Default));
3283 }
3284
3285 let variant = &s[start..];
3286 let prefix = &s[..start];
3287
3288 let variant = variant.strip_prefix('+').unwrap_or(variant);
3290
3291 let Ok(variant) = PythonVariant::from_str(variant) else {
3295 return Ok((s, PythonVariant::Default));
3296 };
3297
3298 Ok((prefix, variant))
3299 }
3300
3301 let (s, variant) = parse_variant(s)?;
3302 let Ok(version) = Version::from_str(s) else {
3303 return parse_version_specifiers_request(s, variant);
3304 };
3305
3306 let version = split_wheel_tag_release_version(version);
3308
3309 if version.post().is_some() || version.dev().is_some() {
3311 return Err(Error::InvalidVersionRequest(s.to_string()));
3312 }
3313
3314 if !version.local().is_empty() {
3317 return Err(Error::InvalidVersionRequest(s.to_string()));
3318 }
3319
3320 let Ok(release) = try_into_u8_slice(&version.release()) else {
3322 return Err(Error::InvalidVersionRequest(s.to_string()));
3323 };
3324
3325 let prerelease = version.pre();
3326
3327 match release.as_slice() {
3328 [major] => {
3330 if prerelease.is_some() {
3332 return Err(Error::InvalidVersionRequest(s.to_string()));
3333 }
3334 Ok(Self::Major(*major, variant))
3335 }
3336 [major, minor] => {
3338 if let Some(prerelease) = prerelease {
3339 return Ok(Self::MajorMinorPrerelease(
3340 *major, *minor, prerelease, variant,
3341 ));
3342 }
3343 Ok(Self::MajorMinor(*major, *minor, variant))
3344 }
3345 [major, minor, patch] => {
3347 if let Some(prerelease) = prerelease {
3348 if *patch == 0 {
3349 return Ok(Self::MajorMinorPrerelease(
3350 *major, *minor, prerelease, variant,
3351 ));
3352 }
3353 return Ok(Self::MajorMinorPatchPrerelease(
3354 *major, *minor, *patch, prerelease, variant,
3355 ));
3356 }
3357 Ok(Self::MajorMinorPatch(*major, *minor, *patch, variant))
3358 }
3359 _ => Err(Error::InvalidVersionRequest(s.to_string())),
3360 }
3361 }
3362}
3363
3364impl FromStr for PythonVariant {
3365 type Err = ();
3366
3367 fn from_str(s: &str) -> Result<Self, Self::Err> {
3368 match s {
3369 "t" | "freethreaded" => Ok(Self::Freethreaded),
3370 "d" | "debug" => Ok(Self::Debug),
3371 "td" | "freethreaded+debug" => Ok(Self::FreethreadedDebug),
3372 "gil" => Ok(Self::Gil),
3373 "gil+debug" => Ok(Self::GilDebug),
3374 "" => Ok(Self::Default),
3375 _ => Err(()),
3376 }
3377 }
3378}
3379
3380impl fmt::Display for PythonVariant {
3381 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3382 match self {
3383 Self::Default => f.write_str("default"),
3384 Self::Debug => f.write_str("debug"),
3385 Self::Freethreaded => f.write_str("freethreaded"),
3386 Self::FreethreadedDebug => f.write_str("freethreaded+debug"),
3387 Self::Gil => f.write_str("gil"),
3388 Self::GilDebug => f.write_str("gil+debug"),
3389 }
3390 }
3391}
3392
3393fn parse_version_specifiers_request(
3394 s: &str,
3395 variant: PythonVariant,
3396) -> Result<VersionRequest, Error> {
3397 let Ok(specifiers) = VersionSpecifiers::from_str(s) else {
3398 return Err(Error::InvalidVersionRequest(s.to_string()));
3399 };
3400 if specifiers.is_empty() {
3401 return Err(Error::InvalidVersionRequest(s.to_string()));
3402 }
3403 Ok(VersionRequest::from_specifiers(specifiers, variant))
3404}
3405
3406impl From<&PythonVersion> for VersionRequest {
3407 fn from(version: &PythonVersion) -> Self {
3408 Self::from_str(&version.string)
3409 .expect("Valid `PythonVersion`s should be valid `VersionRequest`s")
3410 }
3411}
3412
3413impl fmt::Display for VersionRequest {
3414 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3415 match self {
3416 Self::Any => f.write_str("any"),
3417 Self::Default => f.write_str("default"),
3418 Self::Major(major, variant) => write!(f, "{major}{}", variant.display_suffix()),
3419 Self::MajorMinor(major, minor, variant) => {
3420 write!(f, "{major}.{minor}{}", variant.display_suffix())
3421 }
3422 Self::MajorMinorPatch(major, minor, patch, variant) => {
3423 write!(f, "{major}.{minor}.{patch}{}", variant.display_suffix())
3424 }
3425 Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
3426 write!(f, "{major}.{minor}{prerelease}{}", variant.display_suffix())
3427 }
3428 Self::MajorMinorPatchPrerelease(major, minor, patch, prerelease, variant) => {
3429 write!(
3430 f,
3431 "{major}.{minor}.{patch}{prerelease}{}",
3432 variant.display_suffix()
3433 )
3434 }
3435 Self::Range(specifiers, _) => write!(f, "{specifiers}"),
3436 }
3437 }
3438}
3439
3440impl fmt::Display for PythonRequest {
3441 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3442 match self {
3443 Self::Default => write!(f, "a default Python"),
3444 Self::Any => write!(f, "any Python"),
3445 Self::Version(version) => write!(f, "Python {version}"),
3446 Self::Directory(path) => write!(f, "directory `{}`", path.user_display()),
3447 Self::File(path) => write!(f, "path `{}`", path.user_display()),
3448 Self::ExecutableName(name) => write!(f, "executable name `{name}`"),
3449 Self::Implementation(implementation) => {
3450 write!(f, "{}", implementation.pretty())
3451 }
3452 Self::ImplementationVersion(implementation, version) => {
3453 write!(f, "{} {version}", implementation.pretty())
3454 }
3455 Self::Key(request) => write!(f, "{request}"),
3456 }
3457 }
3458}
3459
3460impl fmt::Display for PythonSource {
3461 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3462 match self {
3463 Self::ProvidedPath => f.write_str("provided path"),
3464 Self::ActiveEnvironment => f.write_str("active virtual environment"),
3465 Self::CondaPrefix | Self::BaseCondaPrefix => f.write_str("conda prefix"),
3466 Self::DiscoveredEnvironment => f.write_str("virtual environment"),
3467 Self::SearchPath => f.write_str("search path"),
3468 Self::SearchPathFirst => f.write_str("first executable in the search path"),
3469 Self::Registry => f.write_str("registry"),
3470 Self::MicrosoftStore => f.write_str("Microsoft Store"),
3471 Self::Managed => f.write_str("managed installations"),
3472 Self::ParentInterpreter => f.write_str("parent interpreter"),
3473 }
3474 }
3475}
3476
3477impl PythonPreference {
3478 fn sources(self) -> &'static [PythonSource] {
3481 match self {
3482 Self::OnlyManaged => &[PythonSource::Managed],
3483 Self::Managed => {
3484 if cfg!(windows) {
3485 &[
3486 PythonSource::Managed,
3487 PythonSource::SearchPath,
3488 PythonSource::Registry,
3489 ]
3490 } else {
3491 &[PythonSource::Managed, PythonSource::SearchPath]
3492 }
3493 }
3494 Self::System => {
3495 if cfg!(windows) {
3496 &[
3497 PythonSource::SearchPath,
3498 PythonSource::Registry,
3499 PythonSource::Managed,
3500 ]
3501 } else {
3502 &[PythonSource::SearchPath, PythonSource::Managed]
3503 }
3504 }
3505 Self::OnlySystem => {
3506 if cfg!(windows) {
3507 &[PythonSource::SearchPath, PythonSource::Registry]
3508 } else {
3509 &[PythonSource::SearchPath]
3510 }
3511 }
3512 }
3513 }
3514}
3515
3516impl fmt::Display for PythonPreference {
3517 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3518 f.write_str(match self {
3519 Self::OnlyManaged => "only managed",
3520 Self::Managed => "prefer managed",
3521 Self::System => "prefer system",
3522 Self::OnlySystem => "only system",
3523 })
3524 }
3525}
3526
3527impl DiscoveryPreferences {
3528 fn sources(&self, request: &PythonRequest) -> String {
3531 let python_sources = self
3532 .python_preference
3533 .sources()
3534 .iter()
3535 .map(ToString::to_string)
3536 .collect::<Vec<_>>();
3537 match self.environment_preference {
3538 EnvironmentPreference::Any => disjunction(
3539 &["virtual environments"]
3540 .into_iter()
3541 .chain(python_sources.iter().map(String::as_str))
3542 .collect::<Vec<_>>(),
3543 ),
3544 EnvironmentPreference::ExplicitSystem => {
3545 if request.is_explicit_system() {
3546 disjunction(
3547 &["virtual environments"]
3548 .into_iter()
3549 .chain(python_sources.iter().map(String::as_str))
3550 .collect::<Vec<_>>(),
3551 )
3552 } else {
3553 disjunction(&["virtual environments"])
3554 }
3555 }
3556 EnvironmentPreference::OnlySystem => disjunction(
3557 &python_sources
3558 .iter()
3559 .map(String::as_str)
3560 .collect::<Vec<_>>(),
3561 ),
3562 EnvironmentPreference::OnlyVirtual => disjunction(&["virtual environments"]),
3563 }
3564 }
3565}
3566
3567impl fmt::Display for PythonNotFound {
3568 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
3569 let sources = DiscoveryPreferences {
3570 python_preference: self.python_preference,
3571 environment_preference: self.environment_preference,
3572 }
3573 .sources(&self.request);
3574
3575 match self.request {
3576 PythonRequest::Default | PythonRequest::Any => {
3577 write!(f, "No interpreter found in {sources}")
3578 }
3579 PythonRequest::File(_) => {
3580 write!(f, "No interpreter found at {}", self.request)
3581 }
3582 PythonRequest::Directory(_) => {
3583 write!(f, "No interpreter found in {}", self.request)
3584 }
3585 _ => {
3586 write!(f, "No interpreter found for {} in {sources}", self.request)
3587 }
3588 }
3589 }
3590}
3591
3592fn disjunction(items: &[&str]) -> String {
3594 match items.len() {
3595 0 => String::new(),
3596 1 => items[0].to_string(),
3597 2 => format!("{} or {}", items[0], items[1]),
3598 _ => {
3599 let last = items.last().unwrap();
3600 format!(
3601 "{}, or {}",
3602 items.iter().take(items.len() - 1).join(", "),
3603 last
3604 )
3605 }
3606 }
3607}
3608
3609fn try_into_u8_slice(release: &[u64]) -> Result<Vec<u8>, std::num::TryFromIntError> {
3610 release
3611 .iter()
3612 .map(|x| match u8::try_from(*x) {
3613 Ok(x) => Ok(x),
3614 Err(e) => Err(e),
3615 })
3616 .collect()
3617}
3618
3619fn split_wheel_tag_release_version(version: Version) -> Version {
3626 let release = version.release();
3627 if release.len() != 1 {
3628 return version;
3629 }
3630
3631 let release = release[0].to_string();
3632 let mut chars = release.chars();
3633 let Some(major) = chars.next().and_then(|c| c.to_digit(10)) else {
3634 return version;
3635 };
3636
3637 let Ok(minor) = chars.as_str().parse::<u32>() else {
3638 return version;
3639 };
3640
3641 version.with_release([u64::from(major), u64::from(minor)])
3642}
3643
3644#[cfg(test)]
3645mod tests {
3646 use std::{path::PathBuf, str::FromStr};
3647
3648 use assert_fs::{TempDir, prelude::*};
3649 use target_lexicon::{Aarch64Architecture, Architecture};
3650 use test_log::test;
3651 use uv_distribution_types::RequiresPython;
3652 use uv_pep440::{Prerelease, PrereleaseKind, Version, VersionSpecifiers};
3653
3654 use crate::{
3655 discovery::{PythonRequest, VersionRequest},
3656 downloads::{ArchRequest, PythonDownloadRequest},
3657 implementation::ImplementationName,
3658 };
3659 use uv_platform::{Arch, Libc, Os};
3660
3661 use super::{
3662 DiscoveryPreferences, EnvironmentPreference, Error, PythonPreference, PythonVariant,
3663 };
3664
3665 #[test]
3666 fn interpreter_request_from_str() {
3667 assert_eq!(PythonRequest::parse("any"), PythonRequest::Any);
3668 assert_eq!(PythonRequest::parse("default"), PythonRequest::Default);
3669 assert_eq!(
3670 PythonRequest::parse("3.12"),
3671 PythonRequest::Version(VersionRequest::from_str("3.12").unwrap())
3672 );
3673 assert_eq!(
3674 PythonRequest::parse(">=3.12"),
3675 PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap())
3676 );
3677 assert_eq!(
3678 PythonRequest::parse(">=3.12,<3.13"),
3679 PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap())
3680 );
3681 assert_eq!(
3682 PythonRequest::parse(">=3.12,<3.13"),
3683 PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap())
3684 );
3685
3686 assert_eq!(
3687 PythonRequest::parse("3.13.0a1"),
3688 PythonRequest::Version(VersionRequest::from_str("3.13.0a1").unwrap())
3689 );
3690 assert_eq!(
3691 PythonRequest::parse("3.13.0b5"),
3692 PythonRequest::Version(VersionRequest::from_str("3.13.0b5").unwrap())
3693 );
3694 assert_eq!(
3695 PythonRequest::parse("3.13.0rc1"),
3696 PythonRequest::Version(VersionRequest::from_str("3.13.0rc1").unwrap())
3697 );
3698 assert_eq!(
3699 PythonRequest::parse("3.13.1rc1"),
3700 PythonRequest::ExecutableName("3.13.1rc1".to_string()),
3701 "Pre-release version requests require a patch version of zero"
3702 );
3703 assert_eq!(
3704 PythonRequest::parse("3rc1"),
3705 PythonRequest::ExecutableName("3rc1".to_string()),
3706 "Pre-release version requests require a minor version"
3707 );
3708
3709 assert_eq!(
3710 PythonRequest::parse("cpython"),
3711 PythonRequest::Implementation(ImplementationName::CPython)
3712 );
3713
3714 assert_eq!(
3715 PythonRequest::parse("cpython3.12.2"),
3716 PythonRequest::ImplementationVersion(
3717 ImplementationName::CPython,
3718 VersionRequest::from_str("3.12.2").unwrap(),
3719 )
3720 );
3721
3722 assert_eq!(
3723 PythonRequest::parse("cpython-3.13.2"),
3724 PythonRequest::Key(PythonDownloadRequest {
3725 version: Some(VersionRequest::MajorMinorPatch(
3726 3,
3727 13,
3728 2,
3729 PythonVariant::Default
3730 )),
3731 implementation: Some(ImplementationName::CPython),
3732 arch: None,
3733 os: None,
3734 libc: None,
3735 build: None,
3736 prereleases: None
3737 })
3738 );
3739 assert_eq!(
3740 PythonRequest::parse("cpython-3.13.2-macos-aarch64-none"),
3741 PythonRequest::Key(PythonDownloadRequest {
3742 version: Some(VersionRequest::MajorMinorPatch(
3743 3,
3744 13,
3745 2,
3746 PythonVariant::Default
3747 )),
3748 implementation: Some(ImplementationName::CPython),
3749 arch: Some(ArchRequest::Explicit(Arch::new(
3750 Architecture::Aarch64(Aarch64Architecture::Aarch64),
3751 None
3752 ))),
3753 os: Some(Os::new(target_lexicon::OperatingSystem::Darwin(None))),
3754 libc: Some(Libc::None),
3755 build: None,
3756 prereleases: None
3757 })
3758 );
3759 assert_eq!(
3760 PythonRequest::parse("any-3.13.2"),
3761 PythonRequest::Key(PythonDownloadRequest {
3762 version: Some(VersionRequest::MajorMinorPatch(
3763 3,
3764 13,
3765 2,
3766 PythonVariant::Default
3767 )),
3768 implementation: None,
3769 arch: None,
3770 os: None,
3771 libc: None,
3772 build: None,
3773 prereleases: None
3774 })
3775 );
3776 assert_eq!(
3777 PythonRequest::parse("any-3.13.2-any-aarch64"),
3778 PythonRequest::Key(PythonDownloadRequest {
3779 version: Some(VersionRequest::MajorMinorPatch(
3780 3,
3781 13,
3782 2,
3783 PythonVariant::Default
3784 )),
3785 implementation: None,
3786 arch: Some(ArchRequest::Explicit(Arch::new(
3787 Architecture::Aarch64(Aarch64Architecture::Aarch64),
3788 None
3789 ))),
3790 os: None,
3791 libc: None,
3792 build: None,
3793 prereleases: None
3794 })
3795 );
3796
3797 assert_eq!(
3798 PythonRequest::parse("pypy"),
3799 PythonRequest::Implementation(ImplementationName::PyPy)
3800 );
3801 assert_eq!(
3802 PythonRequest::parse("pp"),
3803 PythonRequest::Implementation(ImplementationName::PyPy)
3804 );
3805 assert_eq!(
3806 PythonRequest::parse("graalpy"),
3807 PythonRequest::Implementation(ImplementationName::GraalPy)
3808 );
3809 assert_eq!(
3810 PythonRequest::parse("gp"),
3811 PythonRequest::Implementation(ImplementationName::GraalPy)
3812 );
3813 assert_eq!(
3814 PythonRequest::parse("cp"),
3815 PythonRequest::Implementation(ImplementationName::CPython)
3816 );
3817 assert_eq!(
3818 PythonRequest::parse("pypy3.10"),
3819 PythonRequest::ImplementationVersion(
3820 ImplementationName::PyPy,
3821 VersionRequest::from_str("3.10").unwrap(),
3822 )
3823 );
3824 assert_eq!(
3825 PythonRequest::parse("pp310"),
3826 PythonRequest::ImplementationVersion(
3827 ImplementationName::PyPy,
3828 VersionRequest::from_str("3.10").unwrap(),
3829 )
3830 );
3831 assert_eq!(
3832 PythonRequest::parse("graalpy3.10"),
3833 PythonRequest::ImplementationVersion(
3834 ImplementationName::GraalPy,
3835 VersionRequest::from_str("3.10").unwrap(),
3836 )
3837 );
3838 assert_eq!(
3839 PythonRequest::parse("gp310"),
3840 PythonRequest::ImplementationVersion(
3841 ImplementationName::GraalPy,
3842 VersionRequest::from_str("3.10").unwrap(),
3843 )
3844 );
3845 assert_eq!(
3846 PythonRequest::parse("cp38"),
3847 PythonRequest::ImplementationVersion(
3848 ImplementationName::CPython,
3849 VersionRequest::from_str("3.8").unwrap(),
3850 )
3851 );
3852 assert_eq!(
3853 PythonRequest::parse("pypy@3.10"),
3854 PythonRequest::ImplementationVersion(
3855 ImplementationName::PyPy,
3856 VersionRequest::from_str("3.10").unwrap(),
3857 )
3858 );
3859 assert_eq!(
3860 PythonRequest::parse("pypy310"),
3861 PythonRequest::ImplementationVersion(
3862 ImplementationName::PyPy,
3863 VersionRequest::from_str("3.10").unwrap(),
3864 )
3865 );
3866 assert_eq!(
3867 PythonRequest::parse("graalpy@3.10"),
3868 PythonRequest::ImplementationVersion(
3869 ImplementationName::GraalPy,
3870 VersionRequest::from_str("3.10").unwrap(),
3871 )
3872 );
3873 assert_eq!(
3874 PythonRequest::parse("graalpy310"),
3875 PythonRequest::ImplementationVersion(
3876 ImplementationName::GraalPy,
3877 VersionRequest::from_str("3.10").unwrap(),
3878 )
3879 );
3880
3881 let tempdir = TempDir::new().unwrap();
3882 assert_eq!(
3883 PythonRequest::parse(tempdir.path().to_str().unwrap()),
3884 PythonRequest::Directory(tempdir.path().to_path_buf()),
3885 "An existing directory is treated as a directory"
3886 );
3887 assert_eq!(
3888 PythonRequest::parse(tempdir.child("foo").path().to_str().unwrap()),
3889 PythonRequest::File(tempdir.child("foo").path().to_path_buf()),
3890 "A path that does not exist is treated as a file"
3891 );
3892 tempdir.child("bar").touch().unwrap();
3893 assert_eq!(
3894 PythonRequest::parse(tempdir.child("bar").path().to_str().unwrap()),
3895 PythonRequest::File(tempdir.child("bar").path().to_path_buf()),
3896 "An existing file is treated as a file"
3897 );
3898 assert_eq!(
3899 PythonRequest::parse("./foo"),
3900 PythonRequest::File(PathBuf::from_str("./foo").unwrap()),
3901 "A string with a file system separator is treated as a file"
3902 );
3903 assert_eq!(
3904 PythonRequest::parse("3.13t"),
3905 PythonRequest::Version(VersionRequest::from_str("3.13t").unwrap())
3906 );
3907 }
3908
3909 #[test]
3910 fn discovery_sources_prefer_system_orders_search_path_first() {
3911 let preferences = DiscoveryPreferences {
3912 python_preference: PythonPreference::System,
3913 environment_preference: EnvironmentPreference::OnlySystem,
3914 };
3915 let sources = preferences.sources(&PythonRequest::Default);
3916
3917 if cfg!(windows) {
3918 assert_eq!(sources, "search path, registry, or managed installations");
3919 } else {
3920 assert_eq!(sources, "search path or managed installations");
3921 }
3922 }
3923
3924 #[test]
3925 fn discovery_sources_only_system_matches_platform_order() {
3926 let preferences = DiscoveryPreferences {
3927 python_preference: PythonPreference::OnlySystem,
3928 environment_preference: EnvironmentPreference::OnlySystem,
3929 };
3930 let sources = preferences.sources(&PythonRequest::Default);
3931
3932 if cfg!(windows) {
3933 assert_eq!(sources, "search path or registry");
3934 } else {
3935 assert_eq!(sources, "search path");
3936 }
3937 }
3938
3939 #[test]
3940 fn interpreter_request_to_canonical_string() {
3941 assert_eq!(PythonRequest::Default.to_canonical_string(), "default");
3942 assert_eq!(PythonRequest::Any.to_canonical_string(), "any");
3943 assert_eq!(
3944 PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()).to_canonical_string(),
3945 "3.12"
3946 );
3947 assert_eq!(
3948 PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap())
3949 .to_canonical_string(),
3950 ">=3.12"
3951 );
3952 assert_eq!(
3953 PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap())
3954 .to_canonical_string(),
3955 ">=3.12, <3.13"
3956 );
3957
3958 assert_eq!(
3959 PythonRequest::Version(VersionRequest::from_str("3.13.0a1").unwrap())
3960 .to_canonical_string(),
3961 "3.13a1"
3962 );
3963
3964 assert_eq!(
3965 PythonRequest::Version(VersionRequest::from_str("3.13.0b5").unwrap())
3966 .to_canonical_string(),
3967 "3.13b5"
3968 );
3969
3970 assert_eq!(
3971 PythonRequest::Version(VersionRequest::from_str("3.13.0rc1").unwrap())
3972 .to_canonical_string(),
3973 "3.13rc1"
3974 );
3975
3976 assert_eq!(
3977 PythonRequest::Version(VersionRequest::from_str("313rc4").unwrap())
3978 .to_canonical_string(),
3979 "3.13rc4"
3980 );
3981
3982 assert_eq!(
3983 PythonRequest::Version(VersionRequest::from_str("3.14.5rc1").unwrap())
3984 .to_canonical_string(),
3985 "3.14.5rc1"
3986 );
3987
3988 assert_eq!(
3989 PythonRequest::ExecutableName("foo".to_string()).to_canonical_string(),
3990 "foo"
3991 );
3992 assert_eq!(
3993 PythonRequest::Implementation(ImplementationName::CPython).to_canonical_string(),
3994 "cpython"
3995 );
3996 assert_eq!(
3997 PythonRequest::ImplementationVersion(
3998 ImplementationName::CPython,
3999 VersionRequest::from_str("3.12.2").unwrap(),
4000 )
4001 .to_canonical_string(),
4002 "cpython@3.12.2"
4003 );
4004 assert_eq!(
4005 PythonRequest::Implementation(ImplementationName::PyPy).to_canonical_string(),
4006 "pypy"
4007 );
4008 assert_eq!(
4009 PythonRequest::ImplementationVersion(
4010 ImplementationName::PyPy,
4011 VersionRequest::from_str("3.10").unwrap(),
4012 )
4013 .to_canonical_string(),
4014 "pypy@3.10"
4015 );
4016 assert_eq!(
4017 PythonRequest::Implementation(ImplementationName::GraalPy).to_canonical_string(),
4018 "graalpy"
4019 );
4020 assert_eq!(
4021 PythonRequest::ImplementationVersion(
4022 ImplementationName::GraalPy,
4023 VersionRequest::from_str("3.10").unwrap(),
4024 )
4025 .to_canonical_string(),
4026 "graalpy@3.10"
4027 );
4028
4029 let tempdir = TempDir::new().unwrap();
4030 assert_eq!(
4031 PythonRequest::Directory(tempdir.path().to_path_buf()).to_canonical_string(),
4032 tempdir.path().to_str().unwrap(),
4033 "An existing directory is treated as a directory"
4034 );
4035 assert_eq!(
4036 PythonRequest::File(tempdir.child("foo").path().to_path_buf()).to_canonical_string(),
4037 tempdir.child("foo").path().to_str().unwrap(),
4038 "A path that does not exist is treated as a file"
4039 );
4040 tempdir.child("bar").touch().unwrap();
4041 assert_eq!(
4042 PythonRequest::File(tempdir.child("bar").path().to_path_buf()).to_canonical_string(),
4043 tempdir.child("bar").path().to_str().unwrap(),
4044 "An existing file is treated as a file"
4045 );
4046 assert_eq!(
4047 PythonRequest::File(PathBuf::from_str("./foo").unwrap()).to_canonical_string(),
4048 "./foo",
4049 "A string with a file system separator is treated as a file"
4050 );
4051 }
4052
4053 #[test]
4054 fn version_request_from_str() {
4055 assert_eq!(
4056 VersionRequest::from_str("3").unwrap(),
4057 VersionRequest::Major(3, PythonVariant::Default)
4058 );
4059 assert_eq!(
4060 VersionRequest::from_str("3.12").unwrap(),
4061 VersionRequest::MajorMinor(3, 12, PythonVariant::Default)
4062 );
4063 assert_eq!(
4064 VersionRequest::from_str("3.12.1").unwrap(),
4065 VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default)
4066 );
4067 assert!(VersionRequest::from_str("1.foo.1").is_err());
4068 assert_eq!(
4069 VersionRequest::from_str("3").unwrap(),
4070 VersionRequest::Major(3, PythonVariant::Default)
4071 );
4072 assert_eq!(
4073 VersionRequest::from_str("38").unwrap(),
4074 VersionRequest::MajorMinor(3, 8, PythonVariant::Default)
4075 );
4076 assert_eq!(
4077 VersionRequest::from_str("312").unwrap(),
4078 VersionRequest::MajorMinor(3, 12, PythonVariant::Default)
4079 );
4080 assert_eq!(
4081 VersionRequest::from_str("3100").unwrap(),
4082 VersionRequest::MajorMinor(3, 100, PythonVariant::Default)
4083 );
4084 assert_eq!(
4085 VersionRequest::from_str("3.13a1").unwrap(),
4086 VersionRequest::MajorMinorPrerelease(
4087 3,
4088 13,
4089 Prerelease {
4090 kind: PrereleaseKind::Alpha,
4091 number: 1
4092 },
4093 PythonVariant::Default
4094 )
4095 );
4096 assert_eq!(
4097 VersionRequest::from_str("313b1").unwrap(),
4098 VersionRequest::MajorMinorPrerelease(
4099 3,
4100 13,
4101 Prerelease {
4102 kind: PrereleaseKind::Beta,
4103 number: 1
4104 },
4105 PythonVariant::Default
4106 )
4107 );
4108 assert_eq!(
4109 VersionRequest::from_str("3.13.0b2").unwrap(),
4110 VersionRequest::MajorMinorPrerelease(
4111 3,
4112 13,
4113 Prerelease {
4114 kind: PrereleaseKind::Beta,
4115 number: 2
4116 },
4117 PythonVariant::Default
4118 )
4119 );
4120 assert_eq!(
4121 VersionRequest::from_str("3.13.0rc3").unwrap(),
4122 VersionRequest::MajorMinorPrerelease(
4123 3,
4124 13,
4125 Prerelease {
4126 kind: PrereleaseKind::Rc,
4127 number: 3
4128 },
4129 PythonVariant::Default
4130 )
4131 );
4132 assert!(
4133 matches!(
4134 VersionRequest::from_str("3rc1"),
4135 Err(Error::InvalidVersionRequest(_))
4136 ),
4137 "Pre-release version requests require a minor version"
4138 );
4139 assert_eq!(
4140 VersionRequest::from_str("3.14.5rc1").unwrap(),
4141 VersionRequest::MajorMinorPatchPrerelease(
4142 3,
4143 14,
4144 5,
4145 Prerelease {
4146 kind: PrereleaseKind::Rc,
4147 number: 1
4148 },
4149 PythonVariant::Default
4150 ),
4151 "Pre-release version requests with a non-zero patch are allowed (e.g., `3.14.5rc1`)"
4152 );
4153 assert_eq!(
4154 VersionRequest::from_str("3.13.2rc1").unwrap(),
4155 VersionRequest::MajorMinorPatchPrerelease(
4156 3,
4157 13,
4158 2,
4159 Prerelease {
4160 kind: PrereleaseKind::Rc,
4161 number: 1
4162 },
4163 PythonVariant::Default
4164 )
4165 );
4166 assert!(
4167 matches!(
4168 VersionRequest::from_str("3.12-dev"),
4169 Err(Error::InvalidVersionRequest(_))
4170 ),
4171 "Development version segments are not allowed"
4172 );
4173 assert!(
4174 matches!(
4175 VersionRequest::from_str("3.12+local"),
4176 Err(Error::InvalidVersionRequest(_))
4177 ),
4178 "Local version segments are not allowed"
4179 );
4180 assert!(
4181 matches!(
4182 VersionRequest::from_str("3.12.post0"),
4183 Err(Error::InvalidVersionRequest(_))
4184 ),
4185 "Post version segments are not allowed"
4186 );
4187 assert!(
4188 matches!(
4190 VersionRequest::from_str("31000"),
4191 Err(Error::InvalidVersionRequest(_))
4192 )
4193 );
4194 assert_eq!(
4195 VersionRequest::from_str("3t").unwrap(),
4196 VersionRequest::Major(3, PythonVariant::Freethreaded)
4197 );
4198 assert_eq!(
4199 VersionRequest::from_str("313t").unwrap(),
4200 VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded)
4201 );
4202 assert_eq!(
4203 VersionRequest::from_str("3.13t").unwrap(),
4204 VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded)
4205 );
4206 assert_eq!(
4207 VersionRequest::from_str(">=3.13t").unwrap(),
4208 VersionRequest::Range(
4209 VersionSpecifiers::from_str(">=3.13").unwrap(),
4210 PythonVariant::Freethreaded
4211 )
4212 );
4213 assert_eq!(
4214 VersionRequest::from_str(">=3.13").unwrap(),
4215 VersionRequest::Range(
4216 VersionSpecifiers::from_str(">=3.13").unwrap(),
4217 PythonVariant::Default
4218 )
4219 );
4220 assert_eq!(
4221 VersionRequest::from_str(">=3.12,<3.14t").unwrap(),
4222 VersionRequest::Range(
4223 VersionSpecifiers::from_str(">=3.12,<3.14").unwrap(),
4224 PythonVariant::Freethreaded
4225 )
4226 );
4227 assert!(matches!(
4228 VersionRequest::from_str("3.13tt"),
4229 Err(Error::InvalidVersionRequest(_))
4230 ));
4231
4232 assert_eq!(
4234 VersionRequest::from_str("==3.12").unwrap(),
4235 VersionRequest::MajorMinor(3, 12, PythonVariant::Default)
4236 );
4237 assert_eq!(
4238 VersionRequest::from_str("==3.12.1").unwrap(),
4239 VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default)
4240 );
4241 }
4242
4243 #[test]
4244 fn version_request_from_specifiers() {
4245 assert_eq!(
4247 VersionRequest::from_specifiers(
4248 VersionSpecifiers::from_str("==3.12").unwrap(),
4249 PythonVariant::Default
4250 ),
4251 VersionRequest::MajorMinor(3, 12, PythonVariant::Default)
4252 );
4253 assert_eq!(
4254 VersionRequest::from_specifiers(
4255 VersionSpecifiers::from_str("==3.12.1").unwrap(),
4256 PythonVariant::Default
4257 ),
4258 VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default)
4259 );
4260
4261 assert_eq!(
4263 VersionRequest::from_specifiers(
4264 VersionSpecifiers::from_str("==3.12.*").unwrap(),
4265 PythonVariant::Default
4266 ),
4267 VersionRequest::Range(
4268 VersionSpecifiers::from_str("==3.12.*").unwrap(),
4269 PythonVariant::Default
4270 )
4271 );
4272
4273 assert_eq!(
4275 VersionRequest::from_specifiers(
4276 VersionSpecifiers::from_str(">=3.12").unwrap(),
4277 PythonVariant::Default
4278 ),
4279 VersionRequest::Range(
4280 VersionSpecifiers::from_str(">=3.12").unwrap(),
4281 PythonVariant::Default
4282 )
4283 );
4284
4285 assert_eq!(
4287 VersionRequest::from_specifiers(
4288 VersionSpecifiers::from_str(">=3.12,<3.14").unwrap(),
4289 PythonVariant::Default
4290 ),
4291 VersionRequest::Range(
4292 VersionSpecifiers::from_str(">=3.12,<3.14").unwrap(),
4293 PythonVariant::Default
4294 )
4295 );
4296 }
4297
4298 #[test]
4299 fn executable_names_from_request() {
4300 fn case(request: &str, expected: &[&str]) {
4301 let (implementation, version) = match PythonRequest::parse(request) {
4302 PythonRequest::Any => (None, VersionRequest::Any),
4303 PythonRequest::Default => (None, VersionRequest::Default),
4304 PythonRequest::Version(version) => (None, version),
4305 PythonRequest::ImplementationVersion(implementation, version) => {
4306 (Some(implementation), version)
4307 }
4308 PythonRequest::Implementation(implementation) => {
4309 (Some(implementation), VersionRequest::Default)
4310 }
4311 result => {
4312 panic!("Test cases should request versions or implementations; got {result:?}")
4313 }
4314 };
4315
4316 let result: Vec<_> = version
4317 .executable_names(implementation.as_ref())
4318 .into_iter()
4319 .map(|name| name.to_string())
4320 .collect();
4321
4322 let expected: Vec<_> = expected
4323 .iter()
4324 .map(|name| format!("{name}{exe}", exe = std::env::consts::EXE_SUFFIX))
4325 .collect();
4326
4327 assert_eq!(result, expected, "mismatch for case \"{request}\"");
4328 }
4329
4330 case(
4331 "any",
4332 &[
4333 "python", "python3", "cpython", "cpython3", "pypy", "pypy3", "graalpy", "graalpy3",
4334 "pyodide", "pyodide3",
4335 ],
4336 );
4337
4338 case("default", &["python", "python3"]);
4339
4340 case("3", &["python3", "python"]);
4341
4342 case("4", &["python4", "python"]);
4343
4344 case("3.13", &["python3.13", "python3", "python"]);
4345
4346 case("pypy", &["pypy", "pypy3", "python", "python3"]);
4347
4348 case(
4349 "pypy@3.10",
4350 &[
4351 "pypy3.10",
4352 "pypy3",
4353 "pypy",
4354 "python3.10",
4355 "python3",
4356 "python",
4357 ],
4358 );
4359
4360 case(
4361 "3.13t",
4362 &[
4363 "python3.13t",
4364 "python3.13",
4365 "python3t",
4366 "python3",
4367 "pythont",
4368 "python",
4369 ],
4370 );
4371 case("3t", &["python3t", "python3", "pythont", "python"]);
4372
4373 case(
4374 "3.13.2",
4375 &["python3.13.2", "python3.13", "python3", "python"],
4376 );
4377
4378 case(
4379 "3.13rc2",
4380 &["python3.13rc2", "python3.13", "python3", "python"],
4381 );
4382 }
4383
4384 #[test]
4385 fn test_try_split_prefix_and_version() {
4386 assert!(matches!(
4387 PythonRequest::try_split_prefix_and_version("prefix", "prefix"),
4388 Ok(None),
4389 ));
4390 assert!(matches!(
4391 PythonRequest::try_split_prefix_and_version("prefix", "prefix3"),
4392 Ok(Some(_)),
4393 ));
4394 assert!(matches!(
4395 PythonRequest::try_split_prefix_and_version("prefix", "prefix@3"),
4396 Ok(Some(_)),
4397 ));
4398 assert!(matches!(
4399 PythonRequest::try_split_prefix_and_version("prefix", "prefix3notaversion"),
4400 Ok(None),
4401 ));
4402 assert!(
4404 PythonRequest::try_split_prefix_and_version("prefix", "prefix@3notaversion").is_err()
4405 );
4406 assert!(PythonRequest::try_split_prefix_and_version("", "@3").is_err());
4408 }
4409
4410 #[test]
4411 fn version_request_as_pep440_version() {
4412 assert_eq!(VersionRequest::Default.as_pep440_version(), None);
4414 assert_eq!(VersionRequest::Any.as_pep440_version(), None);
4415 assert_eq!(
4416 VersionRequest::from_str(">=3.10")
4417 .unwrap()
4418 .as_pep440_version(),
4419 None
4420 );
4421
4422 assert_eq!(
4424 VersionRequest::Major(3, PythonVariant::Default).as_pep440_version(),
4425 Some(Version::from_str("3").unwrap())
4426 );
4427
4428 assert_eq!(
4430 VersionRequest::MajorMinor(3, 12, PythonVariant::Default).as_pep440_version(),
4431 Some(Version::from_str("3.12").unwrap())
4432 );
4433
4434 assert_eq!(
4436 VersionRequest::MajorMinorPatch(3, 12, 5, PythonVariant::Default).as_pep440_version(),
4437 Some(Version::from_str("3.12.5").unwrap())
4438 );
4439
4440 assert_eq!(
4442 VersionRequest::MajorMinorPrerelease(
4443 3,
4444 14,
4445 Prerelease {
4446 kind: PrereleaseKind::Alpha,
4447 number: 1
4448 },
4449 PythonVariant::Default
4450 )
4451 .as_pep440_version(),
4452 Some(Version::from_str("3.14.0a1").unwrap())
4453 );
4454 assert_eq!(
4455 VersionRequest::MajorMinorPrerelease(
4456 3,
4457 14,
4458 Prerelease {
4459 kind: PrereleaseKind::Beta,
4460 number: 2
4461 },
4462 PythonVariant::Default
4463 )
4464 .as_pep440_version(),
4465 Some(Version::from_str("3.14.0b2").unwrap())
4466 );
4467 assert_eq!(
4468 VersionRequest::MajorMinorPrerelease(
4469 3,
4470 13,
4471 Prerelease {
4472 kind: PrereleaseKind::Rc,
4473 number: 3
4474 },
4475 PythonVariant::Default
4476 )
4477 .as_pep440_version(),
4478 Some(Version::from_str("3.13.0rc3").unwrap())
4479 );
4480
4481 assert_eq!(
4483 VersionRequest::Major(3, PythonVariant::Freethreaded).as_pep440_version(),
4484 Some(Version::from_str("3").unwrap())
4485 );
4486 assert_eq!(
4487 VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded).as_pep440_version(),
4488 Some(Version::from_str("3.13").unwrap())
4489 );
4490 }
4491
4492 #[test]
4493 fn python_request_as_pep440_version() {
4494 assert_eq!(PythonRequest::Any.as_pep440_version(), None);
4496 assert_eq!(PythonRequest::Default.as_pep440_version(), None);
4497
4498 assert_eq!(
4500 PythonRequest::Version(VersionRequest::MajorMinor(3, 11, PythonVariant::Default))
4501 .as_pep440_version(),
4502 Some(Version::from_str("3.11").unwrap())
4503 );
4504
4505 assert_eq!(
4507 PythonRequest::ImplementationVersion(
4508 ImplementationName::CPython,
4509 VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default),
4510 )
4511 .as_pep440_version(),
4512 Some(Version::from_str("3.12.1").unwrap())
4513 );
4514
4515 assert_eq!(
4517 PythonRequest::Implementation(ImplementationName::CPython).as_pep440_version(),
4518 None
4519 );
4520
4521 assert_eq!(
4523 PythonRequest::parse("cpython-3.13.2").as_pep440_version(),
4524 Some(Version::from_str("3.13.2").unwrap())
4525 );
4526
4527 assert_eq!(
4529 PythonRequest::parse("cpython-macos-aarch64-none").as_pep440_version(),
4530 None
4531 );
4532
4533 assert_eq!(
4535 PythonRequest::Version(VersionRequest::from_str(">=3.10").unwrap()).as_pep440_version(),
4536 None
4537 );
4538 }
4539
4540 #[test]
4541 fn intersects_requires_python_exact() {
4542 let requires_python =
4543 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4544
4545 assert!(PythonRequest::parse("3.12").intersects_requires_python(&requires_python));
4546 assert!(!PythonRequest::parse("3.11").intersects_requires_python(&requires_python));
4547 }
4548
4549 #[test]
4550 fn intersects_requires_python_major() {
4551 let requires_python =
4552 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4553
4554 assert!(PythonRequest::parse("3").intersects_requires_python(&requires_python));
4556 assert!(!PythonRequest::parse("2").intersects_requires_python(&requires_python));
4558 }
4559
4560 #[test]
4561 fn intersects_requires_python_range() {
4562 let requires_python =
4563 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4564
4565 assert!(PythonRequest::parse(">=3.12,<3.13").intersects_requires_python(&requires_python));
4566 assert!(!PythonRequest::parse(">=3.10,<3.12").intersects_requires_python(&requires_python));
4567 }
4568
4569 #[test]
4570 fn intersects_requires_python_implementation_range() {
4571 let requires_python =
4572 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4573
4574 assert!(
4575 PythonRequest::parse("cpython@>=3.12,<3.13")
4576 .intersects_requires_python(&requires_python)
4577 );
4578 assert!(
4579 !PythonRequest::parse("cpython@>=3.10,<3.12")
4580 .intersects_requires_python(&requires_python)
4581 );
4582 }
4583
4584 #[test]
4585 fn intersects_requires_python_no_version() {
4586 let requires_python =
4587 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4588
4589 assert!(PythonRequest::Any.intersects_requires_python(&requires_python));
4591 assert!(PythonRequest::Default.intersects_requires_python(&requires_python));
4592 assert!(
4593 PythonRequest::Implementation(ImplementationName::CPython)
4594 .intersects_requires_python(&requires_python)
4595 );
4596 }
4597}