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