Skip to content

Commit

Permalink
Add support for editable packages in pip-uninstall (#670)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh authored Dec 17, 2023
1 parent f059c6e commit 08edd17
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 36 deletions.
62 changes: 48 additions & 14 deletions crates/puffin-cli/src/commands/pip_uninstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ pub(crate) async fn pip_uninstall(
let start = std::time::Instant::now();

// Read all requirements from the provided sources.
// TODO(konstin): Uninstall editable packages.
let (requirements, _editables) =
RequirementsSpecification::requirements_and_editables(sources)?;
let (requirements, editables) = RequirementsSpecification::requirements_and_editables(sources)?;

// Detect the current Python interpreter.
let platform = Platform::current()?;
Expand All @@ -37,7 +35,7 @@ pub(crate) async fn pip_uninstall(
// Index the current `site-packages` directory.
let site_packages = puffin_installer::SitePackages::from_executable(&venv)?;

// Sort and deduplicate the requirements.
// Sort and deduplicate the packages, which are keyed by name.
let packages = {
let mut packages = requirements
.into_iter()
Expand All @@ -48,24 +46,60 @@ pub(crate) async fn pip_uninstall(
packages
};

// Sort and deduplicate the editable packages, which are keyed by URL rather than package name.
let editables = {
let mut editables = editables
.iter()
.map(requirements_txt::EditableRequirement::raw)
.collect::<Vec<_>>();
editables.sort_unstable();
editables.dedup();
editables
};

// Map to the local distributions.
let distributions = packages
.iter()
.filter_map(|package| {
let distributions = {
let mut distributions = Vec::with_capacity(packages.len() + editables.len());

// Identify all packages that are installed.
for package in &packages {
if let Some(distribution) = site_packages.get(package) {
Some(distribution)
distributions.push(distribution);
} else {
let _ = writeln!(
writeln!(
printer,
"{}{} Skipping {} as it is not installed.",
"warning".yellow().bold(),
":".bold(),
package.as_ref().bold()
);
None
}
})
.collect::<Vec<_>>();
)?;
};
}

// Identify all editables that are installed.
for editable in &editables {
if let Some(distribution) = site_packages
.editables()
.find(|(_dist, url, _dir_info)| url == editable)
.map(|(dist, _url, _dir_info)| dist)
{
distributions.push(distribution);
} else {
writeln!(
printer,
"{}{} Skipping {} as it is not installed.",
"warning".yellow().bold(),
":".bold(),
editable.as_ref().bold()
)?;
};
}

// Deduplicate, since a package could be listed both by name and editable URL.
distributions.sort_unstable_by_key(|dist| dist.path());
distributions.dedup_by_key(|dist| dist.path());
distributions
};

if distributions.is_empty() {
writeln!(
Expand Down
13 changes: 9 additions & 4 deletions crates/puffin-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ struct PipSyncArgs {

#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
#[command(group = clap::ArgGroup::new("sources").required(true))]
#[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))]
struct PipInstallArgs {
/// Install all listed packages.
#[clap(group = "sources")]
Expand Down Expand Up @@ -335,7 +335,7 @@ struct PipInstallArgs {

#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
#[command(group = clap::ArgGroup::new("sources").required(true))]
#[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))]
struct PipUninstallArgs {
/// Uninstall all listed packages.
#[clap(group = "sources")]
Expand All @@ -344,6 +344,10 @@ struct PipUninstallArgs {
/// Uninstall all packages listed in the given requirements files.
#[clap(short, long, group = "sources")]
requirement: Vec<PathBuf>,

/// Uninstall the editable package based on the provided local file path.
#[clap(short, long, group = "sources")]
editable: Vec<String>,
}

#[derive(Args)]
Expand Down Expand Up @@ -475,7 +479,7 @@ async fn inner() -> Result<ExitStatus> {
let requirements = args
.package
.into_iter()
.map(RequirementsSource::from)
.map(RequirementsSource::Package)
.chain(args.requirement.into_iter().map(RequirementsSource::from))
.collect::<Vec<_>>();
let constraints = args
Expand Down Expand Up @@ -519,7 +523,8 @@ async fn inner() -> Result<ExitStatus> {
let sources = args
.package
.into_iter()
.map(RequirementsSource::from)
.map(RequirementsSource::Package)
.chain(args.editable.into_iter().map(RequirementsSource::Editable))
.chain(args.requirement.into_iter().map(RequirementsSource::from))
.collect::<Vec<_>>();
commands::pip_uninstall(&sources, cache, printer).await
Expand Down
26 changes: 17 additions & 9 deletions crates/puffin-cli/src/requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,16 @@ use requirements_txt::{EditableRequirement, RequirementsTxt};

#[derive(Debug)]
pub(crate) enum RequirementsSource {
/// A dependency was provided on the command line (e.g., `pip install flask`).
Name(String),
/// A package was provided on the command line (e.g., `pip install flask`).
Package(String),
/// An editable path was provided on the command line (e.g., `pip install -e ../flask`).
Editable(String),
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
RequirementsTxt(PathBuf),
/// Dependencies were provided via a `pyproject.toml` file (e.g., `pip-compile pyproject.toml`).
PyprojectToml(PathBuf),
}

impl From<String> for RequirementsSource {
fn from(name: String) -> Self {
Self::Name(name)
}
}

impl From<PathBuf> for RequirementsSource {
fn from(path: PathBuf) -> Self {
if path.ends_with("pyproject.toml") {
Expand Down Expand Up @@ -79,7 +75,7 @@ impl RequirementsSpecification {
extras: &ExtrasSpecification,
) -> Result<Self> {
Ok(match source {
RequirementsSource::Name(name) => {
RequirementsSource::Package(name) => {
let requirement = Requirement::from_str(name)
.with_context(|| format!("Failed to parse `{name}`"))?;
Self {
Expand All @@ -91,6 +87,18 @@ impl RequirementsSpecification {
extras: FxHashSet::default(),
}
}
RequirementsSource::Editable(name) => {
let requirement = EditableRequirement::from_str(name)
.with_context(|| format!("Failed to parse `{name}`"))?;
Self {
project: None,
requirements: vec![],
constraints: vec![],
overrides: vec![],
editables: vec![requirement],
extras: FxHashSet::default(),
}
}
RequirementsSource::RequirementsTxt(path) => {
let requirements_txt = RequirementsTxt::parse(path, std::env::current_dir()?)?;
Self {
Expand Down
Loading

0 comments on commit 08edd17

Please sign in to comment.