Enable freeze and list to introspect non-virtualenv Pythons (#2033)

## Summary

Now that we have the ability to introspect the installed packages for
arbitrary Pythons, we can allow `pip freeze` and `pip list` to fall back
to the "default" Python, if no virtualenv is present.

Closes https://github.com/astral-sh/uv/issues/2005.
This commit is contained in:
Charlie Marsh 2024-02-28 10:00:00 -05:00 committed by GitHub
parent 23afa09fae
commit 02703281f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 74 additions and 10 deletions

View file

@ -68,7 +68,7 @@ pub fn find_requested_python(
}
}
/// Pick a sensible default for the python a user wants when they didn't specify a version.
/// Pick a sensible default for the Python a user wants when they didn't specify a version.
///
/// We prefer the test overwrite `UV_TEST_PYTHON_PATH` if it is set, otherwise `python3`/`python` or
/// `python.exe` respectively.

View file

@ -9,7 +9,7 @@ use uv_fs::{LockedFile, Normalized};
use crate::cfg::PyVenvConfiguration;
use crate::python_platform::PythonPlatform;
use crate::{find_requested_python, Error, Interpreter};
use crate::{find_default_python, find_requested_python, Error, Interpreter};
/// A Python executable and its associated platform markers.
#[derive(Debug, Clone)]
@ -65,6 +65,15 @@ impl Virtualenv {
})
}
/// Create a [`Virtualenv`] for the default Python interpreter.
pub fn from_default_python(platform: &Platform, cache: &Cache) -> Result<Self, Error> {
let interpreter = find_default_python(platform, cache)?;
Ok(Self {
root: interpreter.base_prefix().to_path_buf(),
interpreter,
})
}
/// Returns the location of the Python interpreter.
pub fn root(&self) -> &Path {
&self.root

View file

@ -12,8 +12,8 @@ use crate::printer::Printer;
/// Clear the cache.
pub(crate) fn cache_clean(
cache: &Cache,
packages: &[PackageName],
cache: &Cache,
mut printer: Printer,
) -> Result<ExitStatus> {
if !cache.root().exists() {

View file

@ -17,10 +17,25 @@ use crate::commands::ExitStatus;
use crate::printer::Printer;
/// Enumerate the installed packages in the current environment.
pub(crate) fn pip_freeze(cache: &Cache, strict: bool, mut printer: Printer) -> Result<ExitStatus> {
pub(crate) fn pip_freeze(
strict: bool,
python: Option<&str>,
cache: &Cache,
mut printer: Printer,
) -> Result<ExitStatus> {
// Detect the current Python interpreter.
let platform = Platform::current()?;
let venv = Virtualenv::from_env(platform, cache)?;
let venv = if let Some(python) = python {
Virtualenv::from_requested_python(python, &platform, cache)?
} else {
match Virtualenv::from_env(platform.clone(), cache) {
Ok(venv) => venv,
Err(uv_interpreter::Error::VenvNotFound) => {
Virtualenv::from_default_python(&platform, cache)?
}
Err(err) => return Err(err.into()),
}
};
debug!(
"Using Python {} environment at {}",

View file

@ -21,16 +21,27 @@ use crate::printer::Printer;
/// Enumerate the installed packages in the current environment.
pub(crate) fn pip_list(
cache: &Cache,
strict: bool,
editable: bool,
exclude_editable: bool,
exclude: &[PackageName],
python: Option<&str>,
cache: &Cache,
mut printer: Printer,
) -> Result<ExitStatus> {
// Detect the current Python interpreter.
let platform = Platform::current()?;
let venv = Virtualenv::from_env(platform, cache)?;
let venv = if let Some(python) = python {
Virtualenv::from_requested_python(python, &platform, cache)?
} else {
match Virtualenv::from_env(platform.clone(), cache) {
Ok(venv) => venv,
Err(uv_interpreter::Error::VenvNotFound) => {
Virtualenv::from_default_python(&platform, cache)?
}
Err(err) => return Err(err.into()),
}
};
debug!(
"Using Python {} environment at {}",

View file

@ -730,6 +730,20 @@ struct PipFreezeArgs {
/// issues.
#[clap(long)]
strict: bool,
/// The Python interpreter for which packages should be listed.
///
/// By default, `uv` lists packages in the currently activated virtual environment, or a virtual
/// environment (`.venv`) located in the current working directory or any parent directory,
/// falling back to the system Python if no virtual environment is found.
///
/// Supported formats:
/// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or
/// `python3.10` on Linux and macOS. (Specifying a patch version is not supported.)
/// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`.
/// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.
#[clap(long, short, verbatim_doc_comment)]
python: Option<String>,
}
#[derive(Args)]
@ -751,6 +765,20 @@ struct PipListArgs {
/// Exclude the specified package(s) from the output.
#[clap(long)]
r#exclude: Vec<PackageName>,
/// The Python interpreter for which packages should be listed.
///
/// By default, `uv` lists packages in the currently activated virtual environment, or a virtual
/// environment (`.venv`) located in the current working directory or any parent directory,
/// falling back to the system Python if no virtual environment is found.
///
/// Supported formats:
/// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or
/// `python3.10` on Linux and macOS. (Specifying a patch version is not supported.)
/// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`.
/// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.
#[clap(long, short, verbatim_doc_comment)]
python: Option<String>,
}
#[derive(Args)]
@ -1160,21 +1188,22 @@ async fn run() -> Result<ExitStatus> {
}
Commands::Pip(PipNamespace {
command: PipCommand::Freeze(args),
}) => commands::pip_freeze(&cache, args.strict, printer),
}) => commands::pip_freeze(args.strict, args.python.as_deref(), &cache, printer),
Commands::Pip(PipNamespace {
command: PipCommand::List(args),
}) => commands::pip_list(
&cache,
args.strict,
args.editable,
args.exclude_editable,
&args.exclude,
args.python.as_deref(),
&cache,
printer,
),
Commands::Cache(CacheNamespace {
command: CacheCommand::Clean(args),
})
| Commands::Clean(args) => commands::cache_clean(&cache, &args.package, printer),
| Commands::Clean(args) => commands::cache_clean(&args.package, &cache, printer),
Commands::Cache(CacheNamespace {
command: CacheCommand::Dir,
}) => {