From 8115e1d681b601248a352b9fae314bae81a07b41 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 10:47:56 +0200 Subject: [PATCH 01/23] feat: Add validate_package_records function --- .../rattler_conda_types/src/repo_data/mod.rs | 127 +++++++++++++++++- .../rattler/repo_data/package_record.py | 25 ++++ py-rattler/src/record.rs | 15 ++- .../channels/dummy/linux-64/repodata.json | 15 +++ 4 files changed, 178 insertions(+), 4 deletions(-) diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index bc3f00a82..5f79614e8 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -19,7 +19,6 @@ use serde_with::{serde_as, skip_serializing_none, OneOrMany}; use thiserror::Error; use url::Url; -use crate::utils::serde::sort_map_alphabetically; use crate::utils::url::add_trailing_slash; use crate::{ build_spec::BuildNumber, @@ -27,6 +26,9 @@ use crate::{ utils::serde::DeserializeFromStrUnchecked, Channel, NoArchType, PackageName, PackageUrl, Platform, RepoDataRecord, VersionWithSource, }; +use crate::{ + utils::serde::sort_map_alphabetically, MatchSpec, Matches, ParseMatchSpecError, ParseStrictness, +}; /// [`RepoData`] is an index of package binaries available on in a subdirectory /// of a Conda channel. @@ -275,6 +277,12 @@ pub fn compute_package_url( .expect("failed to join base_url and filename") } +impl AsRef for PackageRecord { + fn as_ref(&self) -> &PackageRecord { + &self + } +} + impl PackageRecord { /// A simple helper method that constructs a `PackageRecord` with the bare /// minimum values. @@ -315,6 +323,77 @@ impl PackageRecord { pub fn sort_topologically + Clone>(records: Vec) -> Vec { topological_sort::sort_topologically(records) } + + /// Validate that the given package records are valid w.r.t. 'depends' and 'constrains'. + /// This function will return Ok(()) if all records form a valid environment, i.e., all dependencies + /// of each package are satisfied by the other packages in the list. + /// If there is a dependency that is not satisfied, this function will return an error. + pub fn validate_package_records>( + records: Vec, + ) -> Result<(), ValidatePackageRecordsError> { + for package in records.iter() { + let package = package.as_ref(); + // First we check if all dependencies are in the environment. + for dep in package.depends.iter() { + // We ignore virtual packages, e.g. `__unix`. + if dep.starts_with("__") { + continue; + } + let dep_spec = MatchSpec::from_str(dep, ParseStrictness::Lenient)?; + if !records.iter().any(|p| dep_spec.matches(p.as_ref())) { + return Err( + ValidatePackageRecordsError::DependencyNotInEnvironmentError { + package: package.name.as_normalized().to_string(), + dependency: dep.to_string(), + }, + ); + } + } + + // Then we check if all constraints are satisfied. + for constraint in package.constrains.iter() { + let constraint_spec = MatchSpec::from_str(constraint, ParseStrictness::Lenient)?; + let matching_package = records + .iter() + .find(|record| Some(record.as_ref().name.clone()) == constraint_spec.name); + if matching_package.is_some_and(|p| !constraint_spec.matches(p.as_ref())) { + return Err( + ValidatePackageRecordsError::PackageConstraintNotSatisfiedError { + package: package.name.as_normalized().to_string(), + constraint: constraint.to_owned(), + violating_package: matching_package.unwrap().as_ref().to_owned(), + }, + ); + } + } + } + Ok(()) + } +} + +/// An error that can occur when parsing a platform from a string. +#[derive(Debug, Error)] +pub enum ValidatePackageRecordsError { + /// A package is not present in the environment. + #[error("package '{package}' has dependency '{dependency}', which is not in the environment")] + DependencyNotInEnvironmentError { + /// The package containing the unmet dependency. + package: String, + /// The dependency that is not in the environment. + dependency: String, + }, + /// A package constraint is not met in the environment. + #[error("package '{package}' has constraint '{constraint}', which is not satisfied by '{violating_package}' in the environment")] + PackageConstraintNotSatisfiedError { + /// The package containing the unmet constraint. + package: String, + /// The constraint that is violated. + constraint: String, + /// The corresponding package that violates the constraint. + violating_package: PackageRecord, + }, + #[error(transparent)] + ParseMatchSpecError(#[from] ParseMatchSpecError), } /// An error that can occur when parsing a platform from a string. @@ -331,7 +410,7 @@ pub enum ConvertSubdirError { /// Platform key is empty #[error("platform key is empty in index.json")] PlatformEmpty, - /// Arc key is empty + /// Arch key is empty #[error("arch key is empty in index.json")] ArchEmpty, } @@ -437,7 +516,7 @@ mod test { use crate::{ repo_data::{compute_package_url, determine_subdir}, - Channel, ChannelConfig, RepoData, + Channel, ChannelConfig, PackageRecord, RepoData, }; // isl-0.12.2-1.tar.bz2 @@ -554,4 +633,46 @@ mod test { let data_path = test_data_path.join(path); RepoData::from_path(data_path).unwrap() } + + #[test] + fn test_validate_package_records() { + // load test data + let test_data_path = dunce::canonicalize( + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../test-data"), + ) + .unwrap(); + let data_path = test_data_path.join("channels/dummy/linux-64/repodata.json"); + let repodata = RepoData::from_path(&data_path).unwrap(); + + let package_depends_only_virtual_package = repodata + .packages + .get("baz-1.0-unix_py36h1af98f8_2.tar.bz2") + .unwrap(); + let package_depends = repodata.packages.get("foobar-2.0-bla_1.tar.bz2").unwrap(); + let package_constrains = repodata.packages.get("foo-3.0.2-py36h1af98f8_3.conda").unwrap(); + let package_bors_1 = repodata.packages.get("bors-1.2.1-bla_1.tar.bz2").unwrap(); + let package_bors_2 = repodata.packages.get("bors-2.1-bla_1.tar.bz2").unwrap(); + + assert!(PackageRecord::validate_package_records(vec![ + package_depends_only_virtual_package + ]) + .is_ok()); + for packages in vec![vec![package_depends], vec![package_depends, package_bors_2]] { + let result = PackageRecord::validate_package_records(packages); + assert!(result.is_err()); + assert!(result.err().unwrap().to_string().contains( + "package 'foobar' has dependency 'bors <2.0', which is not in the environment" + )); + } + + assert!(PackageRecord::validate_package_records(vec![package_depends, package_bors_1]).is_ok()); + assert!(PackageRecord::validate_package_records(vec![package_constrains]).is_ok()); + assert!(PackageRecord::validate_package_records(vec![package_constrains, package_bors_1]).is_ok()); + + let result = PackageRecord::validate_package_records(vec![package_constrains, package_bors_2]); + assert!(result.is_err()); + assert!(result.err().unwrap().to_string().contains( + "package 'foo' has constraint 'bors <2.0', which is not satisfied by 'bors=2.1=bla_1' in the environment" + )); + } } diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index edf4846f7..40e713154 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -118,6 +118,31 @@ def to_graph(records: List[PackageRecord]) -> nx.DiGraph: # type: ignore[type-a graph.add_edge(record, names_to_records[PackageName(name)]) return graph + + @staticmethod + def validate_package_records(records: List[PackageRecord]): + """ + Validate that the given package records are valid w.r.t. 'depends' and 'constrains'. + + This function will return nothing if all records form a valid environment, i.e., all dependencies + of each package are satisfied by the other packages in the list. + If there is a dependency that is not satisfied, this function will raise an exception. + + Examples + -------- + ```python + >>> from os import listdir + >>> from os.path import isfile, join + >>> from rattler import PrefixRecord + >>> records = [ + ... PrefixRecord.from_path(join("../test-data/conda-meta/", f)) + ... for f in listdir("../test-data/conda-meta") + ... if isfile(join("../test-data/conda-meta", f)) + ... ] + >>> validate_package_records(records) + ``` + """ + return [PackageRecord._from_py_record(p) for p in PyRecord.validate_package_records(records)] @classmethod def _from_py_record(cls, py_record: PyRecord) -> PackageRecord: diff --git a/py-rattler/src/record.rs b/py-rattler/src/record.rs index ab3b260fc..bba8bd8cc 100644 --- a/py-rattler/src/record.rs +++ b/py-rattler/src/record.rs @@ -424,6 +424,19 @@ impl PyRecord { .map_err(PyRattlerError::from)?) } + /// Validate that the given package records are valid w.r.t. 'depends' and 'constrains'. + /// This function will return nothing if all records form a valid environment, i.e., all dependencies + /// of each package are satisfied by the other packages in the list. + /// If there is a dependency that is not satisfied, this function will raise an exception. + #[staticmethod] + fn validate_package_records>(records: Vec<&PyAny>) -> PyResult<()> { + let records = records + .into_iter() + .map(PyRecord::try_from) + .collect::>>()?; + Ok(PackageRecord::validate_package_records(records)) + } + /// Sorts the records topologically. /// /// This function is deterministic, meaning that it will return the same result @@ -436,6 +449,6 @@ impl PyRecord { .into_iter() .map(PyRecord::try_from) .collect::>>()?; - Ok(PackageRecord::sort_topologically(records)) + PackageRecord::sort_topologically(records) } } diff --git a/test-data/channels/dummy/linux-64/repodata.json b/test-data/channels/dummy/linux-64/repodata.json index 378ecec96..484072dd9 100644 --- a/test-data/channels/dummy/linux-64/repodata.json +++ b/test-data/channels/dummy/linux-64/repodata.json @@ -62,6 +62,21 @@ "timestamp": 1715610974000, "version": "3.0.2" }, + "foo-3.0.2-py36h1af98f8_3.conda": { + "build": "py36h1af98f8_3", + "build_number": 2, + "depends": [], + "constrains": ["bors <2.0"], + "license": "MIT", + "license_family": "MIT", + "md5": "fb731d9290f0bcbf3a054665f33ec94f", + "name": "foo", + "sha256": "67a63bec3fd3205170eaad532d487595b8aaceb9814d13c6858d7bac3ef24cd4", + "size": 414494, + "subdir": "linux-64", + "timestamp": 1715610974000, + "version": "3.0.2" + }, "foo-4.0.2-py36h1af98f8_2.tar.bz2": { "build": "py36h1af98f8_2", "build_number": 1, From 69d8b01c677cfda15255e35152776b4f6c772626 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 10:54:01 +0200 Subject: [PATCH 02/23] format --- .../rattler_conda_types/src/repo_data/mod.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index 5f79614e8..d421d1513 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -649,7 +649,10 @@ mod test { .get("baz-1.0-unix_py36h1af98f8_2.tar.bz2") .unwrap(); let package_depends = repodata.packages.get("foobar-2.0-bla_1.tar.bz2").unwrap(); - let package_constrains = repodata.packages.get("foo-3.0.2-py36h1af98f8_3.conda").unwrap(); + let package_constrains = repodata + .packages + .get("foo-3.0.2-py36h1af98f8_3.conda") + .unwrap(); let package_bors_1 = repodata.packages.get("bors-1.2.1-bla_1.tar.bz2").unwrap(); let package_bors_2 = repodata.packages.get("bors-2.1-bla_1.tar.bz2").unwrap(); @@ -664,12 +667,18 @@ mod test { "package 'foobar' has dependency 'bors <2.0', which is not in the environment" )); } - - assert!(PackageRecord::validate_package_records(vec![package_depends, package_bors_1]).is_ok()); + + assert!( + PackageRecord::validate_package_records(vec![package_depends, package_bors_1]).is_ok() + ); assert!(PackageRecord::validate_package_records(vec![package_constrains]).is_ok()); - assert!(PackageRecord::validate_package_records(vec![package_constrains, package_bors_1]).is_ok()); + assert!( + PackageRecord::validate_package_records(vec![package_constrains, package_bors_1]) + .is_ok() + ); - let result = PackageRecord::validate_package_records(vec![package_constrains, package_bors_2]); + let result = + PackageRecord::validate_package_records(vec![package_constrains, package_bors_2]); assert!(result.is_err()); assert!(result.err().unwrap().to_string().contains( "package 'foo' has constraint 'bors <2.0', which is not satisfied by 'bors=2.1=bla_1' in the environment" From 4c9e89e10e1ab3717f1a8878005b67ca37158273 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 10:57:25 +0200 Subject: [PATCH 03/23] fix pt 1 --- crates/rattler_conda_types/src/repo_data/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index d421d1513..e9fe90acb 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -279,7 +279,7 @@ pub fn compute_package_url( impl AsRef for PackageRecord { fn as_ref(&self) -> &PackageRecord { - &self + self } } @@ -392,6 +392,7 @@ pub enum ValidatePackageRecordsError { /// The corresponding package that violates the constraint. violating_package: PackageRecord, }, + /// Failed to parse a matchspec. #[error(transparent)] ParseMatchSpecError(#[from] ParseMatchSpecError), } From 3d1ab64b36def2b50a71cd2b80ad64a72ed6a92b Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 11:02:38 +0200 Subject: [PATCH 04/23] record.rs --- py-rattler/src/record.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py-rattler/src/record.rs b/py-rattler/src/record.rs index bba8bd8cc..1022ed3c9 100644 --- a/py-rattler/src/record.rs +++ b/py-rattler/src/record.rs @@ -434,7 +434,7 @@ impl PyRecord { .into_iter() .map(PyRecord::try_from) .collect::>>()?; - Ok(PackageRecord::validate_package_records(records)) + PackageRecord::validate_package_records(records) } /// Sorts the records topologically. @@ -449,6 +449,6 @@ impl PyRecord { .into_iter() .map(PyRecord::try_from) .collect::>>()?; - PackageRecord::sort_topologically(records) + Ok(PackageRecord::sort_topologically(records)) } } From 66a6a8ea1471ace490000675b2eacef95cf71d40 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 11:09:56 +0200 Subject: [PATCH 05/23] record.rs --- py-rattler/src/record.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py-rattler/src/record.rs b/py-rattler/src/record.rs index 1022ed3c9..d8226bf12 100644 --- a/py-rattler/src/record.rs +++ b/py-rattler/src/record.rs @@ -429,7 +429,7 @@ impl PyRecord { /// of each package are satisfied by the other packages in the list. /// If there is a dependency that is not satisfied, this function will raise an exception. #[staticmethod] - fn validate_package_records>(records: Vec<&PyAny>) -> PyResult<()> { + fn validate_package_records(records: Vec<&PyAny>) -> PyResult<()> { let records = records .into_iter() .map(PyRecord::try_from) From 9a48998428a3c44dd07da3e0a8b1b6af35d83f6e Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 11:13:57 +0200 Subject: [PATCH 06/23] Clippy --- crates/rattler_conda_types/src/repo_data/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index e9fe90acb..206258caf 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -342,7 +342,7 @@ impl PackageRecord { let dep_spec = MatchSpec::from_str(dep, ParseStrictness::Lenient)?; if !records.iter().any(|p| dep_spec.matches(p.as_ref())) { return Err( - ValidatePackageRecordsError::DependencyNotInEnvironmentError { + ValidatePackageRecordsError::DependencyNotInEnvironment { package: package.name.as_normalized().to_string(), dependency: dep.to_string(), }, @@ -358,7 +358,7 @@ impl PackageRecord { .find(|record| Some(record.as_ref().name.clone()) == constraint_spec.name); if matching_package.is_some_and(|p| !constraint_spec.matches(p.as_ref())) { return Err( - ValidatePackageRecordsError::PackageConstraintNotSatisfiedError { + ValidatePackageRecordsError::PackageConstraintNotSatisfied { package: package.name.as_normalized().to_string(), constraint: constraint.to_owned(), violating_package: matching_package.unwrap().as_ref().to_owned(), @@ -376,7 +376,7 @@ impl PackageRecord { pub enum ValidatePackageRecordsError { /// A package is not present in the environment. #[error("package '{package}' has dependency '{dependency}', which is not in the environment")] - DependencyNotInEnvironmentError { + DependencyNotInEnvironment { /// The package containing the unmet dependency. package: String, /// The dependency that is not in the environment. @@ -384,7 +384,7 @@ pub enum ValidatePackageRecordsError { }, /// A package constraint is not met in the environment. #[error("package '{package}' has constraint '{constraint}', which is not satisfied by '{violating_package}' in the environment")] - PackageConstraintNotSatisfiedError { + PackageConstraintNotSatisfied { /// The package containing the unmet constraint. package: String, /// The constraint that is violated. @@ -394,7 +394,7 @@ pub enum ValidatePackageRecordsError { }, /// Failed to parse a matchspec. #[error(transparent)] - ParseMatchSpecError(#[from] ParseMatchSpecError), + ParseMatchSpec(#[from] ParseMatchSpecError), } /// An error that can occur when parsing a platform from a string. From 9e9dd970e1f720dc901b25e715b059b39b41dfba Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:24:14 +0200 Subject: [PATCH 07/23] fixes --- crates/rattler_conda_types/src/lib.rs | 2 +- .../rattler_conda_types/src/repo_data/mod.rs | 24 ++++++++----------- .../rattler/repo_data/package_record.py | 4 ++-- py-rattler/src/error.rs | 10 ++++++-- py-rattler/src/record.rs | 2 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/crates/rattler_conda_types/src/lib.rs b/crates/rattler_conda_types/src/lib.rs index 1d7f09e48..620a2e8a2 100644 --- a/crates/rattler_conda_types/src/lib.rs +++ b/crates/rattler_conda_types/src/lib.rs @@ -50,7 +50,7 @@ pub use repo_data::{ compute_package_url, patches::{PackageRecordPatch, PatchInstructions, RepoDataPatch}, sharded::{Shard, ShardedRepodata, ShardedSubdirInfo}, - ChannelInfo, ConvertSubdirError, PackageRecord, RepoData, + ChannelInfo, ConvertSubdirError, PackageRecord, RepoData, ValidatePackageRecordsError }; pub use repo_data_record::RepoDataRecord; pub use run_export::RunExportKind; diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index 206258caf..32565dfb5 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -328,7 +328,7 @@ impl PackageRecord { /// This function will return Ok(()) if all records form a valid environment, i.e., all dependencies /// of each package are satisfied by the other packages in the list. /// If there is a dependency that is not satisfied, this function will return an error. - pub fn validate_package_records>( + pub fn validate>( records: Vec, ) -> Result<(), ValidatePackageRecordsError> { for package in records.iter() { @@ -341,12 +341,10 @@ impl PackageRecord { } let dep_spec = MatchSpec::from_str(dep, ParseStrictness::Lenient)?; if !records.iter().any(|p| dep_spec.matches(p.as_ref())) { - return Err( - ValidatePackageRecordsError::DependencyNotInEnvironment { - package: package.name.as_normalized().to_string(), - dependency: dep.to_string(), - }, - ); + return Err(ValidatePackageRecordsError::DependencyNotInEnvironment { + package: package.name.as_normalized().to_string(), + dependency: dep.to_string(), + }); } } @@ -357,13 +355,11 @@ impl PackageRecord { .iter() .find(|record| Some(record.as_ref().name.clone()) == constraint_spec.name); if matching_package.is_some_and(|p| !constraint_spec.matches(p.as_ref())) { - return Err( - ValidatePackageRecordsError::PackageConstraintNotSatisfied { - package: package.name.as_normalized().to_string(), - constraint: constraint.to_owned(), - violating_package: matching_package.unwrap().as_ref().to_owned(), - }, - ); + return Err(ValidatePackageRecordsError::PackageConstraintNotSatisfied { + package: package.name.as_normalized().to_string(), + constraint: constraint.to_owned(), + violating_package: matching_package.unwrap().as_ref().to_owned(), + }); } } } diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index 40e713154..b360d08e7 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -120,7 +120,7 @@ def to_graph(records: List[PackageRecord]) -> nx.DiGraph: # type: ignore[type-a return graph @staticmethod - def validate_package_records(records: List[PackageRecord]): + def validate_package_records(records: List[PackageRecord]) -> None: """ Validate that the given package records are valid w.r.t. 'depends' and 'constrains'. @@ -142,7 +142,7 @@ def validate_package_records(records: List[PackageRecord]): >>> validate_package_records(records) ``` """ - return [PackageRecord._from_py_record(p) for p in PyRecord.validate_package_records(records)] + return PyRecord.validate_package_records(records) @classmethod def _from_py_record(cls, py_record: PyRecord) -> PackageRecord: diff --git a/py-rattler/src/error.rs b/py-rattler/src/error.rs index 744998c57..73a4a09bc 100644 --- a/py-rattler/src/error.rs +++ b/py-rattler/src/error.rs @@ -4,8 +4,8 @@ use pyo3::{create_exception, exceptions::PyException, PyErr}; use rattler::install::TransactionError; use rattler_conda_types::{ ConvertSubdirError, InvalidPackageNameError, ParseArchError, ParseChannelError, - ParseMatchSpecError, ParsePlatformError, ParseVersionError, VersionBumpError, - VersionExtendError, + ParseMatchSpecError, ParsePlatformError, ParseVersionError, ValidatePackageRecordsError, + VersionBumpError, VersionExtendError, }; use rattler_lock::{ConversionError, ParseCondaLockError}; use rattler_package_streaming::ExtractError; @@ -70,6 +70,8 @@ pub enum PyRattlerError { GatewayError(#[from] GatewayError), #[error(transparent)] InstallerError(#[from] rattler::install::InstallerError), + #[error(transparent)] + ValidatePackageRecordsError(#[from] ValidatePackageRecordsError), } fn pretty_print_error(mut err: &dyn Error) -> String { @@ -154,6 +156,9 @@ impl From for PyErr { PyRattlerError::InstallerError(err) => { InstallerException::new_err(pretty_print_error(&err)) } + PyRattlerError::ValidatePackageRecordsError(err) => { + ValidatePackageRecordsException::new_err(pretty_print_error(&err)) + } } } } @@ -184,3 +189,4 @@ create_exception!(exceptions, ExtractException, PyException); create_exception!(exceptions, ActivationScriptFormatException, PyException); create_exception!(exceptions, GatewayException, PyException); create_exception!(exceptions, InstallerException, PyException); +create_exception!(exceptions, ValidatePackageRecordsException, PyException); diff --git a/py-rattler/src/record.rs b/py-rattler/src/record.rs index d8226bf12..f288ab879 100644 --- a/py-rattler/src/record.rs +++ b/py-rattler/src/record.rs @@ -434,7 +434,7 @@ impl PyRecord { .into_iter() .map(PyRecord::try_from) .collect::>>()?; - PackageRecord::validate_package_records(records) + Ok(PackageRecord::validate(records).map_err(PyRattlerError::from)?) } /// Sorts the records topologically. From efb7233fb0f161f0c2dd1bc0f6c242ec581529a9 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:25:12 +0200 Subject: [PATCH 08/23] fmt --- crates/rattler_conda_types/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rattler_conda_types/src/lib.rs b/crates/rattler_conda_types/src/lib.rs index 620a2e8a2..d79622b17 100644 --- a/crates/rattler_conda_types/src/lib.rs +++ b/crates/rattler_conda_types/src/lib.rs @@ -50,7 +50,7 @@ pub use repo_data::{ compute_package_url, patches::{PackageRecordPatch, PatchInstructions, RepoDataPatch}, sharded::{Shard, ShardedRepodata, ShardedSubdirInfo}, - ChannelInfo, ConvertSubdirError, PackageRecord, RepoData, ValidatePackageRecordsError + ChannelInfo, ConvertSubdirError, PackageRecord, RepoData, ValidatePackageRecordsError, }; pub use repo_data_record::RepoDataRecord; pub use run_export::RunExportKind; From 47b23b655f82ded68c88a7a1a088db1513ed79d1 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:27:59 +0200 Subject: [PATCH 09/23] fix --- .../rattler_conda_types/src/repo_data/mod.rs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index 32565dfb5..c6f3acc19 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -632,7 +632,7 @@ mod test { } #[test] - fn test_validate_package_records() { + fn test_validate() { // load test data let test_data_path = dunce::canonicalize( std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../test-data"), @@ -653,29 +653,20 @@ mod test { let package_bors_1 = repodata.packages.get("bors-1.2.1-bla_1.tar.bz2").unwrap(); let package_bors_2 = repodata.packages.get("bors-2.1-bla_1.tar.bz2").unwrap(); - assert!(PackageRecord::validate_package_records(vec![ - package_depends_only_virtual_package - ]) - .is_ok()); + assert!(PackageRecord::validate(vec![package_depends_only_virtual_package]).is_ok()); for packages in vec![vec![package_depends], vec![package_depends, package_bors_2]] { - let result = PackageRecord::validate_package_records(packages); + let result = PackageRecord::validate(packages); assert!(result.is_err()); assert!(result.err().unwrap().to_string().contains( "package 'foobar' has dependency 'bors <2.0', which is not in the environment" )); } - assert!( - PackageRecord::validate_package_records(vec![package_depends, package_bors_1]).is_ok() - ); - assert!(PackageRecord::validate_package_records(vec![package_constrains]).is_ok()); - assert!( - PackageRecord::validate_package_records(vec![package_constrains, package_bors_1]) - .is_ok() - ); + assert!(PackageRecord::validate(vec![package_depends, package_bors_1]).is_ok()); + assert!(PackageRecord::validate(vec![package_constrains]).is_ok()); + assert!(PackageRecord::validate(vec![package_constrains, package_bors_1]).is_ok()); - let result = - PackageRecord::validate_package_records(vec![package_constrains, package_bors_2]); + let result = PackageRecord::validate(vec![package_constrains, package_bors_2]); assert!(result.is_err()); assert!(result.err().unwrap().to_string().contains( "package 'foo' has constraint 'bors <2.0', which is not satisfied by 'bors=2.1=bla_1' in the environment" From f487f6a884e1bd913358a8ed846b74190f09a61c Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:30:23 +0200 Subject: [PATCH 10/23] fix --- crates/rattler_conda_types/src/repo_data/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index c6f3acc19..a2f64f8bb 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -654,7 +654,7 @@ mod test { let package_bors_2 = repodata.packages.get("bors-2.1-bla_1.tar.bz2").unwrap(); assert!(PackageRecord::validate(vec![package_depends_only_virtual_package]).is_ok()); - for packages in vec![vec![package_depends], vec![package_depends, package_bors_2]] { + for packages in [vec![package_depends], vec![package_depends, package_bors_2]] { let result = PackageRecord::validate(packages); assert!(result.is_err()); assert!(result.err().unwrap().to_string().contains( From 992f9bb1b71074c782595a9ca7eabed62ab507ae Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:34:49 +0200 Subject: [PATCH 11/23] . --- py-rattler/rattler/repo_data/package_record.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index b360d08e7..73168ba8e 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -118,7 +118,7 @@ def to_graph(records: List[PackageRecord]) -> nx.DiGraph: # type: ignore[type-a graph.add_edge(record, names_to_records[PackageName(name)]) return graph - + @staticmethod def validate_package_records(records: List[PackageRecord]) -> None: """ From 460495aba207cfc305fcf5643ff9da1afea5e067 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:52:38 +0200 Subject: [PATCH 12/23] fix --- ...ypes__repo_data__test__serialize_packages.snap | 15 +++++++++++++++ py-rattler/rattler/repo_data/package_record.py | 2 +- test-data/channels/dummy/linux-64/repodata.json | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__serialize_packages.snap b/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__serialize_packages.snap index 725724284..570ea57e1 100644 --- a/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__serialize_packages.snap +++ b/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__serialize_packages.snap @@ -167,6 +167,21 @@ packages: subdir: linux-64 timestamp: 1715610974 version: 3.0.2 + foo-3.0.2-py36h1af98f8_3.conda: + build: py36h1af98f8_3 + build_number: 3 + constrains: + - bors <2.0 + depends: [] + license: MIT + license_family: MIT + md5: fb731d9290f0bcbf3a054665f33ec94f + name: foo + sha256: 67a63bec3fd3205170eaad532d487595b8aaceb9814d13c6858d7bac3ef24cd4 + size: 414494 + subdir: linux-64 + timestamp: 1715610974 + version: 3.0.2 foo-4.0.2-py36h1af98f8_2.tar.bz2: build: py36h1af98f8_2 build_number: 1 diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index 73168ba8e..2cdf0dacf 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -133,7 +133,7 @@ def validate_package_records(records: List[PackageRecord]) -> None: ```python >>> from os import listdir >>> from os.path import isfile, join - >>> from rattler import PrefixRecord + >>> from rattler import PrefixRecord, validate_package_records >>> records = [ ... PrefixRecord.from_path(join("../test-data/conda-meta/", f)) ... for f in listdir("../test-data/conda-meta") diff --git a/test-data/channels/dummy/linux-64/repodata.json b/test-data/channels/dummy/linux-64/repodata.json index 484072dd9..edb62f8b3 100644 --- a/test-data/channels/dummy/linux-64/repodata.json +++ b/test-data/channels/dummy/linux-64/repodata.json @@ -64,7 +64,7 @@ }, "foo-3.0.2-py36h1af98f8_3.conda": { "build": "py36h1af98f8_3", - "build_number": 2, + "build_number": 3, "depends": [], "constrains": ["bors <2.0"], "license": "MIT", From 4018363109381b974419d8b10dcdc31bb6a01df6 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:53:22 +0200 Subject: [PATCH 13/23] . --- py-rattler/rattler/repo_data/package_record.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index 2cdf0dacf..fd2392673 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -133,13 +133,13 @@ def validate_package_records(records: List[PackageRecord]) -> None: ```python >>> from os import listdir >>> from os.path import isfile, join - >>> from rattler import PrefixRecord, validate_package_records + >>> from rattler import PrefixRecord >>> records = [ ... PrefixRecord.from_path(join("../test-data/conda-meta/", f)) ... for f in listdir("../test-data/conda-meta") ... if isfile(join("../test-data/conda-meta", f)) ... ] - >>> validate_package_records(records) + >>> PrefixRecord.validate_package_records(records) ``` """ return PyRecord.validate_package_records(records) From 0c9e5a80f2db7611ee0decc0dc74fb798147bc04 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:57:01 +0200 Subject: [PATCH 14/23] test files --- ...pes__repo_data__test__base_url_packages.snap | 5 +++-- ...__repo_data__test__serialize_packages-2.snap | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__base_url_packages.snap b/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__base_url_packages.snap index 4069166f7..d129494da 100644 --- a/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__base_url_packages.snap +++ b/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__base_url_packages.snap @@ -1,6 +1,6 @@ --- source: crates/rattler_conda_types/src/repo_data/mod.rs -assertion_line: 538 +assertion_line: 594 expression: file_urls --- - channels/dummy/linux-64/issue_717-2.1-bla_1.tar.bz2 @@ -9,6 +9,7 @@ expression: file_urls - channels/dummy/linux-64/foo-3.0.2-py36h1af98f8_1.conda - channels/dummy/linux-64/foo-3.0.2-py36h1af98f8_1.tar.bz2 - channels/dummy/linux-64/foo-4.0.2-py36h1af98f8_2.tar.bz2 +- channels/dummy/linux-64/foo-3.0.2-py36h1af98f8_3.conda - channels/dummy/linux-64/bors-1.2.1-bla_1.tar.bz2 - channels/dummy/linux-64/baz-1.0-unix_py36h1af98f8_2.tar.bz2 - channels/dummy/linux-64/bors-2.1-bla_1.tar.bz2 @@ -17,5 +18,5 @@ expression: file_urls - channels/dummy/linux-64/bors-1.1-bla_1.tar.bz2 - channels/dummy/linux-64/baz-2.0-unix_py36h1af98f8_2.tar.bz2 - channels/dummy/linux-64/foobar-2.1-bla_1.tar.bz2 -- channels/dummy/linux-64/cuda-version-12.5-hd4f0392_3.conda - channels/dummy/linux-64/bors-2.0-bla_1.tar.bz2 +- channels/dummy/linux-64/cuda-version-12.5-hd4f0392_3.conda diff --git a/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__serialize_packages-2.snap b/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__serialize_packages-2.snap index 1b79f058e..c832f9539 100644 --- a/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__serialize_packages-2.snap +++ b/crates/rattler_conda_types/src/repo_data/snapshots/rattler_conda_types__repo_data__test__serialize_packages-2.snap @@ -184,6 +184,23 @@ expression: json "timestamp": 1715610974, "version": "3.0.2" }, + "foo-3.0.2-py36h1af98f8_3.conda": { + "build": "py36h1af98f8_3", + "build_number": 3, + "constrains": [ + "bors <2.0" + ], + "depends": [], + "license": "MIT", + "license_family": "MIT", + "md5": "fb731d9290f0bcbf3a054665f33ec94f", + "name": "foo", + "sha256": "67a63bec3fd3205170eaad532d487595b8aaceb9814d13c6858d7bac3ef24cd4", + "size": 414494, + "subdir": "linux-64", + "timestamp": 1715610974, + "version": "3.0.2" + }, "foo-4.0.2-py36h1af98f8_2.tar.bz2": { "build": "py36h1af98f8_2", "build_number": 1, From 3dc29ed521d5ff91e8258171695b5b529767fb97 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 12:59:14 +0200 Subject: [PATCH 15/23] rename --- py-rattler/rattler/repo_data/package_record.py | 10 +++++----- py-rattler/src/record.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index fd2392673..fa3524823 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -120,7 +120,7 @@ def to_graph(records: List[PackageRecord]) -> nx.DiGraph: # type: ignore[type-a return graph @staticmethod - def validate_package_records(records: List[PackageRecord]) -> None: + def validate(records: List[PackageRecord]) -> None: """ Validate that the given package records are valid w.r.t. 'depends' and 'constrains'. @@ -133,16 +133,16 @@ def validate_package_records(records: List[PackageRecord]) -> None: ```python >>> from os import listdir >>> from os.path import isfile, join - >>> from rattler import PrefixRecord + >>> from rattler import PackageRecord >>> records = [ - ... PrefixRecord.from_path(join("../test-data/conda-meta/", f)) + ... PackageRecord.from_path(join("../test-data/conda-meta/", f)) ... for f in listdir("../test-data/conda-meta") ... if isfile(join("../test-data/conda-meta", f)) ... ] - >>> PrefixRecord.validate_package_records(records) + >>> PackageRecord.validate(records) ``` """ - return PyRecord.validate_package_records(records) + return PyRecord.validate(records) @classmethod def _from_py_record(cls, py_record: PyRecord) -> PackageRecord: diff --git a/py-rattler/src/record.rs b/py-rattler/src/record.rs index f288ab879..336d2fdb0 100644 --- a/py-rattler/src/record.rs +++ b/py-rattler/src/record.rs @@ -429,7 +429,7 @@ impl PyRecord { /// of each package are satisfied by the other packages in the list. /// If there is a dependency that is not satisfied, this function will raise an exception. #[staticmethod] - fn validate_package_records(records: Vec<&PyAny>) -> PyResult<()> { + fn validate(records: Vec<&PyAny>) -> PyResult<()> { let records = records .into_iter() .map(PyRecord::try_from) From c98d930b8751e3f61dc9fc40fa58384b0fc0cf7a Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 13:09:37 +0200 Subject: [PATCH 16/23] fix --- crates/rattler_solve/tests/backends.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/rattler_solve/tests/backends.rs b/crates/rattler_solve/tests/backends.rs index 3a903824f..fc588273f 100644 --- a/crates/rattler_solve/tests/backends.rs +++ b/crates/rattler_solve/tests/backends.rs @@ -346,17 +346,17 @@ macro_rules! solver_backend_tests { assert_eq!(1, pkgs.len()); let info = &pkgs[0]; - assert_eq!("foo-3.0.2-py36h1af98f8_2.conda", info.file_name); + assert_eq!("foo-3.0.2-py36h1af98f8_3.conda", info.file_name); assert_eq!( - "https://conda.anaconda.org/conda-forge/linux-64/foo-3.0.2-py36h1af98f8_2.conda", + "https://conda.anaconda.org/conda-forge/linux-64/foo-3.0.2-py36h1af98f8_3.conda", info.url.to_string() ); assert_eq!("https://conda.anaconda.org/conda-forge/", info.channel); assert_eq!("foo", info.package_record.name.as_normalized()); assert_eq!("linux-64", info.package_record.subdir); assert_eq!("3.0.2", info.package_record.version.to_string()); - assert_eq!("py36h1af98f8_2", info.package_record.build); - assert_eq!(2, info.package_record.build_number); + assert_eq!("py36h1af98f8_3", info.package_record.build); + assert_eq!(3, info.package_record.build_number); assert_eq!( rattler_digest::parse_digest_from_hex::( "67a63bec3fd3205170eaad532d487595b8aaceb9814d13c6858d7bac3ef24cd4" @@ -665,17 +665,17 @@ mod libsolv_c { assert_eq!(1, pkgs.len()); let info = &pkgs[0]; - assert_eq!("foo-3.0.2-py36h1af98f8_2.conda", info.file_name); + assert_eq!("foo-3.0.2-py36h1af98f8_3.conda", info.file_name); assert_eq!( - "https://conda.anaconda.org/conda-forge/linux-64/foo-3.0.2-py36h1af98f8_2.conda", + "https://conda.anaconda.org/conda-forge/linux-64/foo-3.0.2-py36h1af98f8_3.conda", info.url.to_string() ); assert_eq!("https://conda.anaconda.org/conda-forge/", info.channel); assert_eq!("foo", info.package_record.name.as_normalized()); assert_eq!("linux-64", info.package_record.subdir); assert_eq!("3.0.2", info.package_record.version.to_string()); - assert_eq!("py36h1af98f8_2", info.package_record.build); - assert_eq!(2, info.package_record.build_number); + assert_eq!("py36h1af98f8_3", info.package_record.build); + assert_eq!(3, info.package_record.build_number); assert_eq!( rattler_digest::parse_digest_from_hex::( "67a63bec3fd3205170eaad532d487595b8aaceb9814d13c6858d7bac3ef24cd4" @@ -781,7 +781,7 @@ mod resolvo { Version::from_str("3.0.2").unwrap() ); assert_eq!( - result[0].package_record.build_number, 2, + result[0].package_record.build_number, 3, "expected the highest build number" ); } From 457ae57aef7b7a4742aaa7495350f8a4310be4ee Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 13:15:48 +0200 Subject: [PATCH 17/23] . --- py-rattler/rattler/repo_data/package_record.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index fa3524823..6b572edda 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -133,9 +133,9 @@ def validate(records: List[PackageRecord]) -> None: ```python >>> from os import listdir >>> from os.path import isfile, join - >>> from rattler import PackageRecord + >>> from rattler import PrefixRecord >>> records = [ - ... PackageRecord.from_path(join("../test-data/conda-meta/", f)) + ... PrefixRecord.from_path(join("../test-data/conda-meta/", f)) ... for f in listdir("../test-data/conda-meta") ... if isfile(join("../test-data/conda-meta", f)) ... ] From 9cf85a4c8d321a4f709c2afd3dbdb208332ba3e0 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 13:38:16 +0200 Subject: [PATCH 18/23] fix --- crates/rattler_conda_types/src/repo_data/mod.rs | 10 +++++----- py-rattler/rattler/exceptions.py | 5 +++++ py-rattler/rattler/repo_data/package_record.py | 8 +++++++- py-rattler/src/lib.rs | 5 ++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index a2f64f8bb..bdd0a11eb 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -342,7 +342,7 @@ impl PackageRecord { let dep_spec = MatchSpec::from_str(dep, ParseStrictness::Lenient)?; if !records.iter().any(|p| dep_spec.matches(p.as_ref())) { return Err(ValidatePackageRecordsError::DependencyNotInEnvironment { - package: package.name.as_normalized().to_string(), + package: package.to_owned(), dependency: dep.to_string(), }); } @@ -356,7 +356,7 @@ impl PackageRecord { .find(|record| Some(record.as_ref().name.clone()) == constraint_spec.name); if matching_package.is_some_and(|p| !constraint_spec.matches(p.as_ref())) { return Err(ValidatePackageRecordsError::PackageConstraintNotSatisfied { - package: package.name.as_normalized().to_string(), + package: package.to_owned(), constraint: constraint.to_owned(), violating_package: matching_package.unwrap().as_ref().to_owned(), }); @@ -367,14 +367,14 @@ impl PackageRecord { } } -/// An error that can occur when parsing a platform from a string. +/// An error when validating package records. #[derive(Debug, Error)] pub enum ValidatePackageRecordsError { /// A package is not present in the environment. #[error("package '{package}' has dependency '{dependency}', which is not in the environment")] DependencyNotInEnvironment { /// The package containing the unmet dependency. - package: String, + package: PackageRecord, /// The dependency that is not in the environment. dependency: String, }, @@ -382,7 +382,7 @@ pub enum ValidatePackageRecordsError { #[error("package '{package}' has constraint '{constraint}', which is not satisfied by '{violating_package}' in the environment")] PackageConstraintNotSatisfied { /// The package containing the unmet constraint. - package: String, + package: PackageRecord, /// The constraint that is violated. constraint: String, /// The corresponding package that violates the constraint. diff --git a/py-rattler/rattler/exceptions.py b/py-rattler/rattler/exceptions.py index 954789ca4..68d85f640 100644 --- a/py-rattler/rattler/exceptions.py +++ b/py-rattler/rattler/exceptions.py @@ -20,6 +20,7 @@ EnvironmentCreationError, ExtractError, GatewayError, + ValidatePackageRecordsException, ) except ImportError: # They are only redefined for documentation purposes @@ -85,6 +86,9 @@ class ExtractError(Exception): # type: ignore[no-redef] class GatewayError(Exception): # type: ignore[no-redef] """An error that can occur when querying the repodata gateway.""" + class ValidatePackageRecordsException(Exception): # type: ignore[no-redef] + """An error when validating package records.""" + __all__ = [ "ActivationError", @@ -107,4 +111,5 @@ class GatewayError(Exception): # type: ignore[no-redef] "EnvironmentCreationError", "ExtractError", "GatewayError", + "ValidatePackageRecordsException", ] diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index 6b572edda..45abdc8e9 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -134,12 +134,18 @@ def validate(records: List[PackageRecord]) -> None: >>> from os import listdir >>> from os.path import isfile, join >>> from rattler import PrefixRecord + >>> from rattler.exceptions import ValidatePackageRecordsException >>> records = [ ... PrefixRecord.from_path(join("../test-data/conda-meta/", f)) ... for f in listdir("../test-data/conda-meta") ... if isfile(join("../test-data/conda-meta", f)) ... ] - >>> PackageRecord.validate(records) + >>> try: + ... PackageRecord.validate(records) + ... except ValidatePackageRecordsException as e: + ... print(e) + package 'requests=2.28.2=pyhd8ed1ab_0' has dependency 'certifi >=2017.4.17', which is not in the environment + >>> ``` """ return PyRecord.validate(records) diff --git a/py-rattler/src/lib.rs b/py-rattler/src/lib.rs index 3cfe48f3b..2cbd1ff7c 100644 --- a/py-rattler/src/lib.rs +++ b/py-rattler/src/lib.rs @@ -34,7 +34,7 @@ use error::{ InvalidChannelException, InvalidMatchSpecException, InvalidPackageNameException, InvalidUrlException, InvalidVersionException, IoException, LinkException, ParseArchException, ParsePlatformException, PyRattlerError, SolverException, TransactionException, - VersionBumpException, + VersionBumpException, ValidatePackageRecordsException, }; use generic_virtual_package::PyGenericVirtualPackage; use index::py_index; @@ -223,5 +223,8 @@ fn rattler(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add("GatewayError", py.get_type::()) .unwrap(); + m.add("ValidatePackageRecordsException", py.get_type::()) + .unwrap(); + Ok(()) } From 2a1010a1e8689817e8f5cd8c502bc6d7a78e6f5d Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 13:44:52 +0200 Subject: [PATCH 19/23] fix tests --- crates/rattler_conda_types/src/repo_data/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index bdd0a11eb..0664c9399 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -658,7 +658,7 @@ mod test { let result = PackageRecord::validate(packages); assert!(result.is_err()); assert!(result.err().unwrap().to_string().contains( - "package 'foobar' has dependency 'bors <2.0', which is not in the environment" + "package 'foobar=2.0=bla_1' has dependency 'bors <2.0', which is not in the environment" )); } @@ -669,7 +669,7 @@ mod test { let result = PackageRecord::validate(vec![package_constrains, package_bors_2]); assert!(result.is_err()); assert!(result.err().unwrap().to_string().contains( - "package 'foo' has constraint 'bors <2.0', which is not satisfied by 'bors=2.1=bla_1' in the environment" + "package 'foo=3.0.2=py36h1af98f8_3' has constraint 'bors <2.0', which is not satisfied by 'bors=2.1=bla_1' in the environment" )); } } From a81d7ce0767809ce8df6aec1c3a6b4142d19d750 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 13:45:34 +0200 Subject: [PATCH 20/23] fmt --- py-rattler/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/py-rattler/src/lib.rs b/py-rattler/src/lib.rs index 2cbd1ff7c..94d01b778 100644 --- a/py-rattler/src/lib.rs +++ b/py-rattler/src/lib.rs @@ -34,7 +34,7 @@ use error::{ InvalidChannelException, InvalidMatchSpecException, InvalidPackageNameException, InvalidUrlException, InvalidVersionException, IoException, LinkException, ParseArchException, ParsePlatformException, PyRattlerError, SolverException, TransactionException, - VersionBumpException, ValidatePackageRecordsException, + ValidatePackageRecordsException, VersionBumpException, }; use generic_virtual_package::PyGenericVirtualPackage; use index::py_index; @@ -223,8 +223,11 @@ fn rattler(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add("GatewayError", py.get_type::()) .unwrap(); - m.add("ValidatePackageRecordsException", py.get_type::()) - .unwrap(); + m.add( + "ValidatePackageRecordsException", + py.get_type::(), + ) + .unwrap(); Ok(()) } From dd8f4e823051f03c012fec196d3f5a7e5aa22094 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 13:57:17 +0200 Subject: [PATCH 21/23] sorted --- py-rattler/rattler/repo_data/package_record.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index 45abdc8e9..df308595d 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -137,14 +137,14 @@ def validate(records: List[PackageRecord]) -> None: >>> from rattler.exceptions import ValidatePackageRecordsException >>> records = [ ... PrefixRecord.from_path(join("../test-data/conda-meta/", f)) - ... for f in listdir("../test-data/conda-meta") + ... for f in sorted(listdir("../test-data/conda-meta")) ... if isfile(join("../test-data/conda-meta", f)) ... ] >>> try: ... PackageRecord.validate(records) ... except ValidatePackageRecordsException as e: ... print(e) - package 'requests=2.28.2=pyhd8ed1ab_0' has dependency 'certifi >=2017.4.17', which is not in the environment + package 'libsqlite=3.40.0=hcfcfb64_0' has dependency 'ucrt >=10.0.20348.0', which is not in the environment >>> ``` """ From 8bad63fee8b60d40ac1dbcbafc0df00569fb053b Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 14:01:51 +0200 Subject: [PATCH 22/23] color pytest --- .github/workflows/python-bindings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-bindings.yml b/.github/workflows/python-bindings.yml index c78d90cb0..ac767e429 100644 --- a/.github/workflows/python-bindings.yml +++ b/.github/workflows/python-bindings.yml @@ -46,4 +46,4 @@ jobs: - name: Run tests run: | cd py-rattler - pixi run -e test test + pixi run -e test test --color=always From ee5514e4281e1b68436dc478f4337c8bc75290e0 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Sat, 26 Oct 2024 14:05:15 +0200 Subject: [PATCH 23/23] color=yes --- .github/workflows/python-bindings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-bindings.yml b/.github/workflows/python-bindings.yml index ac767e429..8f4d53692 100644 --- a/.github/workflows/python-bindings.yml +++ b/.github/workflows/python-bindings.yml @@ -46,4 +46,4 @@ jobs: - name: Run tests run: | cd py-rattler - pixi run -e test test --color=always + pixi run -e test test --color=yes