@@ -193,6 +193,8 @@ pub enum PythonSource {
193
193
DiscoveredEnvironment ,
194
194
/// An executable was found in the search path i.e. `PATH`
195
195
SearchPath ,
196
+ /// The first executable found in the search path i.e. `PATH`
197
+ SearchPathFirst ,
196
198
/// An executable was found in the Windows registry via PEP 514
197
199
Registry ,
198
200
/// An executable was found in the known Microsoft Store locations
@@ -331,7 +333,14 @@ fn python_executables_from_installed<'a>(
331
333
332
334
let from_search_path = iter:: once_with ( move || {
333
335
python_executables_from_search_path ( version, implementation)
334
- . map ( |path| Ok ( ( PythonSource :: SearchPath , path) ) )
336
+ . enumerate ( )
337
+ . map ( |( i, path) | {
338
+ if i == 0 {
339
+ Ok ( ( PythonSource :: SearchPathFirst , path) )
340
+ } else {
341
+ Ok ( ( PythonSource :: SearchPath , path) )
342
+ }
343
+ } )
335
344
} )
336
345
. flatten ( ) ;
337
346
@@ -1049,7 +1058,10 @@ pub(crate) fn find_python_installation(
1049
1058
// If the interpreter has a default executable name, e.g. `python`, and was found on the
1050
1059
// search path, we consider this opt-in to use it.
1051
1060
let has_default_executable_name = installation. interpreter . has_default_executable_name ( )
1052
- && installation. source == PythonSource :: SearchPath ;
1061
+ && matches ! (
1062
+ installation. source,
1063
+ PythonSource :: SearchPath | PythonSource :: SearchPathFirst
1064
+ ) ;
1053
1065
1054
1066
// If it's a pre-release and pre-releases aren't allowed, skip it — but store it for later
1055
1067
// since we'll use a pre-release if no other versions are available.
@@ -1601,6 +1613,7 @@ impl PythonSource {
1601
1613
match self {
1602
1614
Self :: Managed | Self :: Registry | Self :: MicrosoftStore => false ,
1603
1615
Self :: SearchPath
1616
+ | Self :: SearchPathFirst
1604
1617
| Self :: CondaPrefix
1605
1618
| Self :: BaseCondaPrefix
1606
1619
| Self :: ProvidedPath
@@ -1613,7 +1626,13 @@ impl PythonSource {
1613
1626
/// Whether an alternative Python implementation from this source can be used without opt-in.
1614
1627
pub ( crate ) fn allows_alternative_implementations ( self ) -> bool {
1615
1628
match self {
1616
- Self :: Managed | Self :: Registry | Self :: SearchPath | Self :: MicrosoftStore => false ,
1629
+ Self :: Managed
1630
+ | Self :: Registry
1631
+ | Self :: SearchPath
1632
+ // TODO(zanieb): We may want to allow this at some point, but when adding this variant
1633
+ // we want compatibility with existing behavior
1634
+ | Self :: SearchPathFirst
1635
+ | Self :: MicrosoftStore => false ,
1617
1636
Self :: CondaPrefix
1618
1637
| Self :: BaseCondaPrefix
1619
1638
| Self :: ProvidedPath
@@ -1629,15 +1648,20 @@ impl PythonSource {
1629
1648
/// environment; pragmatically, that's not common and saves us from querying a bunch of system
1630
1649
/// interpreters for no reason. It seems dubious to consider an interpreter in the `PATH` as a
1631
1650
/// target virtual environment if it's not discovered through our virtual environment-specific
1632
- /// patterns.
1651
+ /// patterns. Instead, we special case the first Python executable found on the `PATH` with
1652
+ /// [`PythonSource::SearchPathFirst`], allowing us to check if that's a virtual environment.
1653
+ /// This enables targeting the virtual environment with uv by putting its `bin/` on the `PATH`
1654
+ /// without setting `VIRTUAL_ENV` — but if there's another interpreter before it we will ignore
1655
+ /// it.
1633
1656
pub ( crate ) fn is_maybe_virtualenv ( self ) -> bool {
1634
1657
match self {
1635
1658
Self :: ProvidedPath
1636
1659
| Self :: ActiveEnvironment
1637
1660
| Self :: DiscoveredEnvironment
1638
1661
| Self :: CondaPrefix
1639
1662
| Self :: BaseCondaPrefix
1640
- | Self :: ParentInterpreter => true ,
1663
+ | Self :: ParentInterpreter
1664
+ | Self :: SearchPathFirst => true ,
1641
1665
Self :: Managed | Self :: SearchPath | Self :: Registry | Self :: MicrosoftStore => false ,
1642
1666
}
1643
1667
}
@@ -1651,6 +1675,7 @@ impl PythonSource {
1651
1675
| Self :: ProvidedPath
1652
1676
| Self :: Managed
1653
1677
| Self :: SearchPath
1678
+ | Self :: SearchPathFirst
1654
1679
| Self :: Registry
1655
1680
| Self :: MicrosoftStore => true ,
1656
1681
Self :: ActiveEnvironment | Self :: DiscoveredEnvironment => false ,
@@ -2062,6 +2087,7 @@ impl VersionRequest {
2062
2087
| PythonSource :: DiscoveredEnvironment
2063
2088
| PythonSource :: ActiveEnvironment => Self :: Any ,
2064
2089
PythonSource :: SearchPath
2090
+ | PythonSource :: SearchPathFirst
2065
2091
| PythonSource :: Registry
2066
2092
| PythonSource :: MicrosoftStore
2067
2093
| PythonSource :: Managed => Self :: Default ,
@@ -2473,6 +2499,7 @@ impl fmt::Display for PythonSource {
2473
2499
Self :: CondaPrefix | Self :: BaseCondaPrefix => f. write_str ( "conda prefix" ) ,
2474
2500
Self :: DiscoveredEnvironment => f. write_str ( "virtual environment" ) ,
2475
2501
Self :: SearchPath => f. write_str ( "search path" ) ,
2502
+ Self :: SearchPathFirst => f. write_str ( "first executable in the search path" ) ,
2476
2503
Self :: Registry => f. write_str ( "registry" ) ,
2477
2504
Self :: MicrosoftStore => f. write_str ( "Microsoft Store" ) ,
2478
2505
Self :: Managed => f. write_str ( "managed installations" ) ,
0 commit comments