@@ -446,7 +446,16 @@ fn python_executables_from_installed<'a>(
446446 . flatten ( ) ;
447447
448448 match preference {
449- PythonPreference :: OnlyManaged => Box :: new ( from_managed_installations) ,
449+ PythonPreference :: OnlyManaged => {
450+ // TODO(zanieb): Ideally, we'd create "fake" managed installation directories for tests,
451+ // but for now... we'll just include the test interpreters which are always on the
452+ // search path.
453+ if std:: env:: var ( uv_static:: EnvVars :: UV_INTERNAL__TEST_PYTHON_MANAGED ) . is_ok ( ) {
454+ Box :: new ( from_managed_installations. chain ( from_search_path) )
455+ } else {
456+ Box :: new ( from_managed_installations)
457+ }
458+ }
450459 PythonPreference :: Managed => Box :: new (
451460 from_managed_installations
452461 . chain ( from_search_path)
@@ -730,6 +739,9 @@ fn python_interpreters<'a>(
730739 false
731740 }
732741 } )
742+ . filter_ok ( move |( source, interpreter) | {
743+ satisfies_python_preference ( * source, interpreter, preference)
744+ } )
733745}
734746
735747/// Lazily convert Python executables into interpreters.
@@ -857,6 +869,93 @@ fn source_satisfies_environment_preference(
857869 }
858870}
859871
872+ /// Returns true if a Python interpreter matches the [`PythonPreference`].
873+ pub fn satisfies_python_preference (
874+ source : PythonSource ,
875+ interpreter : & Interpreter ,
876+ preference : PythonPreference ,
877+ ) -> bool {
878+ // If the source is "explicit", we will not apply the Python preference, e.g., if the user has
879+ // activated a virtual environment, we should always allow it. We may want to invalidate the
880+ // environment in some cases, like in projects, but we can't distinguish between explicit
881+ // requests for a different Python preference or a persistent preference in a configuration file
882+ // which would result in overly aggressive invalidation.
883+ let is_explicit = match source {
884+ PythonSource :: ProvidedPath
885+ | PythonSource :: ParentInterpreter
886+ | PythonSource :: ActiveEnvironment
887+ | PythonSource :: CondaPrefix => true ,
888+ PythonSource :: Managed
889+ | PythonSource :: DiscoveredEnvironment
890+ | PythonSource :: SearchPath
891+ | PythonSource :: SearchPathFirst
892+ | PythonSource :: Registry
893+ | PythonSource :: MicrosoftStore
894+ | PythonSource :: BaseCondaPrefix => false ,
895+ } ;
896+
897+ match preference {
898+ PythonPreference :: OnlyManaged => {
899+ // Perform a fast check using the source before querying the interpreter
900+ if matches ! ( source, PythonSource :: Managed ) || interpreter. is_managed ( ) {
901+ true
902+ } else {
903+ if is_explicit {
904+ debug ! (
905+ "Allowing unmanaged Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}" ,
906+ interpreter. sys_executable( ) . display( )
907+ ) ;
908+ true
909+ } else {
910+ debug ! (
911+ "Ignoring Python interpreter at `{}`: only managed interpreters allowed" ,
912+ interpreter. sys_executable( ) . display( )
913+ ) ;
914+ false
915+ }
916+ }
917+ }
918+ // If not "only" a kind, any interpreter is okay
919+ PythonPreference :: Managed | PythonPreference :: System => true ,
920+ PythonPreference :: OnlySystem => {
921+ let is_system = match source {
922+ // A managed interpreter is never a system interpreter
923+ PythonSource :: Managed => false ,
924+ // We can't be sure if this is a system interpreter without checking
925+ PythonSource :: ProvidedPath
926+ | PythonSource :: ParentInterpreter
927+ | PythonSource :: ActiveEnvironment
928+ | PythonSource :: CondaPrefix
929+ | PythonSource :: DiscoveredEnvironment
930+ | PythonSource :: SearchPath
931+ | PythonSource :: SearchPathFirst
932+ | PythonSource :: Registry
933+ | PythonSource :: BaseCondaPrefix => !interpreter. is_managed ( ) ,
934+ // Managed interpreters should never be found in the store
935+ PythonSource :: MicrosoftStore => true ,
936+ } ;
937+
938+ if is_system {
939+ true
940+ } else {
941+ if is_explicit {
942+ debug ! (
943+ "Allowing managed Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}" ,
944+ interpreter. sys_executable( ) . display( )
945+ ) ;
946+ true
947+ } else {
948+ debug ! (
949+ "Ignoring Python interpreter at `{}`: only system interpreters allowed" ,
950+ interpreter. sys_executable( ) . display( )
951+ ) ;
952+ false
953+ }
954+ }
955+ }
956+ }
957+ }
958+
860959/// Check if an encountered error is critical and should stop discovery.
861960///
862961/// Returns false when an error could be due to a faulty Python installation and we should continue searching for a working one.
@@ -2812,6 +2911,18 @@ impl PythonPreference {
28122911 }
28132912 }
28142913 }
2914+
2915+ /// Return the canonical name.
2916+ // TODO(zanieb): This should be a `Display` impl and we should have a different view for
2917+ // the sources
2918+ pub fn canonical_name ( & self ) -> & ' static str {
2919+ match self {
2920+ Self :: OnlyManaged => "only managed" ,
2921+ Self :: Managed => "prefer managed" ,
2922+ Self :: System => "prefer system" ,
2923+ Self :: OnlySystem => "only system" ,
2924+ }
2925+ }
28152926}
28162927
28172928impl fmt:: Display for PythonPreference {
0 commit comments