1use itertools::{Either, Itertools};
2use owo_colors::AnsiColors;
3use regex::Regex;
4use reqwest_retry::policies::ExponentialBackoff;
5use rustc_hash::{FxBuildHasher, FxHashSet};
6use same_file::is_same_file;
7use std::borrow::Cow;
8use std::env::consts::EXE_SUFFIX;
9use std::fmt::{self, Debug, Formatter};
10use std::{env, io, iter};
11use std::{path::Path, path::PathBuf, str::FromStr};
12use thiserror::Error;
13use tracing::{debug, instrument, trace};
14use uv_cache::Cache;
15use uv_client::BaseClient;
16use uv_distribution_types::RequiresPython;
17use uv_fs::Simplified;
18use uv_fs::which::is_executable;
19use uv_pep440::{
20 LowerBound, Prerelease, UpperBound, Version, VersionSpecifier, VersionSpecifiers,
21 release_specifiers_to_ranges,
22};
23use uv_preview::Preview;
24use uv_static::EnvVars;
25use uv_warnings::anstream;
26use uv_warnings::warn_user_once;
27use which::{which, which_all};
28
29use crate::downloads::{ManagedPythonDownloadList, PlatformRequest, PythonDownloadRequest};
30use crate::implementation::ImplementationName;
31use crate::installation::{PythonInstallation, PythonInstallationKey};
32use crate::interpreter::Error as InterpreterError;
33use crate::interpreter::{StatusCodeError, UnexpectedResponseError};
34use crate::managed::{ManagedPythonInstallations, PythonMinorVersionLink};
35#[cfg(windows)]
36use crate::microsoft_store::find_microsoft_store_pythons;
37use crate::python_version::python_build_versions_from_env;
38use crate::virtualenv::Error as VirtualEnvError;
39use crate::virtualenv::{
40 CondaEnvironmentKind, conda_environment_from_env, virtualenv_from_env,
41 virtualenv_from_working_dir, virtualenv_python_executable,
42};
43#[cfg(windows)]
44use crate::windows_registry::{WindowsPython, registry_pythons};
45use crate::{BrokenLink, Interpreter, PythonVersion};
46
47#[derive(Debug, Clone, Eq, Default)]
51pub enum PythonRequest {
52 #[default]
57 Default,
58 Any,
60 Version(VersionRequest),
62 Directory(PathBuf),
64 File(PathBuf),
66 ExecutableName(String),
68 Implementation(ImplementationName),
70 ImplementationVersion(ImplementationName, VersionRequest),
72 Key(PythonDownloadRequest),
75}
76
77impl PartialEq for PythonRequest {
78 fn eq(&self, other: &Self) -> bool {
79 self.to_canonical_string() == other.to_canonical_string()
80 }
81}
82
83impl std::hash::Hash for PythonRequest {
84 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
85 self.to_canonical_string().hash(state);
86 }
87}
88
89impl<'a> serde::Deserialize<'a> for PythonRequest {
90 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91 where
92 D: serde::Deserializer<'a>,
93 {
94 let s = <Cow<'_, str>>::deserialize(deserializer)?;
95 Ok(Self::parse(&s))
96 }
97}
98
99impl serde::Serialize for PythonRequest {
100 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101 where
102 S: serde::Serializer,
103 {
104 let s = self.to_canonical_string();
105 serializer.serialize_str(&s)
106 }
107}
108
109#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
110#[serde(deny_unknown_fields, rename_all = "kebab-case")]
111#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
112#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
113pub enum PythonPreference {
114 OnlyManaged,
116 #[default]
117 Managed,
122 System,
126 OnlySystem,
128}
129
130#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
131#[serde(deny_unknown_fields, rename_all = "kebab-case")]
132#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
133#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
134pub enum PythonDownloads {
135 #[default]
137 #[serde(alias = "auto")]
138 Automatic,
139 Manual,
141 Never,
143}
144
145impl FromStr for PythonDownloads {
146 type Err = String;
147
148 fn from_str(s: &str) -> Result<Self, Self::Err> {
149 match s.to_ascii_lowercase().as_str() {
150 "auto" | "automatic" | "true" | "1" => Ok(Self::Automatic),
151 "manual" => Ok(Self::Manual),
152 "never" | "false" | "0" => Ok(Self::Never),
153 _ => Err(format!("Invalid value for `python-download`: '{s}'")),
154 }
155 }
156}
157
158impl From<bool> for PythonDownloads {
159 fn from(value: bool) -> Self {
160 if value { Self::Automatic } else { Self::Never }
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
165pub enum EnvironmentPreference {
166 #[default]
168 OnlyVirtual,
169 ExplicitSystem,
171 OnlySystem,
173 Any,
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, Default)]
178pub(crate) struct DiscoveryPreferences {
179 python_preference: PythonPreference,
180 environment_preference: EnvironmentPreference,
181}
182
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
184pub enum PythonVariant {
185 #[default]
186 Default,
187 Debug,
188 Freethreaded,
189 FreethreadedDebug,
190 Gil,
191 GilDebug,
192}
193
194#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
196pub enum VersionRequest {
197 #[default]
199 Default,
200 Any,
202 Major(u8, PythonVariant),
203 MajorMinor(u8, u8, PythonVariant),
204 MajorMinorPatch(u8, u8, u8, PythonVariant),
205 MajorMinorPrerelease(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("Invalid version request: {0}")]
279 InvalidVersionRequest(String),
280
281 #[error("Requesting the 'latest' Python version is not yet supported")]
283 LatestVersionRequest,
284
285 #[error("Interpreter discovery for `{0}` requires `{1}` but only `{2}` is allowed")]
287 SourceNotAllowed(PythonRequest, PythonSource, PythonPreference),
288
289 #[error(transparent)]
290 BuildVersion(#[from] crate::python_version::BuildVersionError),
291}
292
293fn python_executables_from_virtual_environments<'a>(
302 preview: Preview,
303) -> impl Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a {
304 let from_active_environment = iter::once_with(|| {
305 virtualenv_from_env()
306 .into_iter()
307 .map(virtualenv_python_executable)
308 .map(|path| Ok((PythonSource::ActiveEnvironment, path)))
309 })
310 .flatten();
311
312 let from_conda_environment = iter::once_with(move || {
314 conda_environment_from_env(CondaEnvironmentKind::Child, preview)
315 .into_iter()
316 .map(virtualenv_python_executable)
317 .map(|path| Ok((PythonSource::CondaPrefix, path)))
318 })
319 .flatten();
320
321 let from_discovered_environment = iter::once_with(|| {
322 virtualenv_from_working_dir()
323 .map(|path| {
324 path.map(virtualenv_python_executable)
325 .map(|path| (PythonSource::DiscoveredEnvironment, path))
326 .into_iter()
327 })
328 .map_err(Error::from)
329 })
330 .flatten_ok();
331
332 from_active_environment
333 .chain(from_conda_environment)
334 .chain(from_discovered_environment)
335}
336
337fn python_executables_from_installed<'a>(
356 version: &'a VersionRequest,
357 implementation: Option<&'a ImplementationName>,
358 platform: PlatformRequest,
359 preference: PythonPreference,
360) -> Box<dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a> {
361 let from_managed_installations = iter::once_with(move || {
362 ManagedPythonInstallations::from_settings(None)
363 .map_err(Error::from)
364 .and_then(|installed_installations| {
365 debug!(
366 "Searching for managed installations at `{}`",
367 installed_installations.root().user_display()
368 );
369 let installations = installed_installations.find_matching_current_platform()?;
370
371 let build_versions = python_build_versions_from_env()?;
372
373 Ok(installations
376 .into_iter()
377 .filter(move |installation| {
378 if !version.matches_version(&installation.version()) {
379 debug!("Skipping managed installation `{installation}`: does not satisfy `{version}`");
380 return false;
381 }
382 if !platform.matches(installation.platform()) {
383 debug!("Skipping managed installation `{installation}`: does not satisfy requested platform `{platform}`");
384 return false;
385 }
386
387 if let Some(requested_build) = build_versions.get(&installation.implementation()) {
388 let Some(installation_build) = installation.build() else {
389 debug!(
390 "Skipping managed installation `{installation}`: a build version was requested but is not recorded for this installation"
391 );
392 return false;
393 };
394 if installation_build != requested_build {
395 debug!(
396 "Skipping managed installation `{installation}`: requested build version `{requested_build}` does not match installation build version `{installation_build}`"
397 );
398 return false;
399 }
400 }
401
402 true
403 })
404 .inspect(|installation| debug!("Found managed installation `{installation}`"))
405 .map(move |installation| {
406 let executable = version
409 .patch()
410 .is_none()
411 .then(|| {
412 PythonMinorVersionLink::from_installation(
413 &installation,
414 )
415 .filter(PythonMinorVersionLink::exists)
416 .map(
417 |minor_version_link| {
418 minor_version_link.symlink_executable.clone()
419 },
420 )
421 })
422 .flatten()
423 .unwrap_or_else(|| installation.executable(false));
424 (PythonSource::Managed, executable)
425 })
426 )
427 })
428 })
429 .flatten_ok();
430
431 let from_search_path = iter::once_with(move || {
432 python_executables_from_search_path(version, implementation)
433 .enumerate()
434 .map(|(i, path)| {
435 if i == 0 {
436 Ok((PythonSource::SearchPathFirst, path))
437 } else {
438 Ok((PythonSource::SearchPath, path))
439 }
440 })
441 })
442 .flatten();
443
444 let from_windows_registry = iter::once_with(move || {
445 #[cfg(windows)]
446 {
447 let version_filter = move |entry: &WindowsPython| {
449 if let Some(found) = &entry.version {
450 if found.string.chars().filter(|c| *c == '.').count() == 1 {
452 version.matches_major_minor(found.major(), found.minor())
453 } else {
454 version.matches_version(found)
455 }
456 } else {
457 true
458 }
459 };
460
461 env::var_os(EnvVars::UV_TEST_PYTHON_PATH)
462 .is_none()
463 .then(|| {
464 registry_pythons()
465 .map(|entries| {
466 entries
467 .into_iter()
468 .filter(version_filter)
469 .map(|entry| (PythonSource::Registry, entry.path))
470 .chain(
471 find_microsoft_store_pythons()
472 .filter(version_filter)
473 .map(|entry| (PythonSource::MicrosoftStore, entry.path)),
474 )
475 })
476 .map_err(Error::from)
477 })
478 .into_iter()
479 .flatten_ok()
480 }
481 #[cfg(not(windows))]
482 {
483 Vec::new()
484 }
485 })
486 .flatten();
487
488 match preference {
489 PythonPreference::OnlyManaged => {
490 if std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED).is_ok() {
494 Box::new(from_managed_installations.chain(from_search_path))
495 } else {
496 Box::new(from_managed_installations)
497 }
498 }
499 PythonPreference::Managed => Box::new(
500 from_managed_installations
501 .chain(from_search_path)
502 .chain(from_windows_registry),
503 ),
504 PythonPreference::System => Box::new(
505 from_search_path
506 .chain(from_windows_registry)
507 .chain(from_managed_installations),
508 ),
509 PythonPreference::OnlySystem => Box::new(from_search_path.chain(from_windows_registry)),
510 }
511}
512
513fn python_executables<'a>(
523 version: &'a VersionRequest,
524 implementation: Option<&'a ImplementationName>,
525 platform: PlatformRequest,
526 environments: EnvironmentPreference,
527 preference: PythonPreference,
528 preview: Preview,
529) -> Box<dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a> {
530 let from_parent_interpreter = iter::once_with(|| {
532 env::var_os(EnvVars::UV_INTERNAL__PARENT_INTERPRETER)
533 .into_iter()
534 .map(|path| Ok((PythonSource::ParentInterpreter, PathBuf::from(path))))
535 })
536 .flatten();
537
538 let from_base_conda_environment = iter::once_with(move || {
540 conda_environment_from_env(CondaEnvironmentKind::Base, preview)
541 .into_iter()
542 .map(virtualenv_python_executable)
543 .map(|path| Ok((PythonSource::BaseCondaPrefix, path)))
544 })
545 .flatten();
546
547 let from_virtual_environments = python_executables_from_virtual_environments(preview);
548 let from_installed =
549 python_executables_from_installed(version, implementation, platform, preference);
550
551 match environments {
555 EnvironmentPreference::OnlyVirtual => {
556 Box::new(from_parent_interpreter.chain(from_virtual_environments))
557 }
558 EnvironmentPreference::ExplicitSystem | EnvironmentPreference::Any => Box::new(
559 from_parent_interpreter
560 .chain(from_virtual_environments)
561 .chain(from_base_conda_environment)
562 .chain(from_installed),
563 ),
564 EnvironmentPreference::OnlySystem => Box::new(
565 from_parent_interpreter
566 .chain(from_base_conda_environment)
567 .chain(from_installed),
568 ),
569 }
570}
571
572fn python_executables_from_search_path<'a>(
584 version: &'a VersionRequest,
585 implementation: Option<&'a ImplementationName>,
586) -> impl Iterator<Item = PathBuf> + 'a {
587 let search_path = env::var_os(EnvVars::UV_TEST_PYTHON_PATH)
589 .unwrap_or(env::var_os(EnvVars::PATH).unwrap_or_default());
590
591 let possible_names: Vec<_> = version
592 .executable_names(implementation)
593 .into_iter()
594 .map(|name| name.to_string())
595 .collect();
596
597 trace!(
598 "Searching PATH for executables: {}",
599 possible_names.join(", ")
600 );
601
602 let search_dirs: Vec<_> = env::split_paths(&search_path).collect();
606 let mut seen_dirs = FxHashSet::with_capacity_and_hasher(search_dirs.len(), FxBuildHasher);
607 search_dirs
608 .into_iter()
609 .filter(|dir| dir.is_dir())
610 .flat_map(move |dir| {
611 let dir_clone = dir.clone();
613 trace!(
614 "Checking `PATH` directory for interpreters: {}",
615 dir.display()
616 );
617 same_file::Handle::from_path(&dir)
618 .map(|handle| seen_dirs.insert(handle))
621 .inspect(|fresh_dir| {
622 if !fresh_dir {
623 trace!("Skipping already seen directory: {}", dir.display());
624 }
625 })
626 .unwrap_or(true)
628 .then(|| {
629 possible_names
630 .clone()
631 .into_iter()
632 .flat_map(move |name| {
633 which::which_in_global(&*name, Some(&dir))
635 .into_iter()
636 .flatten()
637 .collect::<Vec<_>>()
640 })
641 .chain(find_all_minor(implementation, version, &dir_clone))
642 .filter(|path| !is_windows_store_shim(path))
643 .inspect(|path| {
644 trace!("Found possible Python executable: {}", path.display());
645 })
646 .chain(
647 cfg!(windows)
649 .then(move || {
650 which::which_in_global("python.bat", Some(&dir_clone))
651 .into_iter()
652 .flatten()
653 .collect::<Vec<_>>()
654 })
655 .into_iter()
656 .flatten(),
657 )
658 })
659 .into_iter()
660 .flatten()
661 })
662}
663
664fn find_all_minor(
669 implementation: Option<&ImplementationName>,
670 version_request: &VersionRequest,
671 dir: &Path,
672) -> impl Iterator<Item = PathBuf> + use<> {
673 match version_request {
674 &VersionRequest::Any
675 | VersionRequest::Default
676 | VersionRequest::Major(_, _)
677 | VersionRequest::Range(_, _) => {
678 let regex = if let Some(implementation) = implementation {
679 Regex::new(&format!(
680 r"^({}|python3)\.(?<minor>\d\d?)t?{}$",
681 regex::escape(&implementation.to_string()),
682 regex::escape(EXE_SUFFIX)
683 ))
684 .unwrap()
685 } else {
686 Regex::new(&format!(
687 r"^python3\.(?<minor>\d\d?)t?{}$",
688 regex::escape(EXE_SUFFIX)
689 ))
690 .unwrap()
691 };
692 let all_minors = fs_err::read_dir(dir)
693 .into_iter()
694 .flatten()
695 .flatten()
696 .map(|entry| entry.path())
697 .filter(move |path| {
698 let Some(filename) = path.file_name() else {
699 return false;
700 };
701 let Some(filename) = filename.to_str() else {
702 return false;
703 };
704 let Some(captures) = regex.captures(filename) else {
705 return false;
706 };
707
708 let minor = captures["minor"].parse().ok();
710 if let Some(minor) = minor {
711 if minor < 6 {
713 return false;
714 }
715 if !version_request.matches_major_minor(3, minor) {
717 return false;
718 }
719 }
720 true
721 })
722 .filter(|path| is_executable(path))
723 .collect::<Vec<_>>();
724 Either::Left(all_minors.into_iter())
725 }
726 VersionRequest::MajorMinor(_, _, _)
727 | VersionRequest::MajorMinorPatch(_, _, _, _)
728 | VersionRequest::MajorMinorPrerelease(_, _, _, _) => Either::Right(iter::empty()),
729 }
730}
731
732fn python_installations<'a>(
742 version: &'a VersionRequest,
743 implementation: Option<&'a ImplementationName>,
744 platform: PlatformRequest,
745 environments: EnvironmentPreference,
746 preference: PythonPreference,
747 cache: &'a Cache,
748 preview: Preview,
749) -> impl Iterator<Item = Result<PythonInstallation, Error>> + 'a {
750 let installations = python_installations_from_executables(
751 python_executables(
755 version,
756 implementation,
757 platform,
758 environments,
759 preference,
760 preview,
761 )
762 .filter_ok(move |(source, path)| {
763 source_satisfies_environment_preference(*source, path, environments)
764 }),
765 cache,
766 )
767 .filter_ok(move |installation| {
768 interpreter_satisfies_environment_preference(
769 installation.source,
770 &installation.interpreter,
771 environments,
772 )
773 })
774 .filter_ok(move |installation| {
775 let request = version.clone().into_request_for_source(installation.source);
776 if request.matches_interpreter(&installation.interpreter) {
777 true
778 } else {
779 debug!(
780 "Skipping interpreter at `{}` from {}: does not satisfy request `{request}`",
781 installation.interpreter.sys_executable().user_display(),
782 installation.source,
783 );
784 false
785 }
786 })
787 .filter_ok(move |installation| preference.allows_installation(installation));
788
789 if std::env::var(uv_static::EnvVars::UV_INTERNAL__TEST_PYTHON_MANAGED).is_ok() {
790 Either::Left(installations.map_ok(|mut installation| {
791 if installation.interpreter.is_managed() {
794 installation.source = PythonSource::Managed;
795 }
796 installation
797 }))
798 } else {
799 Either::Right(installations)
800 }
801}
802
803fn python_installations_from_executables<'a>(
805 executables: impl Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a,
806 cache: &'a Cache,
807) -> impl Iterator<Item = Result<PythonInstallation, Error>> + 'a {
808 executables.map(|result| match result {
809 Ok((source, path)) => Interpreter::query(&path, cache)
810 .map(|interpreter| PythonInstallation {
811 source,
812 interpreter,
813 })
814 .inspect(|installation| {
815 debug!(
816 "Found `{}` at `{}` ({source})",
817 installation.key(),
818 path.display()
819 );
820 })
821 .map_err(|err| Error::Query(Box::new(err), path, source))
822 .inspect_err(|err| debug!("{err}")),
823 Err(err) => Err(err),
824 })
825}
826
827fn interpreter_satisfies_environment_preference(
834 source: PythonSource,
835 interpreter: &Interpreter,
836 preference: EnvironmentPreference,
837) -> bool {
838 match (
839 preference,
840 interpreter.is_virtualenv() || (matches!(source, PythonSource::CondaPrefix)),
842 ) {
843 (EnvironmentPreference::Any, _) => true,
844 (EnvironmentPreference::OnlyVirtual, true) => true,
845 (EnvironmentPreference::OnlyVirtual, false) => {
846 debug!(
847 "Ignoring Python interpreter at `{}`: only virtual environments allowed",
848 interpreter.sys_executable().display()
849 );
850 false
851 }
852 (EnvironmentPreference::ExplicitSystem, true) => true,
853 (EnvironmentPreference::ExplicitSystem, false) => {
854 if matches!(
855 source,
856 PythonSource::ProvidedPath | PythonSource::ParentInterpreter
857 ) {
858 debug!(
859 "Allowing explicitly requested system Python interpreter at `{}`",
860 interpreter.sys_executable().display()
861 );
862 true
863 } else {
864 debug!(
865 "Ignoring Python interpreter at `{}`: system interpreter not explicitly requested",
866 interpreter.sys_executable().display()
867 );
868 false
869 }
870 }
871 (EnvironmentPreference::OnlySystem, true) => {
872 debug!(
873 "Ignoring Python interpreter at `{}`: system interpreter required",
874 interpreter.sys_executable().display()
875 );
876 false
877 }
878 (EnvironmentPreference::OnlySystem, false) => true,
879 }
880}
881
882fn source_satisfies_environment_preference(
889 source: PythonSource,
890 interpreter_path: &Path,
891 preference: EnvironmentPreference,
892) -> bool {
893 match preference {
894 EnvironmentPreference::Any => true,
895 EnvironmentPreference::OnlyVirtual => {
896 if source.is_maybe_virtualenv() {
897 true
898 } else {
899 debug!(
900 "Ignoring Python interpreter at `{}`: only virtual environments allowed",
901 interpreter_path.display()
902 );
903 false
904 }
905 }
906 EnvironmentPreference::ExplicitSystem => {
907 if source.is_maybe_virtualenv() {
908 true
909 } else {
910 debug!(
911 "Ignoring Python interpreter at `{}`: system interpreter not explicitly requested",
912 interpreter_path.display()
913 );
914 false
915 }
916 }
917 EnvironmentPreference::OnlySystem => {
918 if source.is_maybe_system() {
919 true
920 } else {
921 debug!(
922 "Ignoring Python interpreter at `{}`: system interpreter required",
923 interpreter_path.display()
924 );
925 false
926 }
927 }
928 }
929}
930
931impl Error {
935 pub fn is_critical(&self) -> bool {
936 match self {
937 Self::Query(err, _, source) => match &**err {
940 InterpreterError::Encode(_)
941 | InterpreterError::Io(_)
942 | InterpreterError::SpawnFailed { .. } => true,
943 InterpreterError::UnexpectedResponse(UnexpectedResponseError { path, .. })
944 | InterpreterError::StatusCode(StatusCodeError { path, .. }) => {
945 debug!(
946 "Skipping bad interpreter at {} from {source}: {err}",
947 path.display()
948 );
949 false
950 }
951 InterpreterError::QueryScript { path, err } => {
952 debug!(
953 "Skipping bad interpreter at {} from {source}: {err}",
954 path.display()
955 );
956 false
957 }
958 #[cfg(windows)]
959 InterpreterError::CorruptWindowsPackage { path, err } => {
960 debug!(
961 "Skipping bad interpreter at {} from {source}: {err}",
962 path.display()
963 );
964 false
965 }
966 InterpreterError::PermissionDenied { path, err } => {
967 debug!(
968 "Skipping unexecutable interpreter at {} from {source}: {err}",
969 path.display()
970 );
971 false
972 }
973 InterpreterError::NotFound(path)
974 | InterpreterError::BrokenLink(BrokenLink { path, .. }) => {
975 if matches!(source, PythonSource::ActiveEnvironment)
978 && uv_fs::is_virtualenv_executable(path)
979 {
980 true
981 } else {
982 trace!("Skipping missing interpreter at {}", path.display());
983 false
984 }
985 }
986 },
987 Self::VirtualEnv(VirtualEnvError::MissingPyVenvCfg(path)) => {
988 trace!("Skipping broken virtualenv at {}", path.display());
989 false
990 }
991 _ => true,
992 }
993 }
994}
995
996fn python_installation_from_executable(
998 path: &PathBuf,
999 cache: &Cache,
1000) -> Result<PythonInstallation, crate::interpreter::Error> {
1001 Ok(PythonInstallation {
1002 source: PythonSource::ProvidedPath,
1003 interpreter: Interpreter::query(path, cache)?,
1004 })
1005}
1006
1007fn python_installation_from_directory(
1009 path: &PathBuf,
1010 cache: &Cache,
1011) -> Result<PythonInstallation, crate::interpreter::Error> {
1012 let executable = virtualenv_python_executable(path);
1013 python_installation_from_executable(&executable, cache)
1014}
1015
1016fn python_installations_with_executable_name<'a>(
1018 name: &'a str,
1019 cache: &'a Cache,
1020) -> impl Iterator<Item = Result<PythonInstallation, Error>> + 'a {
1021 python_installations_from_executables(
1022 which_all(name)
1023 .into_iter()
1024 .flat_map(|inner| inner.map(|path| Ok((PythonSource::SearchPath, path)))),
1025 cache,
1026 )
1027}
1028
1029pub fn find_python_installations<'a>(
1031 request: &'a PythonRequest,
1032 environments: EnvironmentPreference,
1033 preference: PythonPreference,
1034 cache: &'a Cache,
1035 preview: Preview,
1036) -> Box<dyn Iterator<Item = Result<FindPythonResult, Error>> + 'a> {
1037 let sources = DiscoveryPreferences {
1038 python_preference: preference,
1039 environment_preference: environments,
1040 }
1041 .sources(request);
1042
1043 match request {
1044 PythonRequest::File(path) => Box::new(iter::once({
1045 if preference.allows_source(PythonSource::ProvidedPath) {
1046 debug!("Checking for Python interpreter at {request}");
1047 match python_installation_from_executable(path, cache) {
1048 Ok(installation) => Ok(Ok(installation)),
1049 Err(InterpreterError::NotFound(_) | InterpreterError::BrokenLink(_)) => {
1050 Ok(Err(PythonNotFound {
1051 request: request.clone(),
1052 python_preference: preference,
1053 environment_preference: environments,
1054 }))
1055 }
1056 Err(err) => Err(Error::Query(
1057 Box::new(err),
1058 path.clone(),
1059 PythonSource::ProvidedPath,
1060 )),
1061 }
1062 } else {
1063 Err(Error::SourceNotAllowed(
1064 request.clone(),
1065 PythonSource::ProvidedPath,
1066 preference,
1067 ))
1068 }
1069 })),
1070 PythonRequest::Directory(path) => Box::new(iter::once({
1071 if preference.allows_source(PythonSource::ProvidedPath) {
1072 debug!("Checking for Python interpreter in {request}");
1073 match python_installation_from_directory(path, cache) {
1074 Ok(installation) => Ok(Ok(installation)),
1075 Err(InterpreterError::NotFound(_) | InterpreterError::BrokenLink(_)) => {
1076 Ok(Err(PythonNotFound {
1077 request: request.clone(),
1078 python_preference: preference,
1079 environment_preference: environments,
1080 }))
1081 }
1082 Err(err) => Err(Error::Query(
1083 Box::new(err),
1084 path.clone(),
1085 PythonSource::ProvidedPath,
1086 )),
1087 }
1088 } else {
1089 Err(Error::SourceNotAllowed(
1090 request.clone(),
1091 PythonSource::ProvidedPath,
1092 preference,
1093 ))
1094 }
1095 })),
1096 PythonRequest::ExecutableName(name) => {
1097 if preference.allows_source(PythonSource::SearchPath) {
1098 debug!("Searching for Python interpreter with {request}");
1099 Box::new(
1100 python_installations_with_executable_name(name, cache)
1101 .filter_ok(move |installation| {
1102 interpreter_satisfies_environment_preference(
1103 installation.source,
1104 &installation.interpreter,
1105 environments,
1106 )
1107 })
1108 .map_ok(Ok),
1109 )
1110 } else {
1111 Box::new(iter::once(Err(Error::SourceNotAllowed(
1112 request.clone(),
1113 PythonSource::SearchPath,
1114 preference,
1115 ))))
1116 }
1117 }
1118 PythonRequest::Any => Box::new({
1119 debug!("Searching for any Python interpreter in {sources}");
1120 python_installations(
1121 &VersionRequest::Any,
1122 None,
1123 PlatformRequest::default(),
1124 environments,
1125 preference,
1126 cache,
1127 preview,
1128 )
1129 .map_ok(Ok)
1130 }),
1131 PythonRequest::Default => Box::new({
1132 debug!("Searching for default Python interpreter in {sources}");
1133 python_installations(
1134 &VersionRequest::Default,
1135 None,
1136 PlatformRequest::default(),
1137 environments,
1138 preference,
1139 cache,
1140 preview,
1141 )
1142 .map_ok(Ok)
1143 }),
1144 PythonRequest::Version(version) => {
1145 if let Err(err) = version.check_supported() {
1146 return Box::new(iter::once(Err(Error::InvalidVersionRequest(err))));
1147 }
1148 Box::new({
1149 debug!("Searching for {request} in {sources}");
1150 python_installations(
1151 version,
1152 None,
1153 PlatformRequest::default(),
1154 environments,
1155 preference,
1156 cache,
1157 preview,
1158 )
1159 .map_ok(Ok)
1160 })
1161 }
1162 PythonRequest::Implementation(implementation) => Box::new({
1163 debug!("Searching for a {request} interpreter in {sources}");
1164 python_installations(
1165 &VersionRequest::Default,
1166 Some(implementation),
1167 PlatformRequest::default(),
1168 environments,
1169 preference,
1170 cache,
1171 preview,
1172 )
1173 .filter_ok(|installation| implementation.matches_interpreter(&installation.interpreter))
1174 .map_ok(Ok)
1175 }),
1176 PythonRequest::ImplementationVersion(implementation, version) => {
1177 if let Err(err) = version.check_supported() {
1178 return Box::new(iter::once(Err(Error::InvalidVersionRequest(err))));
1179 }
1180 Box::new({
1181 debug!("Searching for {request} in {sources}");
1182 python_installations(
1183 version,
1184 Some(implementation),
1185 PlatformRequest::default(),
1186 environments,
1187 preference,
1188 cache,
1189 preview,
1190 )
1191 .filter_ok(|installation| {
1192 implementation.matches_interpreter(&installation.interpreter)
1193 })
1194 .map_ok(Ok)
1195 })
1196 }
1197 PythonRequest::Key(request) => {
1198 if let Some(version) = request.version() {
1199 if let Err(err) = version.check_supported() {
1200 return Box::new(iter::once(Err(Error::InvalidVersionRequest(err))));
1201 }
1202 }
1203
1204 Box::new({
1205 debug!("Searching for {request} in {sources}");
1206 python_installations(
1207 request.version().unwrap_or(&VersionRequest::Default),
1208 request.implementation(),
1209 request.platform(),
1210 environments,
1211 preference,
1212 cache,
1213 preview,
1214 )
1215 .filter_ok(move |installation| {
1216 request.satisfied_by_interpreter(&installation.interpreter)
1217 })
1218 .map_ok(Ok)
1219 })
1220 }
1221 }
1222}
1223
1224pub(crate) fn find_python_installation(
1229 request: &PythonRequest,
1230 environments: EnvironmentPreference,
1231 preference: PythonPreference,
1232 cache: &Cache,
1233 preview: Preview,
1234) -> Result<FindPythonResult, Error> {
1235 let installations =
1236 find_python_installations(request, environments, preference, cache, preview);
1237 let mut first_prerelease = None;
1238 let mut first_debug = None;
1239 let mut first_managed = None;
1240 let mut first_error = None;
1241 for result in installations {
1242 if !result.as_ref().err().is_none_or(Error::is_critical) {
1244 if first_error.is_none() {
1246 if let Err(err) = result {
1247 first_error = Some(err);
1248 }
1249 }
1250 continue;
1251 }
1252
1253 let Ok(Ok(ref installation)) = result else {
1255 return result;
1256 };
1257
1258 let has_default_executable_name = installation.interpreter.has_default_executable_name()
1264 && matches!(
1265 installation.source,
1266 PythonSource::SearchPath | PythonSource::SearchPathFirst
1267 );
1268
1269 if installation.python_version().pre().is_some()
1272 && !request.allows_prereleases()
1273 && !installation.source.allows_prereleases()
1274 && !has_default_executable_name
1275 {
1276 debug!("Skipping pre-release installation {}", installation.key());
1277 if first_prerelease.is_none() {
1278 first_prerelease = Some(installation.clone());
1279 }
1280 continue;
1281 }
1282
1283 if installation.key().variant().is_debug()
1286 && !request.allows_debug()
1287 && !installation.source.allows_debug()
1288 && !has_default_executable_name
1289 {
1290 debug!("Skipping debug installation {}", installation.key());
1291 if first_debug.is_none() {
1292 first_debug = Some(installation.clone());
1293 }
1294 continue;
1295 }
1296
1297 if installation.is_alternative_implementation()
1302 && !request.allows_alternative_implementations()
1303 && !installation.source.allows_alternative_implementations()
1304 && !has_default_executable_name
1305 {
1306 debug!("Skipping alternative implementation {}", installation.key());
1307 continue;
1308 }
1309
1310 if matches!(preference, PythonPreference::System) && installation.is_managed() {
1313 debug!(
1314 "Skipping managed installation {}: system installation preferred",
1315 installation.key()
1316 );
1317 if first_managed.is_none() {
1318 first_managed = Some(installation.clone());
1319 }
1320 continue;
1321 }
1322
1323 return result;
1325 }
1326
1327 if let Some(installation) = first_managed {
1330 debug!(
1331 "Allowing managed installation {}: no system installations",
1332 installation.key()
1333 );
1334 return Ok(Ok(installation));
1335 }
1336
1337 if let Some(installation) = first_debug {
1340 debug!(
1341 "Allowing debug installation {}: no non-debug installations",
1342 installation.key()
1343 );
1344 return Ok(Ok(installation));
1345 }
1346
1347 if let Some(installation) = first_prerelease {
1349 debug!(
1350 "Allowing pre-release installation {}: no stable installations",
1351 installation.key()
1352 );
1353 return Ok(Ok(installation));
1354 }
1355
1356 if let Some(err) = first_error {
1359 return Err(err);
1360 }
1361
1362 Ok(Err(PythonNotFound {
1363 request: request.clone(),
1364 environment_preference: environments,
1365 python_preference: preference,
1366 }))
1367}
1368
1369#[instrument(skip_all, fields(request))]
1383pub(crate) async fn find_best_python_installation(
1384 request: &PythonRequest,
1385 environments: EnvironmentPreference,
1386 preference: PythonPreference,
1387 downloads_enabled: bool,
1388 download_list: &ManagedPythonDownloadList,
1389 client: &BaseClient,
1390 retry_policy: &ExponentialBackoff,
1391 cache: &Cache,
1392 reporter: Option<&dyn crate::downloads::Reporter>,
1393 python_install_mirror: Option<&str>,
1394 pypy_install_mirror: Option<&str>,
1395 preview: Preview,
1396) -> Result<PythonInstallation, crate::Error> {
1397 debug!("Starting Python discovery for {request}");
1398 let original_request = request;
1399
1400 let mut previous_fetch_failed = false;
1401
1402 let request_without_patch = match request {
1403 PythonRequest::Version(version) => {
1404 if version.has_patch() {
1405 Some(PythonRequest::Version(version.clone().without_patch()))
1406 } else {
1407 None
1408 }
1409 }
1410 PythonRequest::ImplementationVersion(implementation, version) => Some(
1411 PythonRequest::ImplementationVersion(*implementation, version.clone().without_patch()),
1412 ),
1413 _ => None,
1414 };
1415
1416 for (attempt, request) in iter::once(original_request)
1417 .chain(request_without_patch.iter())
1418 .chain(iter::once(&PythonRequest::Default))
1419 .enumerate()
1420 {
1421 debug!(
1422 "Looking for {request}{}",
1423 if request != original_request {
1424 format!(" attempt {attempt} (fallback after failing to find: {original_request})")
1425 } else {
1426 String::new()
1427 }
1428 );
1429 let result = find_python_installation(request, environments, preference, cache, preview);
1430 let error = match result {
1431 Ok(Ok(installation)) => {
1432 warn_on_unsupported_python(installation.interpreter());
1433 return Ok(installation);
1434 }
1435 Ok(Err(error)) => error.into(),
1437 Err(error) if !error.is_critical() => error.into(),
1438 Err(error) => return Err(error.into()),
1439 };
1440
1441 if downloads_enabled
1443 && !previous_fetch_failed
1444 && let Some(download_request) = PythonDownloadRequest::from_request(request)
1445 {
1446 let download = download_request
1447 .clone()
1448 .fill()
1449 .map(|request| download_list.find(&request));
1450
1451 let result = match download {
1452 Ok(Ok(download)) => PythonInstallation::fetch(
1453 download,
1454 client,
1455 retry_policy,
1456 cache,
1457 reporter,
1458 python_install_mirror,
1459 pypy_install_mirror,
1460 )
1461 .await
1462 .map(Some),
1463 Ok(Err(crate::downloads::Error::NoDownloadFound(_))) => Ok(None),
1464 Ok(Err(error)) => Err(error.into()),
1465 Err(error) => Err(error.into()),
1466 };
1467 if let Ok(Some(installation)) = result {
1468 return Ok(installation);
1469 }
1470 if let Err(error) = result {
1478 if matches!(request, PythonRequest::Default | PythonRequest::Any) {
1482 return Err(error);
1483 }
1484
1485 let mut error_chain = String::new();
1486 let error = anyhow::Error::from(error).context(format!(
1488 "A managed Python download is available for {request}, but an error occurred when attempting to download it."
1489 ));
1490 uv_warnings::write_error_chain(
1491 error.as_ref(),
1492 &mut error_chain,
1493 "warning",
1494 AnsiColors::Yellow,
1495 )
1496 .unwrap();
1497 anstream::eprint!("{}", error_chain);
1498 previous_fetch_failed = true;
1499 }
1500 }
1501
1502 if matches!(request, PythonRequest::Default | PythonRequest::Any) {
1508 return Err(match error {
1509 crate::Error::MissingPython(err, _) => PythonNotFound {
1510 request: original_request.clone(),
1512 python_preference: err.python_preference,
1513 environment_preference: err.environment_preference,
1514 }
1515 .into(),
1516 other => other,
1517 });
1518 }
1519 }
1520
1521 unreachable!("The loop should have terminated when it reached PythonRequest::Default");
1522}
1523
1524fn warn_on_unsupported_python(interpreter: &Interpreter) {
1526 if interpreter.python_tuple() < (3, 8) {
1528 warn_user_once!(
1529 "uv is only compatible with Python >=3.8, found Python {}",
1530 interpreter.python_version()
1531 );
1532 }
1533}
1534
1535#[cfg(windows)]
1552pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
1553 use std::os::windows::fs::MetadataExt;
1554 use std::os::windows::prelude::OsStrExt;
1555 use windows::Win32::Foundation::CloseHandle;
1556 use windows::Win32::Storage::FileSystem::{
1557 CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS,
1558 FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_MODE, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
1559 OPEN_EXISTING,
1560 };
1561 use windows::Win32::System::IO::DeviceIoControl;
1562 use windows::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT;
1563 use windows::core::PCWSTR;
1564
1565 if !path.is_absolute() {
1567 return false;
1568 }
1569
1570 let mut components = path.components().rev();
1573
1574 if !components
1576 .next()
1577 .and_then(|component| component.as_os_str().to_str())
1578 .is_some_and(|component| {
1579 component.starts_with("python")
1580 && std::path::Path::new(component)
1581 .extension()
1582 .is_some_and(|ext| ext.eq_ignore_ascii_case("exe"))
1583 })
1584 {
1585 return false;
1586 }
1587
1588 if components
1590 .next()
1591 .is_none_or(|component| component.as_os_str() != "WindowsApps")
1592 {
1593 return false;
1594 }
1595
1596 if components
1598 .next()
1599 .is_none_or(|component| component.as_os_str() != "Microsoft")
1600 {
1601 return false;
1602 }
1603
1604 let Ok(md) = fs_err::symlink_metadata(path) else {
1606 return false;
1607 };
1608 if md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT.0 == 0 {
1609 return false;
1610 }
1611
1612 let mut path_encoded = path
1613 .as_os_str()
1614 .encode_wide()
1615 .chain(std::iter::once(0))
1616 .collect::<Vec<_>>();
1617
1618 #[allow(unsafe_code)]
1620 let reparse_handle = unsafe {
1621 CreateFileW(
1622 PCWSTR(path_encoded.as_mut_ptr()),
1623 0,
1624 FILE_SHARE_MODE(0),
1625 None,
1626 OPEN_EXISTING,
1627 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
1628 None,
1629 )
1630 };
1631
1632 let Ok(reparse_handle) = reparse_handle else {
1633 return false;
1634 };
1635
1636 let mut buf = [0u16; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize];
1637 let mut bytes_returned = 0;
1638
1639 #[allow(unsafe_code, clippy::cast_possible_truncation)]
1641 let success = unsafe {
1642 DeviceIoControl(
1643 reparse_handle,
1644 FSCTL_GET_REPARSE_POINT,
1645 None,
1646 0,
1647 Some(buf.as_mut_ptr().cast()),
1648 buf.len() as u32 * 2,
1649 Some(&raw mut bytes_returned),
1650 None,
1651 )
1652 .is_ok()
1653 };
1654
1655 #[allow(unsafe_code)]
1657 unsafe {
1658 let _ = CloseHandle(reparse_handle);
1659 }
1660
1661 if !success {
1663 return false;
1664 }
1665
1666 let reparse_point = String::from_utf16_lossy(&buf[..bytes_returned as usize]);
1667 reparse_point.contains("\\AppInstallerPythonRedirector.exe")
1668}
1669
1670#[cfg(not(windows))]
1674fn is_windows_store_shim(_path: &Path) -> bool {
1675 false
1676}
1677
1678impl PythonVariant {
1679 fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
1680 match self {
1681 Self::Default => {
1682 if (interpreter.python_major(), interpreter.python_minor()) >= (3, 14) {
1685 true
1688 } else {
1689 !interpreter.gil_disabled()
1692 }
1693 }
1694 Self::Debug => interpreter.debug_enabled(),
1695 Self::Freethreaded => interpreter.gil_disabled(),
1696 Self::FreethreadedDebug => interpreter.gil_disabled() && interpreter.debug_enabled(),
1697 Self::Gil => !interpreter.gil_disabled(),
1698 Self::GilDebug => !interpreter.gil_disabled() && interpreter.debug_enabled(),
1699 }
1700 }
1701
1702 pub fn executable_suffix(self) -> &'static str {
1706 match self {
1707 Self::Default => "",
1708 Self::Debug => "d",
1709 Self::Freethreaded => "t",
1710 Self::FreethreadedDebug => "td",
1711 Self::Gil => "",
1712 Self::GilDebug => "d",
1713 }
1714 }
1715
1716 pub fn display_suffix(self) -> &'static str {
1718 match self {
1719 Self::Default => "",
1720 Self::Debug => "+debug",
1721 Self::Freethreaded => "+freethreaded",
1722 Self::FreethreadedDebug => "+freethreaded+debug",
1723 Self::Gil => "+gil",
1724 Self::GilDebug => "+gil+debug",
1725 }
1726 }
1727
1728 pub fn lib_suffix(self) -> &'static str {
1731 match self {
1732 Self::Default | Self::Debug | Self::Gil | Self::GilDebug => "",
1733 Self::Freethreaded | Self::FreethreadedDebug => "t",
1734 }
1735 }
1736
1737 pub fn is_freethreaded(self) -> bool {
1738 match self {
1739 Self::Default | Self::Debug | Self::Gil | Self::GilDebug => false,
1740 Self::Freethreaded | Self::FreethreadedDebug => true,
1741 }
1742 }
1743
1744 pub fn is_debug(self) -> bool {
1745 match self {
1746 Self::Default | Self::Freethreaded | Self::Gil => false,
1747 Self::Debug | Self::FreethreadedDebug | Self::GilDebug => true,
1748 }
1749 }
1750}
1751impl PythonRequest {
1752 pub fn parse(value: &str) -> Self {
1760 let lowercase_value = &value.to_ascii_lowercase();
1761
1762 if lowercase_value == "any" {
1764 return Self::Any;
1765 }
1766 if lowercase_value == "default" {
1767 return Self::Default;
1768 }
1769
1770 let abstract_version_prefixes = ["python", ""];
1772 let all_implementation_names =
1773 ImplementationName::long_names().chain(ImplementationName::short_names());
1774 if let Ok(Some(request)) = Self::parse_versions_and_implementations(
1777 abstract_version_prefixes,
1778 all_implementation_names,
1779 lowercase_value,
1780 ) {
1781 return request;
1782 }
1783
1784 let value_as_path = PathBuf::from(value);
1785 if value_as_path.is_dir() {
1787 return Self::Directory(value_as_path);
1788 }
1789 if value_as_path.is_file() {
1791 return Self::File(value_as_path);
1792 }
1793
1794 #[cfg(windows)]
1796 if value_as_path.extension().is_none() {
1797 let value_as_path = value_as_path.with_extension(EXE_SUFFIX);
1798 if value_as_path.is_file() {
1799 return Self::File(value_as_path);
1800 }
1801 }
1802
1803 #[cfg(test)]
1808 if value_as_path.is_relative() {
1809 if let Ok(current_dir) = crate::current_dir() {
1810 let relative = current_dir.join(&value_as_path);
1811 if relative.is_dir() {
1812 return Self::Directory(relative);
1813 }
1814 if relative.is_file() {
1815 return Self::File(relative);
1816 }
1817 }
1818 }
1819 if value.contains(std::path::MAIN_SEPARATOR) {
1822 return Self::File(value_as_path);
1823 }
1824 if cfg!(windows) && value.contains('/') {
1827 return Self::File(value_as_path);
1828 }
1829 if let Ok(request) = PythonDownloadRequest::from_str(value) {
1830 return Self::Key(request);
1831 }
1832 Self::ExecutableName(value.to_string())
1835 }
1836
1837 pub fn try_from_tool_name(value: &str) -> Result<Option<Self>, Error> {
1851 let lowercase_value = &value.to_ascii_lowercase();
1852 let abstract_version_prefixes = if cfg!(windows) {
1854 &["python", "pythonw"][..]
1855 } else {
1856 &["python"][..]
1857 };
1858 if abstract_version_prefixes.contains(&lowercase_value.as_str()) {
1860 return Ok(Some(Self::Default));
1861 }
1862 Self::parse_versions_and_implementations(
1863 abstract_version_prefixes.iter().copied(),
1864 ImplementationName::long_names(),
1865 lowercase_value,
1866 )
1867 }
1868
1869 fn parse_versions_and_implementations<'a>(
1878 abstract_version_prefixes: impl IntoIterator<Item = &'a str>,
1880 implementation_names: impl IntoIterator<Item = &'a str>,
1882 lowercase_value: &str,
1884 ) -> Result<Option<Self>, Error> {
1885 for prefix in abstract_version_prefixes {
1886 if let Some(version_request) =
1887 Self::try_split_prefix_and_version(prefix, lowercase_value)?
1888 {
1889 return Ok(Some(Self::Version(version_request)));
1893 }
1894 }
1895 for implementation in implementation_names {
1896 if lowercase_value == implementation {
1897 return Ok(Some(Self::Implementation(
1898 ImplementationName::from_str(implementation).unwrap(),
1901 )));
1902 }
1903 if let Some(version_request) =
1904 Self::try_split_prefix_and_version(implementation, lowercase_value)?
1905 {
1906 return Ok(Some(Self::ImplementationVersion(
1908 ImplementationName::from_str(implementation).unwrap(),
1910 version_request,
1911 )));
1912 }
1913 }
1914 Ok(None)
1915 }
1916
1917 fn try_split_prefix_and_version(
1928 prefix: &str,
1929 lowercase_value: &str,
1930 ) -> Result<Option<VersionRequest>, Error> {
1931 if lowercase_value.starts_with('@') {
1932 return Err(Error::InvalidVersionRequest(lowercase_value.to_string()));
1933 }
1934 let Some(rest) = lowercase_value.strip_prefix(prefix) else {
1935 return Ok(None);
1936 };
1937 if rest.is_empty() {
1939 return Ok(None);
1940 }
1941 if let Some(after_at) = rest.strip_prefix('@') {
1944 if after_at == "latest" {
1945 return Err(Error::LatestVersionRequest);
1948 }
1949 return after_at.parse().map(Some);
1950 }
1951 Ok(rest.parse().ok())
1954 }
1955
1956 pub fn includes_patch(&self) -> bool {
1958 match self {
1959 Self::Default => false,
1960 Self::Any => false,
1961 Self::Version(version_request) => version_request.patch().is_some(),
1962 Self::Directory(..) => false,
1963 Self::File(..) => false,
1964 Self::ExecutableName(..) => false,
1965 Self::Implementation(..) => false,
1966 Self::ImplementationVersion(_, version) => version.patch().is_some(),
1967 Self::Key(request) => request
1968 .version
1969 .as_ref()
1970 .is_some_and(|request| request.patch().is_some()),
1971 }
1972 }
1973
1974 pub fn includes_prerelease(&self) -> bool {
1976 match self {
1977 Self::Default => false,
1978 Self::Any => false,
1979 Self::Version(version_request) => version_request.prerelease().is_some(),
1980 Self::Directory(..) => false,
1981 Self::File(..) => false,
1982 Self::ExecutableName(..) => false,
1983 Self::Implementation(..) => false,
1984 Self::ImplementationVersion(_, version) => version.prerelease().is_some(),
1985 Self::Key(request) => request
1986 .version
1987 .as_ref()
1988 .is_some_and(|request| request.prerelease().is_some()),
1989 }
1990 }
1991
1992 pub fn satisfied(&self, interpreter: &Interpreter, cache: &Cache) -> bool {
1994 fn is_same_executable(path1: &Path, path2: &Path) -> bool {
1996 path1 == path2 || is_same_file(path1, path2).unwrap_or(false)
1997 }
1998
1999 match self {
2000 Self::Default | Self::Any => true,
2001 Self::Version(version_request) => version_request.matches_interpreter(interpreter),
2002 Self::Directory(directory) => {
2003 is_same_executable(directory, interpreter.sys_prefix())
2005 || is_same_executable(
2006 virtualenv_python_executable(directory).as_path(),
2007 interpreter.sys_executable(),
2008 )
2009 }
2010 Self::File(file) => {
2011 if is_same_executable(interpreter.sys_executable(), file) {
2013 return true;
2014 }
2015 if interpreter
2017 .sys_base_executable()
2018 .is_some_and(|sys_base_executable| {
2019 is_same_executable(sys_base_executable, file)
2020 })
2021 {
2022 return true;
2023 }
2024 if cfg!(windows) {
2029 if let Ok(file_interpreter) = Interpreter::query(file, cache) {
2030 if let (Some(file_base), Some(interpreter_base)) = (
2031 file_interpreter.sys_base_executable(),
2032 interpreter.sys_base_executable(),
2033 ) {
2034 if is_same_executable(file_base, interpreter_base) {
2035 return true;
2036 }
2037 }
2038 }
2039 }
2040 false
2041 }
2042 Self::ExecutableName(name) => {
2043 if interpreter
2045 .sys_executable()
2046 .file_name()
2047 .is_some_and(|filename| filename == name.as_str())
2048 {
2049 return true;
2050 }
2051 if interpreter
2053 .sys_base_executable()
2054 .and_then(|executable| executable.file_name())
2055 .is_some_and(|file_name| file_name == name.as_str())
2056 {
2057 return true;
2058 }
2059 if which(name)
2062 .ok()
2063 .as_ref()
2064 .and_then(|executable| executable.file_name())
2065 .is_some_and(|file_name| file_name == name.as_str())
2066 {
2067 return true;
2068 }
2069 false
2070 }
2071 Self::Implementation(implementation) => interpreter
2072 .implementation_name()
2073 .eq_ignore_ascii_case(implementation.into()),
2074 Self::ImplementationVersion(implementation, version) => {
2075 version.matches_interpreter(interpreter)
2076 && interpreter
2077 .implementation_name()
2078 .eq_ignore_ascii_case(implementation.into())
2079 }
2080 Self::Key(request) => request.satisfied_by_interpreter(interpreter),
2081 }
2082 }
2083
2084 pub(crate) fn allows_prereleases(&self) -> bool {
2086 match self {
2087 Self::Default => false,
2088 Self::Any => true,
2089 Self::Version(version) => version.allows_prereleases(),
2090 Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
2091 Self::Implementation(_) => false,
2092 Self::ImplementationVersion(_, _) => true,
2093 Self::Key(request) => request.allows_prereleases(),
2094 }
2095 }
2096
2097 pub(crate) fn allows_debug(&self) -> bool {
2099 match self {
2100 Self::Default => false,
2101 Self::Any => true,
2102 Self::Version(version) => version.is_debug(),
2103 Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
2104 Self::Implementation(_) => false,
2105 Self::ImplementationVersion(_, _) => true,
2106 Self::Key(request) => request.allows_debug(),
2107 }
2108 }
2109
2110 pub(crate) fn allows_alternative_implementations(&self) -> bool {
2112 match self {
2113 Self::Default => false,
2114 Self::Any => true,
2115 Self::Version(_) => false,
2116 Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
2117 Self::Implementation(implementation)
2118 | Self::ImplementationVersion(implementation, _) => {
2119 !matches!(implementation, ImplementationName::CPython)
2120 }
2121 Self::Key(request) => request.allows_alternative_implementations(),
2122 }
2123 }
2124
2125 pub(crate) fn is_explicit_system(&self) -> bool {
2126 matches!(self, Self::File(_) | Self::Directory(_))
2127 }
2128
2129 pub fn to_canonical_string(&self) -> String {
2133 match self {
2134 Self::Any => "any".to_string(),
2135 Self::Default => "default".to_string(),
2136 Self::Version(version) => version.to_string(),
2137 Self::Directory(path) => path.display().to_string(),
2138 Self::File(path) => path.display().to_string(),
2139 Self::ExecutableName(name) => name.clone(),
2140 Self::Implementation(implementation) => implementation.to_string(),
2141 Self::ImplementationVersion(implementation, version) => {
2142 format!("{implementation}@{version}")
2143 }
2144 Self::Key(request) => request.to_string(),
2145 }
2146 }
2147
2148 pub fn as_pep440_version(&self) -> Option<Version> {
2152 match self {
2153 Self::Version(v) | Self::ImplementationVersion(_, v) => v.as_pep440_version(),
2154 Self::Key(download_request) => download_request
2155 .version()
2156 .and_then(VersionRequest::as_pep440_version),
2157 Self::Default
2158 | Self::Any
2159 | Self::Directory(_)
2160 | Self::File(_)
2161 | Self::ExecutableName(_)
2162 | Self::Implementation(_) => None,
2163 }
2164 }
2165
2166 pub fn as_version_specifiers(&self) -> Option<VersionSpecifiers> {
2172 match self {
2173 Self::Version(version) | Self::ImplementationVersion(_, version) => {
2174 version.as_version_specifiers()
2175 }
2176 Self::Key(download_request) => download_request
2177 .version()
2178 .and_then(VersionRequest::as_version_specifiers),
2179 Self::Default
2180 | Self::Any
2181 | Self::Directory(_)
2182 | Self::File(_)
2183 | Self::ExecutableName(_)
2184 | Self::Implementation(_) => None,
2185 }
2186 }
2187
2188 pub fn intersects_requires_python(&self, requires_python: &RequiresPython) -> bool {
2194 let Some(specifiers) = self.as_version_specifiers() else {
2195 return true;
2196 };
2197
2198 let request_range = release_specifiers_to_ranges(specifiers);
2199 let requires_python_range =
2200 release_specifiers_to_ranges(requires_python.specifiers().clone());
2201 !request_range
2202 .intersection(&requires_python_range)
2203 .is_empty()
2204 }
2205}
2206
2207impl PythonSource {
2208 pub fn is_managed(self) -> bool {
2209 matches!(self, Self::Managed)
2210 }
2211
2212 pub(crate) fn allows_prereleases(self) -> bool {
2214 match self {
2215 Self::Managed | Self::Registry | Self::MicrosoftStore => false,
2216 Self::SearchPath
2217 | Self::SearchPathFirst
2218 | Self::CondaPrefix
2219 | Self::BaseCondaPrefix
2220 | Self::ProvidedPath
2221 | Self::ParentInterpreter
2222 | Self::ActiveEnvironment
2223 | Self::DiscoveredEnvironment => true,
2224 }
2225 }
2226
2227 pub(crate) fn allows_debug(self) -> bool {
2229 match self {
2230 Self::Managed | Self::Registry | Self::MicrosoftStore => false,
2231 Self::SearchPath
2232 | Self::SearchPathFirst
2233 | Self::CondaPrefix
2234 | Self::BaseCondaPrefix
2235 | Self::ProvidedPath
2236 | Self::ParentInterpreter
2237 | Self::ActiveEnvironment
2238 | Self::DiscoveredEnvironment => true,
2239 }
2240 }
2241
2242 pub(crate) fn allows_alternative_implementations(self) -> bool {
2244 match self {
2245 Self::Managed
2246 | Self::Registry
2247 | Self::SearchPath
2248 | Self::SearchPathFirst
2251 | Self::MicrosoftStore => false,
2252 Self::CondaPrefix
2253 | Self::BaseCondaPrefix
2254 | Self::ProvidedPath
2255 | Self::ParentInterpreter
2256 | Self::ActiveEnvironment
2257 | Self::DiscoveredEnvironment => true,
2258 }
2259 }
2260
2261 pub(crate) fn is_maybe_virtualenv(self) -> bool {
2273 match self {
2274 Self::ProvidedPath
2275 | Self::ActiveEnvironment
2276 | Self::DiscoveredEnvironment
2277 | Self::CondaPrefix
2278 | Self::BaseCondaPrefix
2279 | Self::ParentInterpreter
2280 | Self::SearchPathFirst => true,
2281 Self::Managed | Self::SearchPath | Self::Registry | Self::MicrosoftStore => false,
2282 }
2283 }
2284
2285 pub(crate) fn is_explicit(self) -> bool {
2288 match self {
2289 Self::ProvidedPath
2290 | Self::ParentInterpreter
2291 | Self::ActiveEnvironment
2292 | Self::CondaPrefix => true,
2293 Self::Managed
2294 | Self::DiscoveredEnvironment
2295 | Self::SearchPath
2296 | Self::SearchPathFirst
2297 | Self::Registry
2298 | Self::MicrosoftStore
2299 | Self::BaseCondaPrefix => false,
2300 }
2301 }
2302
2303 pub(crate) fn is_maybe_system(self) -> bool {
2305 match self {
2306 Self::CondaPrefix
2307 | Self::BaseCondaPrefix
2308 | Self::ParentInterpreter
2309 | Self::ProvidedPath
2310 | Self::Managed
2311 | Self::SearchPath
2312 | Self::SearchPathFirst
2313 | Self::Registry
2314 | Self::MicrosoftStore => true,
2315 Self::ActiveEnvironment | Self::DiscoveredEnvironment => false,
2316 }
2317 }
2318}
2319
2320impl PythonPreference {
2321 fn allows_source(self, source: PythonSource) -> bool {
2322 if !matches!(
2324 source,
2325 PythonSource::Managed | PythonSource::SearchPath | PythonSource::Registry
2326 ) {
2327 return true;
2328 }
2329
2330 match self {
2331 Self::OnlyManaged => matches!(source, PythonSource::Managed),
2332 Self::Managed | Self::System => matches!(
2333 source,
2334 PythonSource::Managed | PythonSource::SearchPath | PythonSource::Registry
2335 ),
2336 Self::OnlySystem => {
2337 matches!(source, PythonSource::SearchPath | PythonSource::Registry)
2338 }
2339 }
2340 }
2341
2342 pub(crate) fn allows_managed(self) -> bool {
2343 match self {
2344 Self::OnlySystem => false,
2345 Self::Managed | Self::System | Self::OnlyManaged => true,
2346 }
2347 }
2348
2349 pub fn allows_interpreter(self, interpreter: &Interpreter) -> bool {
2354 match self {
2355 Self::OnlyManaged => interpreter.is_managed(),
2356 Self::OnlySystem => !interpreter.is_managed(),
2357 Self::Managed | Self::System => true,
2358 }
2359 }
2360
2361 pub fn allows_installation(self, installation: &PythonInstallation) -> bool {
2369 let source = installation.source;
2370 let interpreter = &installation.interpreter;
2371
2372 match self {
2373 Self::OnlyManaged => {
2374 if self.allows_interpreter(interpreter) {
2375 true
2376 } else if source.is_explicit() {
2377 debug!(
2378 "Allowing unmanaged Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
2379 interpreter.sys_executable().display()
2380 );
2381 true
2382 } else {
2383 debug!(
2384 "Ignoring Python interpreter at `{}`: only managed interpreters allowed",
2385 interpreter.sys_executable().display()
2386 );
2387 false
2388 }
2389 }
2390 Self::Managed | Self::System => true,
2392 Self::OnlySystem => {
2393 if self.allows_interpreter(interpreter) {
2394 true
2395 } else if source.is_explicit() {
2396 debug!(
2397 "Allowing managed Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
2398 interpreter.sys_executable().display()
2399 );
2400 true
2401 } else {
2402 debug!(
2403 "Ignoring Python interpreter at `{}`: only system interpreters allowed",
2404 interpreter.sys_executable().display()
2405 );
2406 false
2407 }
2408 }
2409 }
2410 }
2411
2412 #[must_use]
2417 pub fn with_system_flag(self, system: bool) -> Self {
2418 match self {
2419 Self::OnlyManaged => self,
2424 Self::Managed => {
2425 if system {
2426 Self::System
2427 } else {
2428 self
2429 }
2430 }
2431 Self::System => self,
2432 Self::OnlySystem => self,
2433 }
2434 }
2435}
2436
2437impl PythonDownloads {
2438 pub fn is_automatic(self) -> bool {
2439 matches!(self, Self::Automatic)
2440 }
2441}
2442
2443impl EnvironmentPreference {
2444 pub fn from_system_flag(system: bool, mutable: bool) -> Self {
2445 match (system, mutable) {
2446 (true, _) => Self::OnlySystem,
2448 (false, true) => Self::ExplicitSystem,
2450 (false, false) => Self::Any,
2452 }
2453 }
2454}
2455
2456#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
2457pub(crate) struct ExecutableName {
2458 implementation: Option<ImplementationName>,
2459 major: Option<u8>,
2460 minor: Option<u8>,
2461 patch: Option<u8>,
2462 prerelease: Option<Prerelease>,
2463 variant: PythonVariant,
2464}
2465
2466#[derive(Debug, Clone, PartialEq, Eq)]
2467struct ExecutableNameComparator<'a> {
2468 name: ExecutableName,
2469 request: &'a VersionRequest,
2470 implementation: Option<&'a ImplementationName>,
2471}
2472
2473impl Ord for ExecutableNameComparator<'_> {
2474 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2478 let name_ordering = if self.implementation.is_some() {
2481 std::cmp::Ordering::Greater
2482 } else {
2483 std::cmp::Ordering::Less
2484 };
2485 if self.name.implementation.is_none() && other.name.implementation.is_some() {
2486 return name_ordering.reverse();
2487 }
2488 if self.name.implementation.is_some() && other.name.implementation.is_none() {
2489 return name_ordering;
2490 }
2491 let ordering = self.name.implementation.cmp(&other.name.implementation);
2493 if ordering != std::cmp::Ordering::Equal {
2494 return ordering;
2495 }
2496 let ordering = self.name.major.cmp(&other.name.major);
2497 let is_default_request =
2498 matches!(self.request, VersionRequest::Any | VersionRequest::Default);
2499 if ordering != std::cmp::Ordering::Equal {
2500 return if is_default_request {
2501 ordering.reverse()
2502 } else {
2503 ordering
2504 };
2505 }
2506 let ordering = self.name.minor.cmp(&other.name.minor);
2507 if ordering != std::cmp::Ordering::Equal {
2508 return if is_default_request {
2509 ordering.reverse()
2510 } else {
2511 ordering
2512 };
2513 }
2514 let ordering = self.name.patch.cmp(&other.name.patch);
2515 if ordering != std::cmp::Ordering::Equal {
2516 return if is_default_request {
2517 ordering.reverse()
2518 } else {
2519 ordering
2520 };
2521 }
2522 let ordering = self.name.prerelease.cmp(&other.name.prerelease);
2523 if ordering != std::cmp::Ordering::Equal {
2524 return if is_default_request {
2525 ordering.reverse()
2526 } else {
2527 ordering
2528 };
2529 }
2530 let ordering = self.name.variant.cmp(&other.name.variant);
2531 if ordering != std::cmp::Ordering::Equal {
2532 return if is_default_request {
2533 ordering.reverse()
2534 } else {
2535 ordering
2536 };
2537 }
2538 ordering
2539 }
2540}
2541
2542impl PartialOrd for ExecutableNameComparator<'_> {
2543 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2544 Some(self.cmp(other))
2545 }
2546}
2547
2548impl ExecutableName {
2549 #[must_use]
2550 fn with_implementation(mut self, implementation: ImplementationName) -> Self {
2551 self.implementation = Some(implementation);
2552 self
2553 }
2554
2555 #[must_use]
2556 fn with_major(mut self, major: u8) -> Self {
2557 self.major = Some(major);
2558 self
2559 }
2560
2561 #[must_use]
2562 fn with_minor(mut self, minor: u8) -> Self {
2563 self.minor = Some(minor);
2564 self
2565 }
2566
2567 #[must_use]
2568 fn with_patch(mut self, patch: u8) -> Self {
2569 self.patch = Some(patch);
2570 self
2571 }
2572
2573 #[must_use]
2574 fn with_prerelease(mut self, prerelease: Prerelease) -> Self {
2575 self.prerelease = Some(prerelease);
2576 self
2577 }
2578
2579 #[must_use]
2580 fn with_variant(mut self, variant: PythonVariant) -> Self {
2581 self.variant = variant;
2582 self
2583 }
2584
2585 fn into_comparator<'a>(
2586 self,
2587 request: &'a VersionRequest,
2588 implementation: Option<&'a ImplementationName>,
2589 ) -> ExecutableNameComparator<'a> {
2590 ExecutableNameComparator {
2591 name: self,
2592 request,
2593 implementation,
2594 }
2595 }
2596}
2597
2598impl fmt::Display for ExecutableName {
2599 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2600 if let Some(implementation) = self.implementation {
2601 write!(f, "{implementation}")?;
2602 } else {
2603 f.write_str("python")?;
2604 }
2605 if let Some(major) = self.major {
2606 write!(f, "{major}")?;
2607 if let Some(minor) = self.minor {
2608 write!(f, ".{minor}")?;
2609 if let Some(patch) = self.patch {
2610 write!(f, ".{patch}")?;
2611 }
2612 }
2613 }
2614 if let Some(prerelease) = &self.prerelease {
2615 write!(f, "{prerelease}")?;
2616 }
2617 f.write_str(self.variant.executable_suffix())?;
2618 f.write_str(EXE_SUFFIX)?;
2619 Ok(())
2620 }
2621}
2622
2623impl VersionRequest {
2624 #[must_use]
2626 pub fn only_minor(self) -> Self {
2627 match self {
2628 Self::Any => self,
2629 Self::Default => self,
2630 Self::Range(specifiers, variant) => Self::Range(
2631 specifiers
2632 .into_iter()
2633 .map(|s| s.only_minor_release())
2634 .collect(),
2635 variant,
2636 ),
2637 Self::Major(..) => self,
2638 Self::MajorMinor(..) => self,
2639 Self::MajorMinorPatch(major, minor, _, variant)
2640 | Self::MajorMinorPrerelease(major, minor, _, variant) => {
2641 Self::MajorMinor(major, minor, variant)
2642 }
2643 }
2644 }
2645
2646 pub(crate) fn executable_names(
2648 &self,
2649 implementation: Option<&ImplementationName>,
2650 ) -> Vec<ExecutableName> {
2651 let prerelease = if let Self::MajorMinorPrerelease(_, _, prerelease, _) = self {
2652 Some(prerelease)
2654 } else {
2655 None
2656 };
2657
2658 let mut names = Vec::new();
2660 names.push(ExecutableName::default());
2661
2662 if let Some(major) = self.major() {
2664 names.push(ExecutableName::default().with_major(major));
2666 if let Some(minor) = self.minor() {
2667 names.push(
2669 ExecutableName::default()
2670 .with_major(major)
2671 .with_minor(minor),
2672 );
2673 if let Some(patch) = self.patch() {
2674 names.push(
2676 ExecutableName::default()
2677 .with_major(major)
2678 .with_minor(minor)
2679 .with_patch(patch),
2680 );
2681 }
2682 }
2683 } else {
2684 names.push(ExecutableName::default().with_major(3));
2686 }
2687
2688 if let Some(prerelease) = prerelease {
2689 for i in 0..names.len() {
2691 let name = names[i];
2692 if name.minor.is_none() {
2693 continue;
2696 }
2697 names.push(name.with_prerelease(*prerelease));
2698 }
2699 }
2700
2701 if let Some(implementation) = implementation {
2703 for i in 0..names.len() {
2704 let name = names[i].with_implementation(*implementation);
2705 names.push(name);
2706 }
2707 } else {
2708 if matches!(self, Self::Any) {
2710 for i in 0..names.len() {
2711 for implementation in ImplementationName::iter_all() {
2712 let name = names[i].with_implementation(implementation);
2713 names.push(name);
2714 }
2715 }
2716 }
2717 }
2718
2719 if let Some(variant) = self.variant() {
2721 if variant != PythonVariant::Default {
2722 for i in 0..names.len() {
2723 let name = names[i].with_variant(variant);
2724 names.push(name);
2725 }
2726 }
2727 }
2728
2729 names.sort_unstable_by_key(|name| name.into_comparator(self, implementation));
2730 names.reverse();
2731
2732 names
2733 }
2734
2735 pub(crate) fn major(&self) -> Option<u8> {
2737 match self {
2738 Self::Any | Self::Default | Self::Range(_, _) => None,
2739 Self::Major(major, _) => Some(*major),
2740 Self::MajorMinor(major, _, _) => Some(*major),
2741 Self::MajorMinorPatch(major, _, _, _) => Some(*major),
2742 Self::MajorMinorPrerelease(major, _, _, _) => Some(*major),
2743 }
2744 }
2745
2746 pub(crate) fn minor(&self) -> Option<u8> {
2748 match self {
2749 Self::Any | Self::Default | Self::Range(_, _) => None,
2750 Self::Major(_, _) => None,
2751 Self::MajorMinor(_, minor, _) => Some(*minor),
2752 Self::MajorMinorPatch(_, minor, _, _) => Some(*minor),
2753 Self::MajorMinorPrerelease(_, minor, _, _) => Some(*minor),
2754 }
2755 }
2756
2757 pub(crate) fn patch(&self) -> Option<u8> {
2759 match self {
2760 Self::Any | Self::Default | Self::Range(_, _) => None,
2761 Self::Major(_, _) => None,
2762 Self::MajorMinor(_, _, _) => None,
2763 Self::MajorMinorPatch(_, _, patch, _) => Some(*patch),
2764 Self::MajorMinorPrerelease(_, _, _, _) => None,
2765 }
2766 }
2767
2768 pub(crate) fn prerelease(&self) -> Option<&Prerelease> {
2770 match self {
2771 Self::Any | Self::Default | Self::Range(_, _) => None,
2772 Self::Major(_, _) => None,
2773 Self::MajorMinor(_, _, _) => None,
2774 Self::MajorMinorPatch(_, _, _, _) => None,
2775 Self::MajorMinorPrerelease(_, _, prerelease, _) => Some(prerelease),
2776 }
2777 }
2778
2779 pub(crate) fn check_supported(&self) -> Result<(), String> {
2783 match self {
2784 Self::Any | Self::Default => (),
2785 Self::Major(major, _) => {
2786 if *major < 3 {
2787 return Err(format!(
2788 "Python <3 is not supported but {major} was requested."
2789 ));
2790 }
2791 }
2792 Self::MajorMinor(major, minor, _) => {
2793 if (*major, *minor) < (3, 6) {
2794 return Err(format!(
2795 "Python <3.6 is not supported but {major}.{minor} was requested."
2796 ));
2797 }
2798 }
2799 Self::MajorMinorPatch(major, minor, patch, _) => {
2800 if (*major, *minor) < (3, 6) {
2801 return Err(format!(
2802 "Python <3.6 is not supported but {major}.{minor}.{patch} was requested."
2803 ));
2804 }
2805 }
2806 Self::MajorMinorPrerelease(major, minor, prerelease, _) => {
2807 if (*major, *minor) < (3, 6) {
2808 return Err(format!(
2809 "Python <3.6 is not supported but {major}.{minor}{prerelease} was requested."
2810 ));
2811 }
2812 }
2813 Self::Range(_, _) => (),
2815 }
2816
2817 if self.is_freethreaded() {
2818 if let Self::MajorMinor(major, minor, _) = self.clone().without_patch() {
2819 if (major, minor) < (3, 13) {
2820 return Err(format!(
2821 "Python <3.13 does not support free-threading but {self} was requested."
2822 ));
2823 }
2824 }
2825 }
2826
2827 Ok(())
2828 }
2829
2830 #[must_use]
2836 pub(crate) fn into_request_for_source(self, source: PythonSource) -> Self {
2837 match self {
2838 Self::Default => match source {
2839 PythonSource::ParentInterpreter
2840 | PythonSource::CondaPrefix
2841 | PythonSource::BaseCondaPrefix
2842 | PythonSource::ProvidedPath
2843 | PythonSource::DiscoveredEnvironment
2844 | PythonSource::ActiveEnvironment => Self::Any,
2845 PythonSource::SearchPath
2846 | PythonSource::SearchPathFirst
2847 | PythonSource::Registry
2848 | PythonSource::MicrosoftStore
2849 | PythonSource::Managed => Self::Default,
2850 },
2851 _ => self,
2852 }
2853 }
2854
2855 pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
2857 match self {
2858 Self::Any => true,
2859 Self::Default => PythonVariant::Default.matches_interpreter(interpreter),
2861 Self::Major(major, variant) => {
2862 interpreter.python_major() == *major && variant.matches_interpreter(interpreter)
2863 }
2864 Self::MajorMinor(major, minor, variant) => {
2865 (interpreter.python_major(), interpreter.python_minor()) == (*major, *minor)
2866 && variant.matches_interpreter(interpreter)
2867 }
2868 Self::MajorMinorPatch(major, minor, patch, variant) => {
2869 (
2870 interpreter.python_major(),
2871 interpreter.python_minor(),
2872 interpreter.python_patch(),
2873 ) == (*major, *minor, *patch)
2874 && interpreter.python_version().pre().is_none()
2877 && variant.matches_interpreter(interpreter)
2878 }
2879 Self::Range(specifiers, variant) => {
2880 let version = if specifiers
2883 .iter()
2884 .any(uv_pep440::VersionSpecifier::any_prerelease)
2885 {
2886 Cow::Borrowed(interpreter.python_version())
2887 } else {
2888 Cow::Owned(interpreter.python_version().only_release())
2889 };
2890 specifiers.contains(&version) && variant.matches_interpreter(interpreter)
2891 }
2892 Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
2893 let version = interpreter.python_version();
2894 let Some(interpreter_prerelease) = version.pre() else {
2895 return false;
2896 };
2897 (
2898 interpreter.python_major(),
2899 interpreter.python_minor(),
2900 interpreter_prerelease,
2901 ) == (*major, *minor, *prerelease)
2902 && variant.matches_interpreter(interpreter)
2903 }
2904 }
2905 }
2906
2907 pub(crate) fn matches_version(&self, version: &PythonVersion) -> bool {
2912 match self {
2913 Self::Any | Self::Default => true,
2914 Self::Major(major, _) => version.major() == *major,
2915 Self::MajorMinor(major, minor, _) => {
2916 (version.major(), version.minor()) == (*major, *minor)
2917 }
2918 Self::MajorMinorPatch(major, minor, patch, _) => {
2919 (version.major(), version.minor(), version.patch())
2920 == (*major, *minor, Some(*patch))
2921 }
2922 Self::Range(specifiers, _) => {
2923 let version = if specifiers
2926 .iter()
2927 .any(uv_pep440::VersionSpecifier::any_prerelease)
2928 {
2929 Cow::Borrowed(&version.version)
2930 } else {
2931 Cow::Owned(version.version.only_release())
2932 };
2933 specifiers.contains(&version)
2934 }
2935 Self::MajorMinorPrerelease(major, minor, prerelease, _) => {
2936 (version.major(), version.minor(), version.pre())
2937 == (*major, *minor, Some(*prerelease))
2938 }
2939 }
2940 }
2941
2942 fn matches_major_minor(&self, major: u8, minor: u8) -> bool {
2947 match self {
2948 Self::Any | Self::Default => true,
2949 Self::Major(self_major, _) => *self_major == major,
2950 Self::MajorMinor(self_major, self_minor, _) => {
2951 (*self_major, *self_minor) == (major, minor)
2952 }
2953 Self::MajorMinorPatch(self_major, self_minor, _, _) => {
2954 (*self_major, *self_minor) == (major, minor)
2955 }
2956 Self::Range(specifiers, _) => {
2957 let range = release_specifiers_to_ranges(specifiers.clone());
2958 let Some((lower, upper)) = range.bounding_range() else {
2959 return true;
2960 };
2961 let version = Version::new([u64::from(major), u64::from(minor)]);
2962
2963 let lower = LowerBound::new(lower.cloned());
2964 if !lower.major_minor().contains(&version) {
2965 return false;
2966 }
2967
2968 let upper = UpperBound::new(upper.cloned());
2969 if !upper.major_minor().contains(&version) {
2970 return false;
2971 }
2972
2973 true
2974 }
2975 Self::MajorMinorPrerelease(self_major, self_minor, _, _) => {
2976 (*self_major, *self_minor) == (major, minor)
2977 }
2978 }
2979 }
2980
2981 pub(crate) fn matches_major_minor_patch_prerelease(
2987 &self,
2988 major: u8,
2989 minor: u8,
2990 patch: u8,
2991 prerelease: Option<Prerelease>,
2992 ) -> bool {
2993 match self {
2994 Self::Any | Self::Default => true,
2995 Self::Major(self_major, _) => *self_major == major,
2996 Self::MajorMinor(self_major, self_minor, _) => {
2997 (*self_major, *self_minor) == (major, minor)
2998 }
2999 Self::MajorMinorPatch(self_major, self_minor, self_patch, _) => {
3000 (*self_major, *self_minor, *self_patch) == (major, minor, patch)
3001 && prerelease.is_none()
3004 }
3005 Self::Range(specifiers, _) => specifiers.contains(
3006 &Version::new([u64::from(major), u64::from(minor), u64::from(patch)])
3007 .with_pre(prerelease),
3008 ),
3009 Self::MajorMinorPrerelease(self_major, self_minor, self_prerelease, _) => {
3010 (*self_major, *self_minor, 0, Some(*self_prerelease))
3012 == (major, minor, patch, prerelease)
3013 }
3014 }
3015 }
3016
3017 pub(crate) fn matches_installation_key(&self, key: &PythonInstallationKey) -> bool {
3022 self.matches_major_minor_patch_prerelease(key.major, key.minor, key.patch, key.prerelease())
3023 }
3024
3025 fn has_patch(&self) -> bool {
3027 match self {
3028 Self::Any | Self::Default => false,
3029 Self::Major(..) => false,
3030 Self::MajorMinor(..) => false,
3031 Self::MajorMinorPatch(..) => true,
3032 Self::MajorMinorPrerelease(..) => false,
3033 Self::Range(_, _) => false,
3034 }
3035 }
3036
3037 #[must_use]
3041 fn without_patch(self) -> Self {
3042 match self {
3043 Self::Default => Self::Default,
3044 Self::Any => Self::Any,
3045 Self::Major(major, variant) => Self::Major(major, variant),
3046 Self::MajorMinor(major, minor, variant) => Self::MajorMinor(major, minor, variant),
3047 Self::MajorMinorPatch(major, minor, _, variant) => {
3048 Self::MajorMinor(major, minor, variant)
3049 }
3050 Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
3051 Self::MajorMinorPrerelease(major, minor, prerelease, variant)
3052 }
3053 Self::Range(_, _) => self,
3054 }
3055 }
3056
3057 pub(crate) fn allows_prereleases(&self) -> bool {
3059 match self {
3060 Self::Default => false,
3061 Self::Any => true,
3062 Self::Major(..) => false,
3063 Self::MajorMinor(..) => false,
3064 Self::MajorMinorPatch(..) => false,
3065 Self::MajorMinorPrerelease(..) => true,
3066 Self::Range(specifiers, _) => specifiers.iter().any(VersionSpecifier::any_prerelease),
3067 }
3068 }
3069
3070 pub(crate) fn is_debug(&self) -> bool {
3072 match self {
3073 Self::Any | Self::Default => false,
3074 Self::Major(_, variant)
3075 | Self::MajorMinor(_, _, variant)
3076 | Self::MajorMinorPatch(_, _, _, variant)
3077 | Self::MajorMinorPrerelease(_, _, _, variant)
3078 | Self::Range(_, variant) => variant.is_debug(),
3079 }
3080 }
3081
3082 pub(crate) fn is_freethreaded(&self) -> bool {
3084 match self {
3085 Self::Any | Self::Default => false,
3086 Self::Major(_, variant)
3087 | Self::MajorMinor(_, _, variant)
3088 | Self::MajorMinorPatch(_, _, _, variant)
3089 | Self::MajorMinorPrerelease(_, _, _, variant)
3090 | Self::Range(_, variant) => variant.is_freethreaded(),
3091 }
3092 }
3093
3094 #[must_use]
3098 pub fn without_python_variant(self) -> Self {
3099 match self {
3102 Self::Any | Self::Default => self,
3103 Self::Major(major, _) => Self::Major(major, PythonVariant::Default),
3104 Self::MajorMinor(major, minor, _) => {
3105 Self::MajorMinor(major, minor, PythonVariant::Default)
3106 }
3107 Self::MajorMinorPatch(major, minor, patch, _) => {
3108 Self::MajorMinorPatch(major, minor, patch, PythonVariant::Default)
3109 }
3110 Self::MajorMinorPrerelease(major, minor, prerelease, _) => {
3111 Self::MajorMinorPrerelease(major, minor, prerelease, PythonVariant::Default)
3112 }
3113 Self::Range(specifiers, _) => Self::Range(specifiers, PythonVariant::Default),
3114 }
3115 }
3116
3117 pub(crate) fn variant(&self) -> Option<PythonVariant> {
3119 match self {
3120 Self::Any => None,
3121 Self::Default => Some(PythonVariant::Default),
3122 Self::Major(_, variant)
3123 | Self::MajorMinor(_, _, variant)
3124 | Self::MajorMinorPatch(_, _, _, variant)
3125 | Self::MajorMinorPrerelease(_, _, _, variant)
3126 | Self::Range(_, variant) => Some(*variant),
3127 }
3128 }
3129
3130 pub fn as_pep440_version(&self) -> Option<Version> {
3134 match self {
3135 Self::Default | Self::Any | Self::Range(_, _) => None,
3136 Self::Major(major, _) => Some(Version::new([u64::from(*major)])),
3137 Self::MajorMinor(major, minor, _) => {
3138 Some(Version::new([u64::from(*major), u64::from(*minor)]))
3139 }
3140 Self::MajorMinorPatch(major, minor, patch, _) => Some(Version::new([
3141 u64::from(*major),
3142 u64::from(*minor),
3143 u64::from(*patch),
3144 ])),
3145 Self::MajorMinorPrerelease(major, minor, prerelease, _) => Some(
3147 Version::new([u64::from(*major), u64::from(*minor), 0]).with_pre(Some(*prerelease)),
3148 ),
3149 }
3150 }
3151
3152 pub fn as_version_specifiers(&self) -> Option<VersionSpecifiers> {
3158 match self {
3159 Self::Default | Self::Any => None,
3160 Self::Major(major, _) => Some(VersionSpecifiers::from(
3161 VersionSpecifier::equals_star_version(Version::new([u64::from(*major)])),
3162 )),
3163 Self::MajorMinor(major, minor, _) => Some(VersionSpecifiers::from(
3164 VersionSpecifier::equals_star_version(Version::new([
3165 u64::from(*major),
3166 u64::from(*minor),
3167 ])),
3168 )),
3169 Self::MajorMinorPatch(major, minor, patch, _) => {
3170 Some(VersionSpecifiers::from(VersionSpecifier::equals_version(
3171 Version::new([u64::from(*major), u64::from(*minor), u64::from(*patch)]),
3172 )))
3173 }
3174 Self::MajorMinorPrerelease(major, minor, prerelease, _) => {
3175 Some(VersionSpecifiers::from(VersionSpecifier::equals_version(
3176 Version::new([u64::from(*major), u64::from(*minor), 0])
3177 .with_pre(Some(*prerelease)),
3178 )))
3179 }
3180 Self::Range(specifiers, _) => Some(specifiers.clone()),
3181 }
3182 }
3183}
3184
3185impl FromStr for VersionRequest {
3186 type Err = Error;
3187
3188 fn from_str(s: &str) -> Result<Self, Self::Err> {
3189 fn parse_variant(s: &str) -> Result<(&str, PythonVariant), Error> {
3192 if s.chars().all(char::is_alphabetic) {
3194 return Err(Error::InvalidVersionRequest(s.to_string()));
3195 }
3196
3197 let Some(mut start) = s.rfind(|c: char| c.is_numeric()) else {
3198 return Ok((s, PythonVariant::Default));
3199 };
3200
3201 start += 1;
3203
3204 if start + 1 > s.len() {
3206 return Ok((s, PythonVariant::Default));
3207 }
3208
3209 let variant = &s[start..];
3210 let prefix = &s[..start];
3211
3212 let variant = variant.strip_prefix('+').unwrap_or(variant);
3214
3215 let Ok(variant) = PythonVariant::from_str(variant) else {
3219 return Ok((s, PythonVariant::Default));
3220 };
3221
3222 Ok((prefix, variant))
3223 }
3224
3225 let (s, variant) = parse_variant(s)?;
3226 let Ok(version) = Version::from_str(s) else {
3227 return parse_version_specifiers_request(s, variant);
3228 };
3229
3230 let version = split_wheel_tag_release_version(version);
3232
3233 if version.post().is_some() || version.dev().is_some() {
3235 return Err(Error::InvalidVersionRequest(s.to_string()));
3236 }
3237
3238 if !version.local().is_empty() {
3241 return Err(Error::InvalidVersionRequest(s.to_string()));
3242 }
3243
3244 let Ok(release) = try_into_u8_slice(&version.release()) else {
3246 return Err(Error::InvalidVersionRequest(s.to_string()));
3247 };
3248
3249 let prerelease = version.pre();
3250
3251 match release.as_slice() {
3252 [major] => {
3254 if prerelease.is_some() {
3256 return Err(Error::InvalidVersionRequest(s.to_string()));
3257 }
3258 Ok(Self::Major(*major, variant))
3259 }
3260 [major, minor] => {
3262 if let Some(prerelease) = prerelease {
3263 return Ok(Self::MajorMinorPrerelease(
3264 *major, *minor, prerelease, variant,
3265 ));
3266 }
3267 Ok(Self::MajorMinor(*major, *minor, variant))
3268 }
3269 [major, minor, patch] => {
3271 if let Some(prerelease) = prerelease {
3272 if *patch != 0 {
3275 return Err(Error::InvalidVersionRequest(s.to_string()));
3276 }
3277 return Ok(Self::MajorMinorPrerelease(
3278 *major, *minor, prerelease, variant,
3279 ));
3280 }
3281 Ok(Self::MajorMinorPatch(*major, *minor, *patch, variant))
3282 }
3283 _ => Err(Error::InvalidVersionRequest(s.to_string())),
3284 }
3285 }
3286}
3287
3288impl FromStr for PythonVariant {
3289 type Err = ();
3290
3291 fn from_str(s: &str) -> Result<Self, Self::Err> {
3292 match s {
3293 "t" | "freethreaded" => Ok(Self::Freethreaded),
3294 "d" | "debug" => Ok(Self::Debug),
3295 "td" | "freethreaded+debug" => Ok(Self::FreethreadedDebug),
3296 "gil" => Ok(Self::Gil),
3297 "gil+debug" => Ok(Self::GilDebug),
3298 "" => Ok(Self::Default),
3299 _ => Err(()),
3300 }
3301 }
3302}
3303
3304impl fmt::Display for PythonVariant {
3305 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3306 match self {
3307 Self::Default => f.write_str("default"),
3308 Self::Debug => f.write_str("debug"),
3309 Self::Freethreaded => f.write_str("freethreaded"),
3310 Self::FreethreadedDebug => f.write_str("freethreaded+debug"),
3311 Self::Gil => f.write_str("gil"),
3312 Self::GilDebug => f.write_str("gil+debug"),
3313 }
3314 }
3315}
3316
3317fn parse_version_specifiers_request(
3318 s: &str,
3319 variant: PythonVariant,
3320) -> Result<VersionRequest, Error> {
3321 let Ok(specifiers) = VersionSpecifiers::from_str(s) else {
3322 return Err(Error::InvalidVersionRequest(s.to_string()));
3323 };
3324 if specifiers.is_empty() {
3325 return Err(Error::InvalidVersionRequest(s.to_string()));
3326 }
3327 Ok(VersionRequest::Range(specifiers, variant))
3328}
3329
3330impl From<&PythonVersion> for VersionRequest {
3331 fn from(version: &PythonVersion) -> Self {
3332 Self::from_str(&version.string)
3333 .expect("Valid `PythonVersion`s should be valid `VersionRequest`s")
3334 }
3335}
3336
3337impl fmt::Display for VersionRequest {
3338 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3339 match self {
3340 Self::Any => f.write_str("any"),
3341 Self::Default => f.write_str("default"),
3342 Self::Major(major, variant) => write!(f, "{major}{}", variant.display_suffix()),
3343 Self::MajorMinor(major, minor, variant) => {
3344 write!(f, "{major}.{minor}{}", variant.display_suffix())
3345 }
3346 Self::MajorMinorPatch(major, minor, patch, variant) => {
3347 write!(f, "{major}.{minor}.{patch}{}", variant.display_suffix())
3348 }
3349 Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
3350 write!(f, "{major}.{minor}{prerelease}{}", variant.display_suffix())
3351 }
3352 Self::Range(specifiers, _) => write!(f, "{specifiers}"),
3353 }
3354 }
3355}
3356
3357impl fmt::Display for PythonRequest {
3358 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3359 match self {
3360 Self::Default => write!(f, "a default Python"),
3361 Self::Any => write!(f, "any Python"),
3362 Self::Version(version) => write!(f, "Python {version}"),
3363 Self::Directory(path) => write!(f, "directory `{}`", path.user_display()),
3364 Self::File(path) => write!(f, "path `{}`", path.user_display()),
3365 Self::ExecutableName(name) => write!(f, "executable name `{name}`"),
3366 Self::Implementation(implementation) => {
3367 write!(f, "{}", implementation.pretty())
3368 }
3369 Self::ImplementationVersion(implementation, version) => {
3370 write!(f, "{} {version}", implementation.pretty())
3371 }
3372 Self::Key(request) => write!(f, "{request}"),
3373 }
3374 }
3375}
3376
3377impl fmt::Display for PythonSource {
3378 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3379 match self {
3380 Self::ProvidedPath => f.write_str("provided path"),
3381 Self::ActiveEnvironment => f.write_str("active virtual environment"),
3382 Self::CondaPrefix | Self::BaseCondaPrefix => f.write_str("conda prefix"),
3383 Self::DiscoveredEnvironment => f.write_str("virtual environment"),
3384 Self::SearchPath => f.write_str("search path"),
3385 Self::SearchPathFirst => f.write_str("first executable in the search path"),
3386 Self::Registry => f.write_str("registry"),
3387 Self::MicrosoftStore => f.write_str("Microsoft Store"),
3388 Self::Managed => f.write_str("managed installations"),
3389 Self::ParentInterpreter => f.write_str("parent interpreter"),
3390 }
3391 }
3392}
3393
3394impl PythonPreference {
3395 fn sources(self) -> &'static [PythonSource] {
3398 match self {
3399 Self::OnlyManaged => &[PythonSource::Managed],
3400 Self::Managed => {
3401 if cfg!(windows) {
3402 &[
3403 PythonSource::Managed,
3404 PythonSource::SearchPath,
3405 PythonSource::Registry,
3406 ]
3407 } else {
3408 &[PythonSource::Managed, PythonSource::SearchPath]
3409 }
3410 }
3411 Self::System => {
3412 if cfg!(windows) {
3413 &[
3414 PythonSource::SearchPath,
3415 PythonSource::Registry,
3416 PythonSource::Managed,
3417 ]
3418 } else {
3419 &[PythonSource::SearchPath, PythonSource::Managed]
3420 }
3421 }
3422 Self::OnlySystem => {
3423 if cfg!(windows) {
3424 &[PythonSource::SearchPath, PythonSource::Registry]
3425 } else {
3426 &[PythonSource::SearchPath]
3427 }
3428 }
3429 }
3430 }
3431
3432 pub fn canonical_name(&self) -> &'static str {
3436 match self {
3437 Self::OnlyManaged => "only managed",
3438 Self::Managed => "prefer managed",
3439 Self::System => "prefer system",
3440 Self::OnlySystem => "only system",
3441 }
3442 }
3443}
3444
3445impl fmt::Display for PythonPreference {
3446 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3447 f.write_str(match self {
3448 Self::OnlyManaged => "only managed",
3449 Self::Managed => "prefer managed",
3450 Self::System => "prefer system",
3451 Self::OnlySystem => "only system",
3452 })
3453 }
3454}
3455
3456impl DiscoveryPreferences {
3457 fn sources(&self, request: &PythonRequest) -> String {
3460 let python_sources = self
3461 .python_preference
3462 .sources()
3463 .iter()
3464 .map(ToString::to_string)
3465 .collect::<Vec<_>>();
3466 match self.environment_preference {
3467 EnvironmentPreference::Any => disjunction(
3468 &["virtual environments"]
3469 .into_iter()
3470 .chain(python_sources.iter().map(String::as_str))
3471 .collect::<Vec<_>>(),
3472 ),
3473 EnvironmentPreference::ExplicitSystem => {
3474 if request.is_explicit_system() {
3475 disjunction(
3476 &["virtual environments"]
3477 .into_iter()
3478 .chain(python_sources.iter().map(String::as_str))
3479 .collect::<Vec<_>>(),
3480 )
3481 } else {
3482 disjunction(&["virtual environments"])
3483 }
3484 }
3485 EnvironmentPreference::OnlySystem => disjunction(
3486 &python_sources
3487 .iter()
3488 .map(String::as_str)
3489 .collect::<Vec<_>>(),
3490 ),
3491 EnvironmentPreference::OnlyVirtual => disjunction(&["virtual environments"]),
3492 }
3493 }
3494}
3495
3496impl fmt::Display for PythonNotFound {
3497 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
3498 let sources = DiscoveryPreferences {
3499 python_preference: self.python_preference,
3500 environment_preference: self.environment_preference,
3501 }
3502 .sources(&self.request);
3503
3504 match self.request {
3505 PythonRequest::Default | PythonRequest::Any => {
3506 write!(f, "No interpreter found in {sources}")
3507 }
3508 PythonRequest::File(_) => {
3509 write!(f, "No interpreter found at {}", self.request)
3510 }
3511 PythonRequest::Directory(_) => {
3512 write!(f, "No interpreter found in {}", self.request)
3513 }
3514 _ => {
3515 write!(f, "No interpreter found for {} in {sources}", self.request)
3516 }
3517 }
3518 }
3519}
3520
3521fn disjunction(items: &[&str]) -> String {
3523 match items.len() {
3524 0 => String::new(),
3525 1 => items[0].to_string(),
3526 2 => format!("{} or {}", items[0], items[1]),
3527 _ => {
3528 let last = items.last().unwrap();
3529 format!(
3530 "{}, or {}",
3531 items.iter().take(items.len() - 1).join(", "),
3532 last
3533 )
3534 }
3535 }
3536}
3537
3538fn try_into_u8_slice(release: &[u64]) -> Result<Vec<u8>, std::num::TryFromIntError> {
3539 release
3540 .iter()
3541 .map(|x| match u8::try_from(*x) {
3542 Ok(x) => Ok(x),
3543 Err(e) => Err(e),
3544 })
3545 .collect()
3546}
3547
3548fn split_wheel_tag_release_version(version: Version) -> Version {
3555 let release = version.release();
3556 if release.len() != 1 {
3557 return version;
3558 }
3559
3560 let release = release[0].to_string();
3561 let mut chars = release.chars();
3562 let Some(major) = chars.next().and_then(|c| c.to_digit(10)) else {
3563 return version;
3564 };
3565
3566 let Ok(minor) = chars.as_str().parse::<u32>() else {
3567 return version;
3568 };
3569
3570 version.with_release([u64::from(major), u64::from(minor)])
3571}
3572
3573#[cfg(test)]
3574mod tests {
3575 use std::{path::PathBuf, str::FromStr};
3576
3577 use assert_fs::{TempDir, prelude::*};
3578 use target_lexicon::{Aarch64Architecture, Architecture};
3579 use test_log::test;
3580 use uv_distribution_types::RequiresPython;
3581 use uv_pep440::{Prerelease, PrereleaseKind, Version, VersionSpecifiers};
3582
3583 use crate::{
3584 discovery::{PythonRequest, VersionRequest},
3585 downloads::{ArchRequest, PythonDownloadRequest},
3586 implementation::ImplementationName,
3587 };
3588 use uv_platform::{Arch, Libc, Os};
3589
3590 use super::{
3591 DiscoveryPreferences, EnvironmentPreference, Error, PythonPreference, PythonVariant,
3592 };
3593
3594 #[test]
3595 fn interpreter_request_from_str() {
3596 assert_eq!(PythonRequest::parse("any"), PythonRequest::Any);
3597 assert_eq!(PythonRequest::parse("default"), PythonRequest::Default);
3598 assert_eq!(
3599 PythonRequest::parse("3.12"),
3600 PythonRequest::Version(VersionRequest::from_str("3.12").unwrap())
3601 );
3602 assert_eq!(
3603 PythonRequest::parse(">=3.12"),
3604 PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap())
3605 );
3606 assert_eq!(
3607 PythonRequest::parse(">=3.12,<3.13"),
3608 PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap())
3609 );
3610 assert_eq!(
3611 PythonRequest::parse(">=3.12,<3.13"),
3612 PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap())
3613 );
3614
3615 assert_eq!(
3616 PythonRequest::parse("3.13.0a1"),
3617 PythonRequest::Version(VersionRequest::from_str("3.13.0a1").unwrap())
3618 );
3619 assert_eq!(
3620 PythonRequest::parse("3.13.0b5"),
3621 PythonRequest::Version(VersionRequest::from_str("3.13.0b5").unwrap())
3622 );
3623 assert_eq!(
3624 PythonRequest::parse("3.13.0rc1"),
3625 PythonRequest::Version(VersionRequest::from_str("3.13.0rc1").unwrap())
3626 );
3627 assert_eq!(
3628 PythonRequest::parse("3.13.1rc1"),
3629 PythonRequest::ExecutableName("3.13.1rc1".to_string()),
3630 "Pre-release version requests require a patch version of zero"
3631 );
3632 assert_eq!(
3633 PythonRequest::parse("3rc1"),
3634 PythonRequest::ExecutableName("3rc1".to_string()),
3635 "Pre-release version requests require a minor version"
3636 );
3637
3638 assert_eq!(
3639 PythonRequest::parse("cpython"),
3640 PythonRequest::Implementation(ImplementationName::CPython)
3641 );
3642
3643 assert_eq!(
3644 PythonRequest::parse("cpython3.12.2"),
3645 PythonRequest::ImplementationVersion(
3646 ImplementationName::CPython,
3647 VersionRequest::from_str("3.12.2").unwrap(),
3648 )
3649 );
3650
3651 assert_eq!(
3652 PythonRequest::parse("cpython-3.13.2"),
3653 PythonRequest::Key(PythonDownloadRequest {
3654 version: Some(VersionRequest::MajorMinorPatch(
3655 3,
3656 13,
3657 2,
3658 PythonVariant::Default
3659 )),
3660 implementation: Some(ImplementationName::CPython),
3661 arch: None,
3662 os: None,
3663 libc: None,
3664 build: None,
3665 prereleases: None
3666 })
3667 );
3668 assert_eq!(
3669 PythonRequest::parse("cpython-3.13.2-macos-aarch64-none"),
3670 PythonRequest::Key(PythonDownloadRequest {
3671 version: Some(VersionRequest::MajorMinorPatch(
3672 3,
3673 13,
3674 2,
3675 PythonVariant::Default
3676 )),
3677 implementation: Some(ImplementationName::CPython),
3678 arch: Some(ArchRequest::Explicit(Arch::new(
3679 Architecture::Aarch64(Aarch64Architecture::Aarch64),
3680 None
3681 ))),
3682 os: Some(Os::new(target_lexicon::OperatingSystem::Darwin(None))),
3683 libc: Some(Libc::None),
3684 build: None,
3685 prereleases: None
3686 })
3687 );
3688 assert_eq!(
3689 PythonRequest::parse("any-3.13.2"),
3690 PythonRequest::Key(PythonDownloadRequest {
3691 version: Some(VersionRequest::MajorMinorPatch(
3692 3,
3693 13,
3694 2,
3695 PythonVariant::Default
3696 )),
3697 implementation: None,
3698 arch: None,
3699 os: None,
3700 libc: None,
3701 build: None,
3702 prereleases: None
3703 })
3704 );
3705 assert_eq!(
3706 PythonRequest::parse("any-3.13.2-any-aarch64"),
3707 PythonRequest::Key(PythonDownloadRequest {
3708 version: Some(VersionRequest::MajorMinorPatch(
3709 3,
3710 13,
3711 2,
3712 PythonVariant::Default
3713 )),
3714 implementation: None,
3715 arch: Some(ArchRequest::Explicit(Arch::new(
3716 Architecture::Aarch64(Aarch64Architecture::Aarch64),
3717 None
3718 ))),
3719 os: None,
3720 libc: None,
3721 build: None,
3722 prereleases: None
3723 })
3724 );
3725
3726 assert_eq!(
3727 PythonRequest::parse("pypy"),
3728 PythonRequest::Implementation(ImplementationName::PyPy)
3729 );
3730 assert_eq!(
3731 PythonRequest::parse("pp"),
3732 PythonRequest::Implementation(ImplementationName::PyPy)
3733 );
3734 assert_eq!(
3735 PythonRequest::parse("graalpy"),
3736 PythonRequest::Implementation(ImplementationName::GraalPy)
3737 );
3738 assert_eq!(
3739 PythonRequest::parse("gp"),
3740 PythonRequest::Implementation(ImplementationName::GraalPy)
3741 );
3742 assert_eq!(
3743 PythonRequest::parse("cp"),
3744 PythonRequest::Implementation(ImplementationName::CPython)
3745 );
3746 assert_eq!(
3747 PythonRequest::parse("pypy3.10"),
3748 PythonRequest::ImplementationVersion(
3749 ImplementationName::PyPy,
3750 VersionRequest::from_str("3.10").unwrap(),
3751 )
3752 );
3753 assert_eq!(
3754 PythonRequest::parse("pp310"),
3755 PythonRequest::ImplementationVersion(
3756 ImplementationName::PyPy,
3757 VersionRequest::from_str("3.10").unwrap(),
3758 )
3759 );
3760 assert_eq!(
3761 PythonRequest::parse("graalpy3.10"),
3762 PythonRequest::ImplementationVersion(
3763 ImplementationName::GraalPy,
3764 VersionRequest::from_str("3.10").unwrap(),
3765 )
3766 );
3767 assert_eq!(
3768 PythonRequest::parse("gp310"),
3769 PythonRequest::ImplementationVersion(
3770 ImplementationName::GraalPy,
3771 VersionRequest::from_str("3.10").unwrap(),
3772 )
3773 );
3774 assert_eq!(
3775 PythonRequest::parse("cp38"),
3776 PythonRequest::ImplementationVersion(
3777 ImplementationName::CPython,
3778 VersionRequest::from_str("3.8").unwrap(),
3779 )
3780 );
3781 assert_eq!(
3782 PythonRequest::parse("pypy@3.10"),
3783 PythonRequest::ImplementationVersion(
3784 ImplementationName::PyPy,
3785 VersionRequest::from_str("3.10").unwrap(),
3786 )
3787 );
3788 assert_eq!(
3789 PythonRequest::parse("pypy310"),
3790 PythonRequest::ImplementationVersion(
3791 ImplementationName::PyPy,
3792 VersionRequest::from_str("3.10").unwrap(),
3793 )
3794 );
3795 assert_eq!(
3796 PythonRequest::parse("graalpy@3.10"),
3797 PythonRequest::ImplementationVersion(
3798 ImplementationName::GraalPy,
3799 VersionRequest::from_str("3.10").unwrap(),
3800 )
3801 );
3802 assert_eq!(
3803 PythonRequest::parse("graalpy310"),
3804 PythonRequest::ImplementationVersion(
3805 ImplementationName::GraalPy,
3806 VersionRequest::from_str("3.10").unwrap(),
3807 )
3808 );
3809
3810 let tempdir = TempDir::new().unwrap();
3811 assert_eq!(
3812 PythonRequest::parse(tempdir.path().to_str().unwrap()),
3813 PythonRequest::Directory(tempdir.path().to_path_buf()),
3814 "An existing directory is treated as a directory"
3815 );
3816 assert_eq!(
3817 PythonRequest::parse(tempdir.child("foo").path().to_str().unwrap()),
3818 PythonRequest::File(tempdir.child("foo").path().to_path_buf()),
3819 "A path that does not exist is treated as a file"
3820 );
3821 tempdir.child("bar").touch().unwrap();
3822 assert_eq!(
3823 PythonRequest::parse(tempdir.child("bar").path().to_str().unwrap()),
3824 PythonRequest::File(tempdir.child("bar").path().to_path_buf()),
3825 "An existing file is treated as a file"
3826 );
3827 assert_eq!(
3828 PythonRequest::parse("./foo"),
3829 PythonRequest::File(PathBuf::from_str("./foo").unwrap()),
3830 "A string with a file system separator is treated as a file"
3831 );
3832 assert_eq!(
3833 PythonRequest::parse("3.13t"),
3834 PythonRequest::Version(VersionRequest::from_str("3.13t").unwrap())
3835 );
3836 }
3837
3838 #[test]
3839 fn discovery_sources_prefer_system_orders_search_path_first() {
3840 let preferences = DiscoveryPreferences {
3841 python_preference: PythonPreference::System,
3842 environment_preference: EnvironmentPreference::OnlySystem,
3843 };
3844 let sources = preferences.sources(&PythonRequest::Default);
3845
3846 if cfg!(windows) {
3847 assert_eq!(sources, "search path, registry, or managed installations");
3848 } else {
3849 assert_eq!(sources, "search path or managed installations");
3850 }
3851 }
3852
3853 #[test]
3854 fn discovery_sources_only_system_matches_platform_order() {
3855 let preferences = DiscoveryPreferences {
3856 python_preference: PythonPreference::OnlySystem,
3857 environment_preference: EnvironmentPreference::OnlySystem,
3858 };
3859 let sources = preferences.sources(&PythonRequest::Default);
3860
3861 if cfg!(windows) {
3862 assert_eq!(sources, "search path or registry");
3863 } else {
3864 assert_eq!(sources, "search path");
3865 }
3866 }
3867
3868 #[test]
3869 fn interpreter_request_to_canonical_string() {
3870 assert_eq!(PythonRequest::Default.to_canonical_string(), "default");
3871 assert_eq!(PythonRequest::Any.to_canonical_string(), "any");
3872 assert_eq!(
3873 PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()).to_canonical_string(),
3874 "3.12"
3875 );
3876 assert_eq!(
3877 PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap())
3878 .to_canonical_string(),
3879 ">=3.12"
3880 );
3881 assert_eq!(
3882 PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap())
3883 .to_canonical_string(),
3884 ">=3.12, <3.13"
3885 );
3886
3887 assert_eq!(
3888 PythonRequest::Version(VersionRequest::from_str("3.13.0a1").unwrap())
3889 .to_canonical_string(),
3890 "3.13a1"
3891 );
3892
3893 assert_eq!(
3894 PythonRequest::Version(VersionRequest::from_str("3.13.0b5").unwrap())
3895 .to_canonical_string(),
3896 "3.13b5"
3897 );
3898
3899 assert_eq!(
3900 PythonRequest::Version(VersionRequest::from_str("3.13.0rc1").unwrap())
3901 .to_canonical_string(),
3902 "3.13rc1"
3903 );
3904
3905 assert_eq!(
3906 PythonRequest::Version(VersionRequest::from_str("313rc4").unwrap())
3907 .to_canonical_string(),
3908 "3.13rc4"
3909 );
3910
3911 assert_eq!(
3912 PythonRequest::ExecutableName("foo".to_string()).to_canonical_string(),
3913 "foo"
3914 );
3915 assert_eq!(
3916 PythonRequest::Implementation(ImplementationName::CPython).to_canonical_string(),
3917 "cpython"
3918 );
3919 assert_eq!(
3920 PythonRequest::ImplementationVersion(
3921 ImplementationName::CPython,
3922 VersionRequest::from_str("3.12.2").unwrap(),
3923 )
3924 .to_canonical_string(),
3925 "cpython@3.12.2"
3926 );
3927 assert_eq!(
3928 PythonRequest::Implementation(ImplementationName::PyPy).to_canonical_string(),
3929 "pypy"
3930 );
3931 assert_eq!(
3932 PythonRequest::ImplementationVersion(
3933 ImplementationName::PyPy,
3934 VersionRequest::from_str("3.10").unwrap(),
3935 )
3936 .to_canonical_string(),
3937 "pypy@3.10"
3938 );
3939 assert_eq!(
3940 PythonRequest::Implementation(ImplementationName::GraalPy).to_canonical_string(),
3941 "graalpy"
3942 );
3943 assert_eq!(
3944 PythonRequest::ImplementationVersion(
3945 ImplementationName::GraalPy,
3946 VersionRequest::from_str("3.10").unwrap(),
3947 )
3948 .to_canonical_string(),
3949 "graalpy@3.10"
3950 );
3951
3952 let tempdir = TempDir::new().unwrap();
3953 assert_eq!(
3954 PythonRequest::Directory(tempdir.path().to_path_buf()).to_canonical_string(),
3955 tempdir.path().to_str().unwrap(),
3956 "An existing directory is treated as a directory"
3957 );
3958 assert_eq!(
3959 PythonRequest::File(tempdir.child("foo").path().to_path_buf()).to_canonical_string(),
3960 tempdir.child("foo").path().to_str().unwrap(),
3961 "A path that does not exist is treated as a file"
3962 );
3963 tempdir.child("bar").touch().unwrap();
3964 assert_eq!(
3965 PythonRequest::File(tempdir.child("bar").path().to_path_buf()).to_canonical_string(),
3966 tempdir.child("bar").path().to_str().unwrap(),
3967 "An existing file is treated as a file"
3968 );
3969 assert_eq!(
3970 PythonRequest::File(PathBuf::from_str("./foo").unwrap()).to_canonical_string(),
3971 "./foo",
3972 "A string with a file system separator is treated as a file"
3973 );
3974 }
3975
3976 #[test]
3977 fn version_request_from_str() {
3978 assert_eq!(
3979 VersionRequest::from_str("3").unwrap(),
3980 VersionRequest::Major(3, PythonVariant::Default)
3981 );
3982 assert_eq!(
3983 VersionRequest::from_str("3.12").unwrap(),
3984 VersionRequest::MajorMinor(3, 12, PythonVariant::Default)
3985 );
3986 assert_eq!(
3987 VersionRequest::from_str("3.12.1").unwrap(),
3988 VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default)
3989 );
3990 assert!(VersionRequest::from_str("1.foo.1").is_err());
3991 assert_eq!(
3992 VersionRequest::from_str("3").unwrap(),
3993 VersionRequest::Major(3, PythonVariant::Default)
3994 );
3995 assert_eq!(
3996 VersionRequest::from_str("38").unwrap(),
3997 VersionRequest::MajorMinor(3, 8, PythonVariant::Default)
3998 );
3999 assert_eq!(
4000 VersionRequest::from_str("312").unwrap(),
4001 VersionRequest::MajorMinor(3, 12, PythonVariant::Default)
4002 );
4003 assert_eq!(
4004 VersionRequest::from_str("3100").unwrap(),
4005 VersionRequest::MajorMinor(3, 100, PythonVariant::Default)
4006 );
4007 assert_eq!(
4008 VersionRequest::from_str("3.13a1").unwrap(),
4009 VersionRequest::MajorMinorPrerelease(
4010 3,
4011 13,
4012 Prerelease {
4013 kind: PrereleaseKind::Alpha,
4014 number: 1
4015 },
4016 PythonVariant::Default
4017 )
4018 );
4019 assert_eq!(
4020 VersionRequest::from_str("313b1").unwrap(),
4021 VersionRequest::MajorMinorPrerelease(
4022 3,
4023 13,
4024 Prerelease {
4025 kind: PrereleaseKind::Beta,
4026 number: 1
4027 },
4028 PythonVariant::Default
4029 )
4030 );
4031 assert_eq!(
4032 VersionRequest::from_str("3.13.0b2").unwrap(),
4033 VersionRequest::MajorMinorPrerelease(
4034 3,
4035 13,
4036 Prerelease {
4037 kind: PrereleaseKind::Beta,
4038 number: 2
4039 },
4040 PythonVariant::Default
4041 )
4042 );
4043 assert_eq!(
4044 VersionRequest::from_str("3.13.0rc3").unwrap(),
4045 VersionRequest::MajorMinorPrerelease(
4046 3,
4047 13,
4048 Prerelease {
4049 kind: PrereleaseKind::Rc,
4050 number: 3
4051 },
4052 PythonVariant::Default
4053 )
4054 );
4055 assert!(
4056 matches!(
4057 VersionRequest::from_str("3rc1"),
4058 Err(Error::InvalidVersionRequest(_))
4059 ),
4060 "Pre-release version requests require a minor version"
4061 );
4062 assert!(
4063 matches!(
4064 VersionRequest::from_str("3.13.2rc1"),
4065 Err(Error::InvalidVersionRequest(_))
4066 ),
4067 "Pre-release version requests require a patch version of zero"
4068 );
4069 assert!(
4070 matches!(
4071 VersionRequest::from_str("3.12-dev"),
4072 Err(Error::InvalidVersionRequest(_))
4073 ),
4074 "Development version segments are not allowed"
4075 );
4076 assert!(
4077 matches!(
4078 VersionRequest::from_str("3.12+local"),
4079 Err(Error::InvalidVersionRequest(_))
4080 ),
4081 "Local version segments are not allowed"
4082 );
4083 assert!(
4084 matches!(
4085 VersionRequest::from_str("3.12.post0"),
4086 Err(Error::InvalidVersionRequest(_))
4087 ),
4088 "Post version segments are not allowed"
4089 );
4090 assert!(
4091 matches!(
4093 VersionRequest::from_str("31000"),
4094 Err(Error::InvalidVersionRequest(_))
4095 )
4096 );
4097 assert_eq!(
4098 VersionRequest::from_str("3t").unwrap(),
4099 VersionRequest::Major(3, PythonVariant::Freethreaded)
4100 );
4101 assert_eq!(
4102 VersionRequest::from_str("313t").unwrap(),
4103 VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded)
4104 );
4105 assert_eq!(
4106 VersionRequest::from_str("3.13t").unwrap(),
4107 VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded)
4108 );
4109 assert_eq!(
4110 VersionRequest::from_str(">=3.13t").unwrap(),
4111 VersionRequest::Range(
4112 VersionSpecifiers::from_str(">=3.13").unwrap(),
4113 PythonVariant::Freethreaded
4114 )
4115 );
4116 assert_eq!(
4117 VersionRequest::from_str(">=3.13").unwrap(),
4118 VersionRequest::Range(
4119 VersionSpecifiers::from_str(">=3.13").unwrap(),
4120 PythonVariant::Default
4121 )
4122 );
4123 assert_eq!(
4124 VersionRequest::from_str(">=3.12,<3.14t").unwrap(),
4125 VersionRequest::Range(
4126 VersionSpecifiers::from_str(">=3.12,<3.14").unwrap(),
4127 PythonVariant::Freethreaded
4128 )
4129 );
4130 assert!(matches!(
4131 VersionRequest::from_str("3.13tt"),
4132 Err(Error::InvalidVersionRequest(_))
4133 ));
4134 }
4135
4136 #[test]
4137 fn executable_names_from_request() {
4138 fn case(request: &str, expected: &[&str]) {
4139 let (implementation, version) = match PythonRequest::parse(request) {
4140 PythonRequest::Any => (None, VersionRequest::Any),
4141 PythonRequest::Default => (None, VersionRequest::Default),
4142 PythonRequest::Version(version) => (None, version),
4143 PythonRequest::ImplementationVersion(implementation, version) => {
4144 (Some(implementation), version)
4145 }
4146 PythonRequest::Implementation(implementation) => {
4147 (Some(implementation), VersionRequest::Default)
4148 }
4149 result => {
4150 panic!("Test cases should request versions or implementations; got {result:?}")
4151 }
4152 };
4153
4154 let result: Vec<_> = version
4155 .executable_names(implementation.as_ref())
4156 .into_iter()
4157 .map(|name| name.to_string())
4158 .collect();
4159
4160 let expected: Vec<_> = expected
4161 .iter()
4162 .map(|name| format!("{name}{exe}", exe = std::env::consts::EXE_SUFFIX))
4163 .collect();
4164
4165 assert_eq!(result, expected, "mismatch for case \"{request}\"");
4166 }
4167
4168 case(
4169 "any",
4170 &[
4171 "python", "python3", "cpython", "cpython3", "pypy", "pypy3", "graalpy", "graalpy3",
4172 "pyodide", "pyodide3",
4173 ],
4174 );
4175
4176 case("default", &["python", "python3"]);
4177
4178 case("3", &["python3", "python"]);
4179
4180 case("4", &["python4", "python"]);
4181
4182 case("3.13", &["python3.13", "python3", "python"]);
4183
4184 case("pypy", &["pypy", "pypy3", "python", "python3"]);
4185
4186 case(
4187 "pypy@3.10",
4188 &[
4189 "pypy3.10",
4190 "pypy3",
4191 "pypy",
4192 "python3.10",
4193 "python3",
4194 "python",
4195 ],
4196 );
4197
4198 case(
4199 "3.13t",
4200 &[
4201 "python3.13t",
4202 "python3.13",
4203 "python3t",
4204 "python3",
4205 "pythont",
4206 "python",
4207 ],
4208 );
4209 case("3t", &["python3t", "python3", "pythont", "python"]);
4210
4211 case(
4212 "3.13.2",
4213 &["python3.13.2", "python3.13", "python3", "python"],
4214 );
4215
4216 case(
4217 "3.13rc2",
4218 &["python3.13rc2", "python3.13", "python3", "python"],
4219 );
4220 }
4221
4222 #[test]
4223 fn test_try_split_prefix_and_version() {
4224 assert!(matches!(
4225 PythonRequest::try_split_prefix_and_version("prefix", "prefix"),
4226 Ok(None),
4227 ));
4228 assert!(matches!(
4229 PythonRequest::try_split_prefix_and_version("prefix", "prefix3"),
4230 Ok(Some(_)),
4231 ));
4232 assert!(matches!(
4233 PythonRequest::try_split_prefix_and_version("prefix", "prefix@3"),
4234 Ok(Some(_)),
4235 ));
4236 assert!(matches!(
4237 PythonRequest::try_split_prefix_and_version("prefix", "prefix3notaversion"),
4238 Ok(None),
4239 ));
4240 assert!(
4242 PythonRequest::try_split_prefix_and_version("prefix", "prefix@3notaversion").is_err()
4243 );
4244 assert!(PythonRequest::try_split_prefix_and_version("", "@3").is_err());
4246 }
4247
4248 #[test]
4249 fn version_request_as_pep440_version() {
4250 assert_eq!(VersionRequest::Default.as_pep440_version(), None);
4252 assert_eq!(VersionRequest::Any.as_pep440_version(), None);
4253 assert_eq!(
4254 VersionRequest::from_str(">=3.10")
4255 .unwrap()
4256 .as_pep440_version(),
4257 None
4258 );
4259
4260 assert_eq!(
4262 VersionRequest::Major(3, PythonVariant::Default).as_pep440_version(),
4263 Some(Version::from_str("3").unwrap())
4264 );
4265
4266 assert_eq!(
4268 VersionRequest::MajorMinor(3, 12, PythonVariant::Default).as_pep440_version(),
4269 Some(Version::from_str("3.12").unwrap())
4270 );
4271
4272 assert_eq!(
4274 VersionRequest::MajorMinorPatch(3, 12, 5, PythonVariant::Default).as_pep440_version(),
4275 Some(Version::from_str("3.12.5").unwrap())
4276 );
4277
4278 assert_eq!(
4280 VersionRequest::MajorMinorPrerelease(
4281 3,
4282 14,
4283 Prerelease {
4284 kind: PrereleaseKind::Alpha,
4285 number: 1
4286 },
4287 PythonVariant::Default
4288 )
4289 .as_pep440_version(),
4290 Some(Version::from_str("3.14.0a1").unwrap())
4291 );
4292 assert_eq!(
4293 VersionRequest::MajorMinorPrerelease(
4294 3,
4295 14,
4296 Prerelease {
4297 kind: PrereleaseKind::Beta,
4298 number: 2
4299 },
4300 PythonVariant::Default
4301 )
4302 .as_pep440_version(),
4303 Some(Version::from_str("3.14.0b2").unwrap())
4304 );
4305 assert_eq!(
4306 VersionRequest::MajorMinorPrerelease(
4307 3,
4308 13,
4309 Prerelease {
4310 kind: PrereleaseKind::Rc,
4311 number: 3
4312 },
4313 PythonVariant::Default
4314 )
4315 .as_pep440_version(),
4316 Some(Version::from_str("3.13.0rc3").unwrap())
4317 );
4318
4319 assert_eq!(
4321 VersionRequest::Major(3, PythonVariant::Freethreaded).as_pep440_version(),
4322 Some(Version::from_str("3").unwrap())
4323 );
4324 assert_eq!(
4325 VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded).as_pep440_version(),
4326 Some(Version::from_str("3.13").unwrap())
4327 );
4328 }
4329
4330 #[test]
4331 fn python_request_as_pep440_version() {
4332 assert_eq!(PythonRequest::Any.as_pep440_version(), None);
4334 assert_eq!(PythonRequest::Default.as_pep440_version(), None);
4335
4336 assert_eq!(
4338 PythonRequest::Version(VersionRequest::MajorMinor(3, 11, PythonVariant::Default))
4339 .as_pep440_version(),
4340 Some(Version::from_str("3.11").unwrap())
4341 );
4342
4343 assert_eq!(
4345 PythonRequest::ImplementationVersion(
4346 ImplementationName::CPython,
4347 VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default),
4348 )
4349 .as_pep440_version(),
4350 Some(Version::from_str("3.12.1").unwrap())
4351 );
4352
4353 assert_eq!(
4355 PythonRequest::Implementation(ImplementationName::CPython).as_pep440_version(),
4356 None
4357 );
4358
4359 assert_eq!(
4361 PythonRequest::parse("cpython-3.13.2").as_pep440_version(),
4362 Some(Version::from_str("3.13.2").unwrap())
4363 );
4364
4365 assert_eq!(
4367 PythonRequest::parse("cpython-macos-aarch64-none").as_pep440_version(),
4368 None
4369 );
4370
4371 assert_eq!(
4373 PythonRequest::Version(VersionRequest::from_str(">=3.10").unwrap()).as_pep440_version(),
4374 None
4375 );
4376 }
4377
4378 #[test]
4379 fn intersects_requires_python_exact() {
4380 let requires_python =
4381 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4382
4383 assert!(PythonRequest::parse("3.12").intersects_requires_python(&requires_python));
4384 assert!(!PythonRequest::parse("3.11").intersects_requires_python(&requires_python));
4385 }
4386
4387 #[test]
4388 fn intersects_requires_python_major() {
4389 let requires_python =
4390 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4391
4392 assert!(PythonRequest::parse("3").intersects_requires_python(&requires_python));
4394 assert!(!PythonRequest::parse("2").intersects_requires_python(&requires_python));
4396 }
4397
4398 #[test]
4399 fn intersects_requires_python_range() {
4400 let requires_python =
4401 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4402
4403 assert!(PythonRequest::parse(">=3.12,<3.13").intersects_requires_python(&requires_python));
4404 assert!(!PythonRequest::parse(">=3.10,<3.12").intersects_requires_python(&requires_python));
4405 }
4406
4407 #[test]
4408 fn intersects_requires_python_implementation_range() {
4409 let requires_python =
4410 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4411
4412 assert!(
4413 PythonRequest::parse("cpython@>=3.12,<3.13")
4414 .intersects_requires_python(&requires_python)
4415 );
4416 assert!(
4417 !PythonRequest::parse("cpython@>=3.10,<3.12")
4418 .intersects_requires_python(&requires_python)
4419 );
4420 }
4421
4422 #[test]
4423 fn intersects_requires_python_no_version() {
4424 let requires_python =
4425 RequiresPython::from_specifiers(&VersionSpecifiers::from_str(">=3.12").unwrap());
4426
4427 assert!(PythonRequest::Any.intersects_requires_python(&requires_python));
4429 assert!(PythonRequest::Default.intersects_requires_python(&requires_python));
4430 assert!(
4431 PythonRequest::Implementation(ImplementationName::CPython)
4432 .intersects_requires_python(&requires_python)
4433 );
4434 }
4435}