feat(python): pipx support

see #50
closes #48
This commit is contained in:
steven-omaha
2024-01-28 08:12:40 +01:00
4 changed files with 68 additions and 8 deletions
+4
View File
@@ -176,6 +176,7 @@ disabled_backends: [] # backends that pacdef should not manage, e.g. ["python"]
warn_not_symlinks: true # warn if a group file is not a symlink warn_not_symlinks: true # warn if a group file is not a symlink
flatpak_systemwide: true # whether flatpak packages should be installed system-wide or per user flatpak_systemwide: true # whether flatpak packages should be installed system-wide or per user
pip_binary: pip # choose whether to use pipx instead of pip for python package management [See [Pitfalls while using pipx](#pitfalls-while-using-pipx)]
``` ```
@@ -219,3 +220,6 @@ Pacdef is supported by [topgrade](https://github.com/topgrade-rs/topgrade).
MSRV is 1.70.0 due to dependencies that require this specific version. Development is conducted against the latest stable version. MSRV is 1.70.0 due to dependencies that require this specific version. Development is conducted against the latest stable version.
### Pitfalls while using pipx
Some packages like [mdformat-myst](https://github.com/executablebooks/mdformat-myst) do not provide an executable themselves but rather act as a plugin to their dependency, which is mdformat in this case. Please install such packages explicitly by running `pipx install <package-name> --include-deps`.
@@ -12,6 +12,7 @@ use crate::{Group, Package};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Python { pub struct Python {
pub(crate) binary: String,
pub(crate) packages: HashSet<Package>, pub(crate) packages: HashSet<Package>,
} }
@@ -26,19 +27,30 @@ const SWITCHES_REMOVE: Switches = &["uninstall"];
const SUPPORTS_AS_DEPENDENCY: bool = false; const SUPPORTS_AS_DEPENDENCY: bool = false;
macro_rules! ERROR{
($bin:expr) => {
panic!("Cannot use {} for package management in python. Please use a valid package manager like pip or pipx.", $bin)
};
}
impl Backend for Python { impl Backend for Python {
impl_backend_constants!(); impl_backend_constants!();
fn get_all_installed_packages(&self) -> Result<HashSet<Package>> { fn get_binary(&self) -> Text {
let output = run_pip_command(&["list", "--format", "json", "--user"])?; let r#box = self.binary.clone().into_boxed_str();
Box::leak(r#box)
}
extract_pacdef_packages(output) fn get_all_installed_packages(&self) -> Result<HashSet<Package>> {
let mut cmd = Command::new(self.get_binary());
let output = run_pip_command(&mut cmd, self.get_switches_runtime())?;
self.extract_packages(output)
} }
fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> { fn get_explicitly_installed_packages(&self) -> Result<HashSet<Package>> {
let output = run_pip_command(&["list", "--format", "json", "--not-required", "--user"])?; let mut cmd = Command::new(self.get_binary());
let output = run_pip_command(&mut cmd, self.get_switches_explicit())?;
extract_pacdef_packages(output) self.extract_packages(output)
} }
fn make_dependency(&self, _packages: &[Package]) -> Result<ExitStatus> { fn make_dependency(&self, _packages: &[Package]) -> Result<ExitStatus> {
@@ -46,8 +58,7 @@ impl Backend for Python {
} }
} }
fn run_pip_command(args: &[&str]) -> Result<Value> { fn run_pip_command(cmd: &mut Command, args: &[&str]) -> Result<Value> {
let mut cmd = Command::new(BINARY);
cmd.args(args); cmd.args(args);
let output = String::from_utf8(cmd.output()?.stdout)?; let output = String::from_utf8(cmd.output()?.stdout)?;
let val: Value = serde_json::from_str(&output)?; let val: Value = serde_json::from_str(&output)?;
@@ -57,9 +68,33 @@ fn run_pip_command(args: &[&str]) -> Result<Value> {
impl Python { impl Python {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
binary: BINARY.to_string(),
packages: HashSet::new(), packages: HashSet::new(),
} }
} }
fn get_switches_runtime(&self) -> Switches {
match self.get_binary() {
"pip" => &["list", "--format", "json", "--not-required", "--user"],
"pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()),
}
}
fn get_switches_explicit(&self) -> Switches {
match self.get_binary() {
"pip" => &["list", "--format", "json", "--user"],
"pipx" => &["list", "--json"],
_ => ERROR!(self.get_binary()),
}
}
fn extract_packages(&self, output: Value) -> Result<HashSet<Package>> {
match self.get_binary() {
"pip" => extract_pacdef_packages(output),
"pipx" => extract_pacdef_packages_pipx(output),
_ => ERROR!(self.get_binary()),
}
}
} }
fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> { fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> {
@@ -72,3 +107,13 @@ fn extract_pacdef_packages(value: Value) -> Result<HashSet<Package>> {
.collect(); .collect();
Ok(result) Ok(result)
} }
fn extract_pacdef_packages_pipx(value: Value) -> Result<HashSet<Package>> {
let result = value["venvs"]
.as_object()
.context("getting inner json object")?
.iter()
.map(|(name, _)| Package::from(name.as_str()))
.collect();
Ok(result)
}
+4
View File
@@ -24,6 +24,9 @@ pub struct Config {
/// Backends the user does not want to use even though the binary exists. /// Backends the user does not want to use even though the binary exists.
#[serde(default)] #[serde(default)]
pub disabled_backends: Vec<String>, pub disabled_backends: Vec<String>,
/// Choose whether to use pipx instead of pip for python package management
#[serde(default)]
pub pip_binary: String,
} }
fn yes() -> bool { fn yes() -> bool {
@@ -86,6 +89,7 @@ impl Default for Config {
flatpak_systemwide: true, flatpak_systemwide: true,
warn_not_symlinks: true, warn_not_symlinks: true,
disabled_backends: vec![], disabled_backends: vec![],
pip_binary: "pip".into(),
} }
} }
} }
+7
View File
@@ -140,6 +140,13 @@ impl Pacdef {
{ {
flatpak.systemwide = self.config.flatpak_systemwide; flatpak.systemwide = self.config.flatpak_systemwide;
} }
if let Some(python) = backend
.as_any_mut()
.downcast_mut::<crate::backend::Python>()
{
python.binary = self.config.pip_binary.clone();
}
} }
fn install_packages(&mut self, noconfirm: bool) -> Result<()> { fn install_packages(&mut self, noconfirm: bool) -> Result<()> {