@@ -594,6 +594,9 @@ fn python_interpreters<'a>(
594594 )
595595 . filter ( move |result| result_satisfies_environment_preference ( result, environments) )
596596 . filter ( move |result| result_satisfies_version_request ( result, version) )
597+ // Ensure that interpreters, e.g., from the search path, meet the preference for managed
598+ // interpreters since the user can link them to arbitrary locations.
599+ . filter ( move |result| result_satisfies_python_preference ( result, preference) )
597600}
598601
599602/// Lazily convert Python executables into interpreters.
@@ -696,6 +699,101 @@ fn result_satisfies_environment_preference(
696699 } )
697700}
698701
702+ /// Returns true if a Python interpreter matches the [`PythonPreference`].
703+ pub fn satisfies_python_preference (
704+ source : PythonSource ,
705+ interpreter : & Interpreter ,
706+ preference : PythonPreference ,
707+ ) -> bool {
708+ // If the source is "explicit", we will not apply the Python preference, e.g., if the user has
709+ // activated a virtual environment, we should always respect allow it. We may want to invalidate
710+ // the environment in some cases, like in projects, but we can't distinguish between explicit
711+ // requests for a different Python preference or a persistent preference in a configuration file
712+ // which would result in overly aggressive invalidation.
713+ let is_explicit = match source {
714+ PythonSource :: ProvidedPath
715+ | PythonSource :: ParentInterpreter
716+ | PythonSource :: ActiveEnvironment
717+ | PythonSource :: CondaPrefix => true ,
718+ PythonSource :: Managed
719+ | PythonSource :: DiscoveredEnvironment
720+ | PythonSource :: SearchPath
721+ | PythonSource :: Registry
722+ | PythonSource :: MicrosoftStore
723+ | PythonSource :: BaseCondaPrefix => false ,
724+ } ;
725+
726+ match preference {
727+ PythonPreference :: OnlyManaged => {
728+ // Perform a fast check using the source before querying the interpreter
729+ if matches ! ( source, PythonSource :: Managed ) || interpreter. is_managed ( ) {
730+ true
731+ } else {
732+ if is_explicit {
733+ debug ! (
734+ "Allowing unmanaged Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}" ,
735+ interpreter. sys_executable( ) . display( )
736+ ) ;
737+ true
738+ } else {
739+ debug ! (
740+ "Ignoring Python interpreter at `{}`: only managed interpreters allowed" ,
741+ interpreter. sys_executable( ) . display( )
742+ ) ;
743+ false
744+ }
745+ }
746+ }
747+ // If not "only" a kind, any interpreter is okay
748+ PythonPreference :: Managed | PythonPreference :: System => true ,
749+ PythonPreference :: OnlySystem => {
750+ let is_system = match source {
751+ // A managed interpreter is never a system interpreter
752+ PythonSource :: Managed => false ,
753+ // We can't be sure if this is a system interpreter without checking
754+ PythonSource :: ProvidedPath
755+ | PythonSource :: ParentInterpreter
756+ | PythonSource :: ActiveEnvironment
757+ | PythonSource :: CondaPrefix
758+ | PythonSource :: DiscoveredEnvironment
759+ | PythonSource :: SearchPath
760+ | PythonSource :: Registry
761+ | PythonSource :: BaseCondaPrefix => !interpreter. is_managed ( ) ,
762+ // Managed interpreters should never be found in the store
763+ PythonSource :: MicrosoftStore => true ,
764+ } ;
765+
766+ if is_system {
767+ true
768+ } else {
769+ if is_explicit {
770+ debug ! (
771+ "Allowing managed Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}" ,
772+ interpreter. sys_executable( ) . display( )
773+ ) ;
774+ true
775+ } else {
776+ debug ! (
777+ "Ignoring Python interpreter at `{}`: only system interpreters allowed" ,
778+ interpreter. sys_executable( ) . display( )
779+ ) ;
780+ false
781+ }
782+ }
783+ }
784+ }
785+ }
786+
787+ /// Utility for applying [`satisfies_python_preference`] to a result type.
788+ fn result_satisfies_python_preference (
789+ result : & Result < ( PythonSource , Interpreter ) , Error > ,
790+ preference : PythonPreference ,
791+ ) -> bool {
792+ result. as_ref ( ) . ok ( ) . map_or ( true , |( source, interpreter) | {
793+ satisfies_python_preference ( * source, interpreter, preference)
794+ } )
795+ }
796+
699797/// Check if an encountered error is critical and should stop discovery.
700798///
701799/// Returns false when an error could be due to a faulty Python installation and we should continue searching for a working one.
@@ -2295,6 +2393,18 @@ impl PythonPreference {
22952393 }
22962394 }
22972395 }
2396+
2397+ /// Return the canonical name.
2398+ // TODO(zanieb): This should be a `Display` impl and we should have a different view for
2399+ // the sources
2400+ pub fn canonical_name ( & self ) -> & ' static str {
2401+ match self {
2402+ Self :: OnlyManaged => "only managed" ,
2403+ Self :: Managed => "prefer managed" ,
2404+ Self :: System => "prefer system" ,
2405+ Self :: OnlySystem => "only system" ,
2406+ }
2407+ }
22982408}
22992409
23002410impl fmt:: Display for PythonPreference {
0 commit comments