Skip to content

Commit

Permalink
chore: Update python_package_manager to install multiple packages at …
Browse files Browse the repository at this point in the history
…once
  • Loading branch information
jatin711-debug committed Aug 3, 2024
1 parent 695f643 commit 96137f9
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 42 deletions.
34 changes: 18 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::process::Command;
use std::path::Path;

#[derive(Parser)]
Expand All @@ -15,7 +16,7 @@ pub struct Cli {

#[derive(Subcommand)]
pub enum Commands {
Install { name: Option<String>, version: Option<String>, requirements: Option<String> },
Install { packages: Vec<String> },
Delete { name: String },
Update { name: String, version: String },
List,
Expand Down Expand Up @@ -62,8 +63,6 @@ pub fn run_command(_command: &str) -> bool {

#[cfg(not(test))]
pub fn run_command(command: &str) -> bool {
use std::process::Command;

if cfg!(target_os = "windows") {
Command::new("cmd")
.args(&["/C", command])
Expand All @@ -80,18 +79,15 @@ pub fn run_command(command: &str) -> bool {
}
}

pub fn install_package(name: &str, version: Option<&str>, packages: &mut PackageRegistry) {
let version = version.unwrap_or("latest");
let install_command = if version == "latest" {
format!("pip install {}", name)
} else {
format!("pip install {}=={}", name, version)
};
if run_command(&install_command) {
packages.packages.insert(name.to_string(), version.to_string());
println!("Package {} installed successfully", name);
} else {
println!("Failed to install package {}", name);
pub fn install_packages(package_names: &[String], packages: &mut PackageRegistry) {
for name in package_names {
let install_command = format!("pip install {}", name);
if run_command(&install_command) {
packages.packages.insert(name.clone(), "latest".to_string());
println!("Package {} installed successfully", name);
} else {
println!("Failed to install package {}", name);
}
}
}

Expand All @@ -118,7 +114,13 @@ pub fn install_from_requirements(requirements: &str, packages: &mut PackageRegis
if let Ok(data) = fs::read_to_string(requirements) {
if let Ok(registry) = serde_json::from_str::<PackageRegistry>(&data) {
for (name, version) in registry.packages {
install_package(&name, Some(&version), packages);
let install_command = format!("pip install {}=={}", name, version);
if run_command(&install_command) {
packages.packages.insert(name.clone(), version.clone());
println!("Package {} installed successfully", name);
} else {
println!("Failed to install package {}", name);
}
}
} else {
println!("Failed to parse the requirements file.");
Expand Down
23 changes: 11 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
use clap::Parser;
use python_package_manager::{Cli, Commands, load_packages, save_packages, install_package, delete_package, update_package, list_packages, install_from_requirements};
use python_package_manager::{Cli, Commands, load_packages, save_packages, install_packages, delete_package, update_package, list_packages, install_from_requirements};

fn main() {
let args = Cli::parse();
let mut packages = load_packages();
let mut package_registry = load_packages();

match args.command {
Commands::Install { name, version, requirements } => {
if let Some(requirements) = requirements {
install_from_requirements(&requirements, &mut packages);
} else if let Some(name) = name {
install_package(&name, version.as_deref(), &mut packages);
Commands::Install { packages } => {
if packages.len() == 1 && packages[0].starts_with("-r=") {
let requirements_path = &packages[0][3..];
install_from_requirements(requirements_path, &mut package_registry);
} else {
println!("You must specify a package name or a requirements file.");
install_packages(&packages, &mut package_registry);
}
}
Commands::Delete { name } => {
delete_package(&name, &mut packages);
delete_package(&name, &mut package_registry);
}
Commands::Update { name, version } => {
update_package(&name, &version, &mut packages);
update_package(&name, &version, &mut package_registry);
}
Commands::List => {
list_packages(&packages);
list_packages(&package_registry);
}
}

save_packages(&packages);
save_packages(&package_registry);
}
163 changes: 149 additions & 14 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,87 @@
use std::fs;
use std::path::Path;
use std::process::{Command, Output};
use tempfile::tempdir;
use python_package_manager::{PackageRegistry, load_packages_from_path, save_packages_to_path, install_package, delete_package, update_package, list_packages};
use python_package_manager::{PackageRegistry, load_packages_from_path, save_packages_to_path, install_packages, delete_package, update_package, list_packages, install_from_requirements};

fn get_python_interpreter() -> String {
let version_output: Output = if cfg!(target_os = "windows") {
Command::new("cmd")
.args(&["/C", "where python"])
.output()
.expect("Failed to execute command")
} else {
Command::new("sh")
.arg("-c")
.arg("which python3")
.output()
.expect("Failed to execute command")
};

if !version_output.status.success() {
panic!("Failed to find Python interpreter: {}", String::from_utf8_lossy(&version_output.stderr));
}

let interpreter_path = String::from_utf8_lossy(&version_output.stdout)
.lines()
.next()
.expect("No Python interpreter found")
.to_string();

// Verify the Python version is 3.10 or above
let version_check = Command::new(&interpreter_path)
.arg("--version")
.output()
.expect("Failed to check Python version");

if !version_check.status.success() {
panic!("Failed to check Python version: {}", String::from_utf8_lossy(&version_check.stderr));
}

let version_str = String::from_utf8_lossy(&version_check.stdout);
if version_str.contains("Python 3.10") || version_str.contains("Python 3.11") {
interpreter_path
} else {
panic!("Python version 3.10 or above is required. Found: {}", version_str);
}
}

fn create_virtualenv(env_path: &Path) {
let python_interpreter = get_python_interpreter();

let output = Command::new(&python_interpreter)
.args(&["-m", "venv", env_path.to_str().unwrap()])
.output()
.expect("Failed to create virtual environment");

if !output.status.success() {
panic!(
"Failed to create virtual environment: {}",
String::from_utf8_lossy(&output.stderr)
);
}
}

fn run_command_in_virtualenv(env_path: &Path, args: &[&str]) -> bool {
let bin_path = if cfg!(target_os = "windows") {
env_path.join("Scripts").join("pip")
} else {
env_path.join("bin").join("pip")
};

let output = Command::new(bin_path)
.args(args)
.output()
.expect("Failed to run command in virtual environment");

if !output.status.success() {
println!("Command failed: {}", String::from_utf8_lossy(&output.stderr));
} else {
println!("Command succeeded: {}", String::from_utf8_lossy(&output.stdout));
}

output.status.success()
}

#[test]
fn test_load_empty_packages() {
Expand All @@ -26,7 +107,7 @@ fn test_load_packages() {
fn test_save_packages() {
let dir = tempdir().expect("Failed to create temp dir");
let file_path = dir.path().join("requirements.json");
let mut packages = PackageRegistry::new();
let mut packages: PackageRegistry = PackageRegistry::new();
packages.packages.insert("pandas".to_string(), "1.0.0".to_string());
save_packages_to_path(&packages, &file_path);
let data = fs::read_to_string(&file_path).expect("Failed to read from temp file");
Expand All @@ -38,41 +119,71 @@ fn test_save_packages() {
}

#[test]
fn test_install_package() {
fn test_install_packages() {
let dir = tempdir().expect("Failed to create temp dir");
let file_path: std::path::PathBuf = dir.path().join("requirements.json");
let env_path = dir.path().join("env");
create_virtualenv(&env_path);

let file_path = dir.path().join("requirements.json");
fs::write(&file_path, "{}").expect("Failed to write to temp file");

let mut packages = PackageRegistry::new();
install_package("pandas", Some("latest"), &mut packages);
let package_list = vec!["pandas".to_string(), "numpy".to_string(), "scipy".to_string()];
for package in &package_list {
if !run_command_in_virtualenv(&env_path, &["install", package]) {
panic!("Failed to install {}", package);
}
}

install_packages(&package_list, &mut packages);
save_packages_to_path(&packages, &file_path);
let updated_packages = load_packages_from_path(&file_path);
assert_eq!(updated_packages.packages.get("pandas").unwrap(), "latest");

for package in &package_list {
assert_eq!(updated_packages.packages.get(package).unwrap(), "latest");
}
}

#[test]
fn test_delete_package() {
let dir = tempdir().expect("Failed to create temp dir");
let env_path = dir.path().join("env");
create_virtualenv(&env_path);

let file_path = dir.path().join("requirements.json");
let mut packages = PackageRegistry::new();
packages.packages.insert("pandas".to_string(), "1.0.0".to_string());
save_packages_to_path(&packages, &file_path);
delete_package("pandas", &mut packages);
save_packages_to_path(&packages, &file_path);
let updated_packages = load_packages_from_path(&file_path);
assert!(updated_packages.packages.get("pandas").is_none());

if run_command_in_virtualenv(&env_path, &["uninstall", "-y", "pandas"]) {
delete_package("pandas", &mut packages);
save_packages_to_path(&packages, &file_path);
let updated_packages = load_packages_from_path(&file_path);
assert!(updated_packages.packages.get("pandas").is_none());
} else {
panic!("Failed to uninstall pandas");
}
}

#[test]
fn test_update_package() {
let dir = tempdir().expect("Failed to create temp dir");
let env_path = dir.path().join("env");
create_virtualenv(&env_path);

let file_path = dir.path().join("requirements.json");
let mut packages = PackageRegistry::new();
packages.packages.insert("pandas".to_string(), "1.0.0".to_string());
save_packages_to_path(&packages, &file_path);
update_package("pandas", "2.0.0", &mut packages);
save_packages_to_path(&packages, &file_path);
let updated_packages = load_packages_from_path(&file_path);
assert_eq!(updated_packages.packages.get("pandas").unwrap(), "2.0.0");

if run_command_in_virtualenv(&env_path, &["install", "pandas==2.0.0"]) {
update_package("pandas", "2.0.0", &mut packages);
save_packages_to_path(&packages, &file_path);
let updated_packages = load_packages_from_path(&file_path);
assert_eq!(updated_packages.packages.get("pandas").unwrap(), "2.0.0");
} else {
panic!("Failed to update pandas");
}
}

#[test]
Expand All @@ -84,3 +195,27 @@ fn test_list_packages() {
save_packages_to_path(&packages, &file_path);
list_packages(&packages); // This just prints to stdout, so we're testing for no panic
}

#[test]
fn test_install_from_requirements() {
let dir = tempdir().expect("Failed to create temp dir");
let env_path = dir.path().join("env");
create_virtualenv(&env_path);

let file_path = dir.path().join("requirements.json");
let mut initial_packages = PackageRegistry::new();
initial_packages.packages.insert("pandas".to_string(), "1.0.0".to_string());
initial_packages.packages.insert("numpy".to_string(), "1.19.5".to_string());
save_packages_to_path(&initial_packages, &file_path);

println!("Content of requirements.json: {}", fs::read_to_string(&file_path).unwrap());

let mut packages = PackageRegistry::new();
install_from_requirements(file_path.to_str().unwrap(), &mut packages);

save_packages_to_path(&packages, &file_path);
let updated_packages = load_packages_from_path(&file_path);

assert_eq!(updated_packages.packages.get("pandas").unwrap(), "1.0.0");
assert_eq!(updated_packages.packages.get("numpy").unwrap(), "1.19.5");
}

0 comments on commit 96137f9

Please sign in to comment.