Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: pypi dependencies not being removed #952

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/install_pypi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ fn elapsed(duration: Duration) -> String {
}

/// Derived from uv [`uv_installer::Plan`]
#[derive(Debug)]
struct PixiInstallPlan {
/// The distributions that are not already installed in the current environment, but are
/// available in the local cache.
Expand Down
52 changes: 25 additions & 27 deletions src/lock_file/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,35 +107,33 @@ impl<'p> LockFileDerivedData<'p> {
.unwrap_or_default();
let pypi_records = self.pypi_records(environment, platform).unwrap_or_default();

if environment.has_pypi_dependencies() {
let uv_context = match &self.uv_context {
None => {
let context = UvResolutionContext::from_project(self.project)?;
self.uv_context = Some(context.clone());
context
}
Some(context) => context.clone(),
};
let uv_context = match &self.uv_context {
None => {
let context = UvResolutionContext::from_project(self.project)?;
self.uv_context = Some(context.clone());
context
}
Some(context) => context.clone(),
};

let env_variables = environment.project().get_env_variables(environment).await?;
// Update the prefix with Pypi records
environment::update_prefix_pypi(
environment.name(),
&prefix,
platform,
&repodata_records,
&pypi_records,
&python_status,
&environment.system_requirements(),
uv_context,
env_variables,
)
.await?;
let env_variables = environment.project().get_env_variables(environment).await?;
// Update the prefix with Pypi records
environment::update_prefix_pypi(
environment.name(),
&prefix,
platform,
&repodata_records,
&pypi_records,
&python_status,
&environment.system_requirements(),
uv_context,
env_variables,
)
.await?;

// Store that we updated the environment, so we won't have to do it again.
self.updated_pypi_prefixes
.insert(environment.clone(), prefix.clone());
}
// Store that we updated the environment, so we won't have to do it again.
self.updated_pypi_prefixes
.insert(environment.clone(), prefix.clone());

Ok(prefix)
}
Expand Down
48 changes: 48 additions & 0 deletions tests/common/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
//! ```

use futures::FutureExt;
use pixi::cli::remove;
use pixi::task::TaskName;
use pixi::{
cli::{add, init, install, project, task},
Expand Down Expand Up @@ -143,6 +144,53 @@ impl IntoFuture for AddBuilder {
}
}

/// Contains the arguments to pass to [`remove::execute()`]. Call `.await` to call the CLI execute method
/// and await the result at the same time.
pub struct RemoveBuilder {
pub args: remove::Args,
}

impl RemoveBuilder {
pub fn with_spec(mut self, spec: &str) -> Self {
self.args.deps.push(spec.to_string());
self
}

/// Set as a host
pub fn set_type(mut self, t: DependencyType) -> Self {
match t {
DependencyType::CondaDependency(spec_type) => match spec_type {
SpecType::Host => {
self.args.host = true;
self.args.build = false;
}
SpecType::Build => {
self.args.host = false;
self.args.build = true;
}
SpecType::Run => {
self.args.host = false;
self.args.build = false;
}
},
DependencyType::PypiDependency => {
self.args.host = false;
self.args.build = false;
self.args.pypi = true;
}
}
self
}
}

impl IntoFuture for RemoveBuilder {
type Output = miette::Result<()>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + 'static>>;

fn into_future(self) -> Self::IntoFuture {
remove::execute(self.args).boxed_local()
}
}
pub struct TaskAddBuilder {
pub manifest_path: Option<PathBuf>,
pub args: task::AddArgs,
Expand Down
19 changes: 18 additions & 1 deletion tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use pixi::{
cli::{
add, init,
install::Args,
project, run,
project, remove, run,
task::{self, AddArgs, AliasArgs},
},
consts, EnvironmentName, ExecutableTask, Project, RunOutput, SearchEnvironments, TaskGraph,
Expand All @@ -35,6 +35,8 @@ use std::{
use tempfile::TempDir;
use thiserror::Error;

use self::builders::RemoveBuilder;

/// To control the pixi process
pub struct PixiControl {
/// The path to the project working file
Expand Down Expand Up @@ -229,6 +231,21 @@ impl PixiControl {
}
}

/// Remove dependencies from the project. Returns a [`RemoveBuilder`].
pub fn remove(&self, spec: &str) -> RemoveBuilder {
RemoveBuilder {
args: remove::Args {
deps: vec![spec.to_string()],
manifest_path: Some(self.manifest_path()),
host: false,
build: false,
pypi: false,
platform: Default::default(),
feature: None,
},
}
}

/// Add a new channel to the project.
pub fn project_channel_add(&self) -> ProjectChannelAddBuilder {
ProjectChannelAddBuilder {
Expand Down
35 changes: 35 additions & 0 deletions tests/install_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,41 @@ async fn pypi_reinstall_python() {
}
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[cfg_attr(not(feature = "slow_integration_tests"), ignore)]
// Check if we add and remove a pypi package that the site-packages is cleared
async fn pypi_add_remove() {
let pixi = PixiControl::new().unwrap();
pixi.init().await.unwrap();
// Add and update lockfile with this version of python
pixi.add("python==3.11").with_install(true).await.unwrap();

// Add flask from pypi
pixi.add("flask")
.with_install(true)
.set_type(pixi::DependencyType::PypiDependency)
.await
.unwrap();

let prefix = pixi.project().unwrap().root().join(".pixi/envs/default");

let cache = uv_cache::Cache::temp().unwrap();

// Check if site-packages has entries
let env = create_uv_environment(&prefix, &cache);
let installed_311 = uv_installer::SitePackages::from_executable(&env).unwrap();
assert!(installed_311.iter().count() > 0);

pixi.remove("flask")
.set_type(pixi::DependencyType::PypiDependency)
.await
.unwrap();

let installed_311 = uv_installer::SitePackages::from_executable(&env).unwrap();
assert!(installed_311.iter().count() == 0);
}

#[tokio::test]
async fn test_channels_changed() {
// Write a channel with a package `bar` with only one version
Expand Down
Loading