Skip to content

Commit

Permalink
Use HuakError in PythonPackage init (#255)
Browse files Browse the repository at this point in the history
* Use HuakError in PythonPackage init

adds InvalidPyPackageVersion

* Use InvalidPyPackageVersionOp

* Use some more errors and prefix PackageInstallFailure with Py
  • Loading branch information
cnpryer authored Sep 29, 2022
1 parent 9cbb0db commit f63340d
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 39 deletions.
13 changes: 12 additions & 1 deletion src/huak/env/venv.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
env::{self, consts::OS},
path::{Path, PathBuf},
process::ExitCode,
};

use crate::{
Expand Down Expand Up @@ -142,9 +143,19 @@ impl Venv {
self.create()?;

let module_path = self.module_path(module)?;
let package = match PythonPackage::from(module.to_string()) {
Ok(it) => it,
// TODO: Don't do this post-decouple.
Err(_) => {
return Err(CliError::new(
HuakError::PyPackageInitError(module.to_string()),
ExitCode::FAILURE,
))
}
};

if !module_path.exists() {
self.install_package(&PythonPackage::from(module.to_string()))?;
self.install_package(&package)?;
}

let module_path = crate::utils::path::to_string(module_path.as_path())?;
Expand Down
18 changes: 15 additions & 3 deletions src/huak/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ pub enum HuakError {
PythonNotFound,
VenvNotFound,
PyProjectTomlNotFound, // TODO: Manfiest
PackageInstallFailure(String),
PyPackageInstallFailure(String),
PyPackageInitError(String),
InvalidPyPackageVersionOp(String),
}

#[derive(Debug)]
Expand Down Expand Up @@ -95,8 +97,18 @@ impl fmt::Display for CliError {
HuakError::PyProjectTomlNotFound => {
"A pyproject.toml could not be found."
}
HuakError::PackageInstallFailure(package) => {
binding = format!("Failed to install package: {package}.");
HuakError::PyPackageInstallFailure(package) => {
binding =
format!("Failed to install Python package: {package}.");
binding.as_str()
}
HuakError::PyPackageInitError(package) => {
binding = format!("Failed to init Python package: {package}.");
binding.as_str()
}
HuakError::InvalidPyPackageVersionOp(op) => {
binding =
format!("Invalid Python package version operator: {op}.");
binding.as_str()
}
};
Expand Down
4 changes: 2 additions & 2 deletions src/huak/ops/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn add_project_dependency(
let version = json.info.version;
let name = json.info.name;
let package =
PythonPackage::new(name.as_str(), None, Some(version.as_str()));
PythonPackage::new(name.as_str(), None, Some(version.as_str()))?;

let venv = match project.venv() {
Some(v) => v,
Expand All @@ -52,7 +52,7 @@ pub fn add_project_dependency(
let dep = package.string();

if venv.install_package(&package).is_err() {
return Err(HuakError::PackageInstallFailure(dep.clone()));
return Err(HuakError::PyPackageInstallFailure(dep.clone()));
};

match is_dev {
Expand Down
71 changes: 40 additions & 31 deletions src/huak/package/python.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use core::fmt;
use std::str::FromStr;

use crate::errors::HuakError;

/// Version operators used in dependency strings.
const VERSION_OPERATORS: [&str; 8] =
["==", "~=", "!=", ">=", "<=", ">", "<", "==="];
Expand All @@ -10,17 +12,17 @@ const VERSION_OPERATORS: [&str; 8] =
/// # Examples
/// ```
/// use huak::package::python::PythonPackage;
/// let python_pkg = PythonPackage::new("request", Some(">="), Some("2.28.1"));
/// let python_pkg = PythonPackage::new("request", Some(">="), Some("2.28.1")).unwrap();
/// // or
/// let other_pkg = PythonPackage::from("problems==0.0.2".to_string());
/// let other_pkg = PythonPackage::from("problems==0.0.2".to_string()).unwrap();
/// println!("I've got 99 {} but huak ain't one", other_pkg);
/// ```
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct PythonPackage {
/// The name of the python package, pretty straight forward, why are you reading this?
pub name: String,
/// op represents PEP's Version Specifiers, such as "==" or "<="
pub op: Option<VersionOp>,
/// Th operator represents PEP's Version Specifiers, such as "==" or "<="
pub operator: Option<VersionOp>,
/// The semantic version associated with a python package
pub version: Option<String>,
}
Expand Down Expand Up @@ -51,23 +53,27 @@ pub enum VersionOp {
impl PythonPackage {
pub fn new(
name: &str,
op: Option<&str>,
operator: Option<&str>,
version: Option<&str>,
) -> PythonPackage {
if let Some(operator) = op {
let op_from_string = VersionOp::from_str(operator).unwrap();
PythonPackage {
name: name.to_string(),
op: Some(op_from_string),
version: version.map(|it| it.to_string()),
}
} else {
PythonPackage {
name: name.to_string(),
op: Some(VersionOp::default()),
version: version.map(|it| it.to_string()),
) -> Result<PythonPackage, HuakError> {
let op = match operator {
Some(it) => Some(VersionOp::from_str(it)?),
None => {
if version.is_none() {
None
} else {
Some(VersionOp::default())
}
}
}
};

let ver = version.map(|it| it.to_string());

Ok(PythonPackage {
name: name.to_string(),
operator: op,
version: ver,
})
}

/// Instantiate a PythonPackage struct from a String
Expand All @@ -80,11 +86,12 @@ impl PythonPackage {
/// use huak::package::python::PythonPackage;
/// let my_pkg = PythonPackage::from("requests==2.28.1".to_string());
/// ```
pub fn from(pkg_string: String) -> PythonPackage {
pub fn from(pkg_string: String) -> Result<PythonPackage, HuakError> {
// unfortunately, we have to redeclare the operators here or bring in a 3rd party crate (like strum)
// to derive an iterable from out VersionOp enum
let version_operators = VERSION_OPERATORS.into_iter();
let mut op: Option<&str> = None;
// TODO: Collect from filter on iter. Maybe contains.
for i in version_operators {
if pkg_string.contains(i) {
op = Some(i);
Expand All @@ -97,18 +104,18 @@ impl PythonPackage {
let pkg_vec = pkg_components.collect::<Vec<&str>>();
PythonPackage {
name: pkg_vec[0].to_string(),
op: Some(VersionOp::from_str(it).unwrap()),
operator: Some(VersionOp::from_str(it)?),
version: Some(pkg_vec[1].to_string()),
}
}
None => PythonPackage {
name: pkg_string,
op: None,
operator: None,
version: None,
},
};

package
Ok(package)
}

pub fn string(&self) -> &String {
Expand All @@ -121,15 +128,15 @@ impl PythonPackage {
/// # Examples
/// ```
/// use huak::package::python::PythonPackage;
/// let my_pkg = PythonPackage::from("requests==2.28.1".to_string());
/// let my_pkg = PythonPackage::from("requests==2.28.1".to_string()).unwrap();
/// println!("{}", my_pkg); // output: "request==2.28.1"
/// ```
impl fmt::Display for PythonPackage {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// check if a version is specified
if let Some(ver) = &self.version {
// check if a version specifier (operator) is supplied
if let Some(operator) = &self.op {
if let Some(operator) = &self.operator {
write!(f, "{}{}{}", self.name, operator, ver)
} else {
// if no version specifier, default to '=='
Expand Down Expand Up @@ -179,8 +186,8 @@ impl fmt::Display for VersionOp {
/// let ver_op_enum = VersionOp::from_str(ver_op_string).unwrap();
/// ```
impl FromStr for VersionOp {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
type Err = HuakError;
fn from_str(s: &str) -> Result<Self, self::HuakError> {
match s {
"~=" => Ok(VersionOp::Compatible),
"==" => Ok(VersionOp::Matching),
Expand All @@ -190,7 +197,7 @@ impl FromStr for VersionOp {
"<" => Ok(VersionOp::LesserExcluding),
">=" => Ok(VersionOp::GreaterIncluding),
">" => Ok(VersionOp::GreaterExcluding),
_ => Err(()),
_ => Err(self::HuakError::InvalidPyPackageVersionOp(s.to_string())),
}
}
}
Expand All @@ -211,7 +218,8 @@ mod tests {
fn display_python_package() {
let pkg_name = "test";
let pkg_version: Option<&str> = Some("0.0.1");
let python_pkg = PythonPackage::new(pkg_name, None, pkg_version);
let python_pkg =
PythonPackage::new(pkg_name, None, pkg_version).unwrap();
let py_pkg_fmt = format!("{}", python_pkg);
assert_eq!(py_pkg_fmt, "test==0.0.1");
}
Expand All @@ -231,9 +239,10 @@ mod tests {
let new_pkg_from_string = PythonPackage::from(format!(
"{}{}{}",
dependency, operator, version
));
))
.unwrap();
assert_eq!(new_pkg_from_string.name, dependency);
if let Some(op_from_new_pkg) = new_pkg_from_string.op {
if let Some(op_from_new_pkg) = new_pkg_from_string.operator {
assert_eq!(format!("{}", op_from_new_pkg), operator);
}
assert_eq!(new_pkg_from_string.version.unwrap(), version);
Expand Down
4 changes: 2 additions & 2 deletions src/huak/project/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl PythonConfig for Config {

// Collect into vector of owned `PythonPackage` data.
from.iter()
.map(|d| PythonPackage::from(d.clone()))
.filter_map(|d| PythonPackage::from(d.clone()).ok())
.collect()
}
// Get vec of `PythonPackage`s from the manifest.
Expand All @@ -126,7 +126,7 @@ impl PythonConfig for Config {

// Collect into vector of owned `PythonPackage` data.
from.iter()
.map(|d| PythonPackage::from(d.clone()))
.filter_map(|d| PythonPackage::from(d.clone()).ok())
.collect()
}
}

0 comments on commit f63340d

Please sign in to comment.