From e69c3e55c4c0340c201ac995ae3cd1c0b2ed1ce3 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Tue, 29 Aug 2023 13:21:24 +0200 Subject: [PATCH] chore: add more tests, hash and repr changes (#300) * chore: add more tests, hash and repr changes * fix: fmt --- py-rattler/rattler/match_spec/match_spec.py | 4 +- .../rattler/match_spec/nameless_match_spec.py | 2 +- py-rattler/rattler/version/version.py | 157 ++++++++++++++++-- py-rattler/src/version/mod.rs | 40 ++--- 4 files changed, 157 insertions(+), 46 deletions(-) diff --git a/py-rattler/rattler/match_spec/match_spec.py b/py-rattler/rattler/match_spec/match_spec.py index 10ba05c3c..ace62730e 100644 --- a/py-rattler/rattler/match_spec/match_spec.py +++ b/py-rattler/rattler/match_spec/match_spec.py @@ -111,7 +111,7 @@ def from_nameless(cls, spec: NamelessMatchSpec, name: str) -> Self: >>> from rattler import NamelessMatchSpec >>> spec = NamelessMatchSpec('3.4') >>> MatchSpec.from_nameless(spec, "foo") - foo ==3.4 + MatchSpec("foo ==3.4") >>> MatchSpec.from_nameless(spec, "$foo") # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): exceptions.InvalidPackageNameException @@ -124,4 +124,4 @@ def __str__(self) -> str: return self._match_spec.as_str() def __repr__(self) -> str: - return self.__str__() + return f'MatchSpec("{self._match_spec.as_str()}")' diff --git a/py-rattler/rattler/match_spec/nameless_match_spec.py b/py-rattler/rattler/match_spec/nameless_match_spec.py index 032c33bdc..c78cc8017 100644 --- a/py-rattler/rattler/match_spec/nameless_match_spec.py +++ b/py-rattler/rattler/match_spec/nameless_match_spec.py @@ -55,4 +55,4 @@ def __str__(self) -> str: return self._nameless_match_spec.as_str() def __repr__(self) -> str: - return self.__str__() + return f'NamelessMatchSpec("{self._nameless_match_spec.as_str()}")' diff --git a/py-rattler/rattler/version/version.py b/py-rattler/rattler/version/version.py index 73aaf64c3..af067c1b0 100644 --- a/py-rattler/rattler/version/version.py +++ b/py-rattler/rattler/version/version.py @@ -33,12 +33,6 @@ def _from_py_version(cls, py_version: PyVersion) -> Version: version._version = py_version return version - def __str__(self) -> str: - return self._version.as_str() - - def __repr__(self) -> str: - return self.__str__() - @property def epoch(self) -> Optional[str]: """ @@ -61,7 +55,7 @@ def bump(self) -> Version: -------- >>> v = Version('1.0') >>> v.bump() - 1.1 + Version("1.1") """ return Version._from_py_version(self._version.bump()) @@ -185,9 +179,9 @@ def pop_segments(self, n: int = 1) -> Version: -------- >>> v = Version('2!1.0.1') >>> v.pop_segments() # `n` defaults to 1 if left empty - 2!1.0 + Version("2!1.0") >>> v.pop_segments(2) # old version is still usable - 2!1 + Version("2!1") >>> v.pop_segments(3) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): exceptions.InvalidVersionException: new Version must have atleast 1 valid @@ -209,7 +203,7 @@ def with_segments(self, start: int, stop: int) -> Version: -------- >>> v = Version('2!1.2.3') >>> v.with_segments(0, 2) - 2!1.2 + Version("2!1.2") """ new_py_version = self._version.with_segments(start, stop) if new_py_version: @@ -239,24 +233,153 @@ def strip_local(self) -> Version: -------- >>> v = Version('1.2.3+4.alpha-5') >>> v.strip_local() - 1.2.3 + Version("1.2.3") """ return self._from_py_version(self._version.strip_local()) + def __str__(self) -> str: + """ + Returns the string representation of the version + + Examples + -------- + >>> str(Version("1.2.3")) + '1.2.3' + """ + return self._version.as_str() + + def __repr__(self) -> str: + """ + Returns a representation of the version + + Examples + -------- + >>> Version("1.2.3") + Version("1.2.3") + """ + return f'Version("{self._version.as_str()}")' + + def __hash__(self) -> int: + """ + Computes the hash of this instance. + + Examples + -------- + >>> hash(Version("1.2.3")) == hash(Version("1.2.3")) + True + >>> hash(Version("1.2.3")) == hash(Version("3.2.1")) + False + >>> hash(Version("1")) == hash(Version("1.0.0")) + True + """ + return self._version.__hash__() + def __eq__(self, other: Version) -> bool: - return self._version.equals(other._version) + """ + Returns True if this instance represents the same version as `other`. + + Examples + -------- + >>> Version("1.2.3") == Version("1.2.3") + True + >>> Version("3.2.1") == Version("1.2.3") + False + >>> Version("1") == Version("1.0.0") + True + """ + return self._version == other._version def __ne__(self, other: Version) -> bool: - return self._version.not_equal(other._version) + """ + Returns True if this instance represents the same version as `other`. + + Examples + -------- + >>> Version("1.2.3") != Version("1.2.3") + False + >>> Version("3.2.1") != Version("1.2.3") + True + >>> Version("1") != Version("1.0.0") + False + """ + return self._version != other._version def __gt__(self, other: Version) -> bool: - return self._version.greater_than(other._version) + """ + Returns True if this instance should be ordered *after* `other`. + + Examples + -------- + >>> Version("1.2.3") > Version("1.2.3") + False + >>> Version("1.2.4") > Version("1.2.3") + True + >>> Version("1.2.3.1") > Version("1.2.3") + True + >>> Version("3.2.1") > Version("1.2.3") + True + >>> Version("1") > Version("1.0.0") + False + """ + return self._version > other._version def __lt__(self, other: Version) -> bool: - return self._version.less_than(other._version) + """ + Returns True if this instance should be ordered *before* `other`. + + Examples + -------- + >>> Version("1.2.3") < Version("1.2.3") + False + >>> Version("1.2.3") < Version("1.2.4") + True + >>> Version("1.2.3") < Version("1.2.3.1") + True + >>> Version("3.2.1") < Version("1.2.3") + False + >>> Version("1") < Version("1.0.0") + False + """ + return self._version < other._version def __ge__(self, other: Version) -> bool: - return self._version.greater_than_equals(other._version) + """ + Returns True if this instance should be ordered *after* or at the same location + as `other`. + + Examples + -------- + >>> Version("1.2.3") >= Version("1.2.3") + True + >>> Version("1.2.4") >= Version("1.2.3") + True + >>> Version("1.2.3.1") >= Version("1.2.3") + True + >>> Version("3.2.1") >= Version("1.2.3") + True + >>> Version("1.2.3") >= Version("3.2.1") + False + >>> Version("1") >= Version("1.0.0") + True + """ + return self._version >= other._version def __le__(self, other: Version) -> bool: - return self._version.less_than_equals(other._version) + """ + Returns True if this instance should be ordered *before* or at the same + location as `other`. + + Examples + -------- + >>> Version("1.2.3") <= Version("1.2.3") + True + >>> Version("1.2.3") <= Version("1.2.4") + True + >>> Version("1.2.3") <= Version("1.2.3.1") + True + >>> Version("3.2.1") <= Version("1.2.3") + False + >>> Version("1") <= Version("1.0.0") + True + """ + return self._version <= other._version diff --git a/py-rattler/src/version/mod.rs b/py-rattler/src/version/mod.rs index db5ffa372..cf99e0662 100644 --- a/py-rattler/src/version/mod.rs +++ b/py-rattler/src/version/mod.rs @@ -2,9 +2,13 @@ mod component; use crate::PyRattlerError; use component::PyComponent; -use pyo3::{pyclass, pymethods}; +use pyo3::{basic::CompareOp, pyclass, pymethods}; use rattler_conda_types::Version; -use std::str::FromStr; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + str::FromStr, +}; #[pyclass] #[repr(transparent)] @@ -129,31 +133,15 @@ impl PyVersion { } } - pub fn equal(&self, other: &Self) -> bool { - self.inner == other.inner + /// Compute the hash of the version. + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.inner.hash(&mut hasher); + hasher.finish() } - pub fn not_equal(&self, other: &Self) -> bool { - self.inner != other.inner - } - - pub fn less_than(&self, other: &Self) -> bool { - self.inner < other.inner - } - - pub fn less_than_equals(&self, other: &Self) -> bool { - self.inner <= other.inner - } - - pub fn equals(&self, other: &Self) -> bool { - self.inner == other.inner - } - - pub fn greater_than_equals(&self, other: &Self) -> bool { - self.inner >= other.inner - } - - pub fn greater_than(&self, other: &Self) -> bool { - self.inner > other.inner + /// Performs comparison between this version and another. + pub fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { + op.matches(self.inner.cmp(&other.inner)) } }