From db0adbb6cf3904f96387c2e5eefc40a32bef6d6e Mon Sep 17 00:00:00 2001 From: Wackyator Date: Wed, 30 Aug 2023 13:45:24 +0530 Subject: [PATCH 1/6] feat: add PyVersionWithSource --- py-rattler/Cargo.lock | 30 ++++++------- py-rattler/src/lib.rs | 3 +- py-rattler/src/version/mod.rs | 13 +++++- py-rattler/src/version/version_with_source.rs | 42 +++++++++++++++++++ 4 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 py-rattler/src/version/version_with_source.rs diff --git a/py-rattler/Cargo.lock b/py-rattler/Cargo.lock index b70ee88e7..c064adcd3 100644 --- a/py-rattler/Cargo.lock +++ b/py-rattler/Cargo.lock @@ -91,9 +91,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "f56b4c72906975ca04becb8a30e102dfecddd0c06181e3e95ddc444be28881f8" dependencies = [ "android-tzdata", "iana-time-zone", @@ -102,7 +102,7 @@ dependencies = [ "serde", "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] @@ -641,9 +641,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" dependencies = [ "aho-corasick", "memchr", @@ -653,9 +653,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", @@ -664,9 +664,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustversion" @@ -688,9 +688,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -1008,9 +1008,9 @@ checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", diff --git a/py-rattler/src/lib.rs b/py-rattler/src/lib.rs index c73b6fd19..38ebf94f2 100644 --- a/py-rattler/src/lib.rs +++ b/py-rattler/src/lib.rs @@ -10,13 +10,14 @@ use error::{ use match_spec::PyMatchSpec; use nameless_match_spec::PyNamelessMatchSpec; use repo_data::package_record::PyPackageRecord; -use version::PyVersion; +use version::{PyVersion, PyVersionWithSource}; use pyo3::prelude::*; #[pymodule] fn rattler(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::().unwrap(); + m.add_class::().unwrap(); m.add_class::().unwrap(); m.add_class::().unwrap(); diff --git a/py-rattler/src/version/mod.rs b/py-rattler/src/version/mod.rs index cf99e0662..3efa36f85 100644 --- a/py-rattler/src/version/mod.rs +++ b/py-rattler/src/version/mod.rs @@ -1,4 +1,5 @@ mod component; +mod version_with_source; use crate::PyRattlerError; use component::PyComponent; @@ -10,11 +11,13 @@ use std::{ str::FromStr, }; -#[pyclass] +pub use version_with_source::PyVersionWithSource; + +#[pyclass(subclass)] #[repr(transparent)] #[derive(Clone)] pub struct PyVersion { - inner: Version, + pub(crate) inner: Version, } impl From for PyVersion { @@ -23,6 +26,12 @@ impl From for PyVersion { } } +impl From for Version { + fn from(value: PyVersion) -> Self { + value.inner + } +} + #[pymethods] impl PyVersion { #[new] diff --git a/py-rattler/src/version/version_with_source.rs b/py-rattler/src/version/version_with_source.rs new file mode 100644 index 000000000..2c9656f7a --- /dev/null +++ b/py-rattler/src/version/version_with_source.rs @@ -0,0 +1,42 @@ +use pyo3::{pyclass, pymethods, PyClassInitializer}; +use rattler_conda_types::VersionWithSource; + +use crate::version::PyVersion; + +#[pyclass(extends=PyVersion, subclass)] +#[repr(transparent)] +#[derive(Clone)] +pub struct PyVersionWithSource { + pub(crate) inner: VersionWithSource, +} + +impl From for PyVersionWithSource { + fn from(value: VersionWithSource) -> Self { + Self { inner: value } + } +} + +impl From for VersionWithSource { + fn from(value: PyVersionWithSource) -> Self { + value.inner + } +} + +#[pymethods] +impl PyVersionWithSource { + #[new] + pub fn new(version: &PyVersion, source: String) -> pyo3::PyClassInitializer { + PyClassInitializer::from(version.clone()) + .add_subclass(VersionWithSource::new(version.inner.clone(), source).into()) + } + + /// Returns the `PyVersion` from current object. + pub fn version(&self) -> PyVersion { + self.inner.version().clone().into() + } + + /// Returns a string representation of `PyVersionWithSource`. + pub fn as_str(&self) -> String { + self.inner.as_str().into_owned() + } +} From 6f8761dc663006d639851e8f04846ba18e093f5e Mon Sep 17 00:00:00 2001 From: Wackyator Date: Wed, 30 Aug 2023 18:36:48 +0530 Subject: [PATCH 2/6] feat: add VersionWithSource --- py-rattler/rattler/__init__.py | 10 +- py-rattler/rattler/version/__init__.py | 3 +- py-rattler/rattler/version/with_source.py | 447 ++++++++++++++++++ py-rattler/src/version/mod.rs | 4 +- ...{version_with_source.rs => with_source.rs} | 19 +- 5 files changed, 477 insertions(+), 6 deletions(-) create mode 100644 py-rattler/rattler/version/with_source.py rename py-rattler/src/version/{version_with_source.rs => with_source.rs} (67%) diff --git a/py-rattler/rattler/__init__.py b/py-rattler/rattler/__init__.py index 4d084172b..b504c312b 100644 --- a/py-rattler/rattler/__init__.py +++ b/py-rattler/rattler/__init__.py @@ -1,5 +1,11 @@ -from rattler.version import Version +from rattler.version import Version, VersionWithSource from rattler.match_spec import MatchSpec, NamelessMatchSpec from rattler.repo_data import PackageRecord -__all__ = ["Version", "MatchSpec", "NamelessMatchSpec", "PackageRecord"] +__all__ = [ + "Version", + "VersionWithSource", + "MatchSpec", + "NamelessMatchSpec", + "PackageRecord", +] diff --git a/py-rattler/rattler/version/__init__.py b/py-rattler/rattler/version/__init__.py index 6ccadac82..199655316 100644 --- a/py-rattler/rattler/version/__init__.py +++ b/py-rattler/rattler/version/__init__.py @@ -1,3 +1,4 @@ from rattler.version.version import Version +from rattler.version.with_source import VersionWithSource -__all__ = ["Version"] +__all__ = ["Version", "VersionWithSource"] diff --git a/py-rattler/rattler/version/with_source.py b/py-rattler/rattler/version/with_source.py new file mode 100644 index 000000000..9b2b95b28 --- /dev/null +++ b/py-rattler/rattler/version/with_source.py @@ -0,0 +1,447 @@ +from __future__ import annotations + +from typing import List, Optional, Tuple, Union + +from rattler.version import Version + +from rattler.rattler import PyVersionWithSource, InvalidVersionError + + +class VersionWithSource: + """ + Holds a version and the string it was created from. This is useful if + you want to retain the original string the version was created from. + This might be useful in cases where you have multiple strings that + are represented by the same [`Version`] but you still want to be able to + distinguish them. + + The string `1.0` and `1.01` represent the same version. When you print + the parsed version though it will come out as `1.0`. You loose the + original representation. This struct stores the original source string. + """ + + def __init__(self, source: str, version: Optional[Version] = None): + if isinstance(source, str): + if version is None: + version = Version(source) + + if isinstance(version, Version): + # maybe we should use _inner for inner FFI objects everywhere? + self._inner = PyVersionWithSource(version._version, source) + else: + raise TypeError( + "VersionWithSource constructor received unsupported type " + f" {type(version).__name__!r} for the `version` parameter" + ) + else: + raise TypeError( + "VersionWithSource constructor received unsupported type " + f" {type(version).__name__!r} for the `source` parameter" + ) + + @property + def version(self) -> Version: + """ + Returns the `Version` from current object. + + Examples + -------- + >>> v = VersionWithSource("1.0.0") + >>> v.version + Version("1.0.0") + >>> v2 = VersionWithSource("1.0.0", v.version) + >>> v2.version + Version("1.0.0") + """ + return Version._from_py_version(self._inner.version()) + + @property + def epoch(self) -> Optional[int]: + """ + Gets the epoch of the version or `None` if the epoch was not defined. + + Examples + -------- + >>> v = VersionWithSource('2!1.0') + >>> v.epoch + 2 + >>> v2 = VersionWithSource('2!1.0', v.version) + >>> v2.epoch + 2 + """ + return self._inner.epoch() + + def bump(self) -> VersionWithSource: + """ + Returns a new version where the last numerical segment of this version has + been bumped. + + Examples + -------- + >>> v = VersionWithSource('1.0') + >>> v.bump() + VersionWithSource("1.1") + >>> v2 = VersionWithSource('1.0', v.version) + >>> v2.bump() + VersionWithSource("1.1") + """ + return VersionWithSource._from_py_version_with_source(self._inner.bump()) + + @property + def has_local(self) -> bool: + """ + Returns true if this version has a local segment defined. + The local part of a version is the part behind the (optional) `+`. + + Examples + -------- + >>> v = VersionWithSource('1.0+3.2-alpha0') + >>> v.has_local + True + >>> v2 = VersionWithSource('1.0') + >>> v2.has_local + False + >>> v3 = VersionWithSource('1.0+3.2-alpha0', v.version) + >>> v3.has_local + True + >>> v4 = VersionWithSource('1.0', v2.version) + >>> v4.has_local + False + """ + return self._inner.has_local() + + def segments(self) -> List[List[Union[str, int]]]: + """ + Returns a list of segments of the version. It does not contain + the local segment of the version. + + Examples + -------- + >>> v = VersionWithSource("1.2dev.3-alpha4.5+6.8") + >>> v.segments() + [[1], [2, 'dev'], [3], [0, 'alpha', 4], [5]] + >>> v2 = VersionWithSource("1.2dev.3-alpha4.5+6.8", v.version) + >>> v2.segments() + [[1], [2, 'dev'], [3], [0, 'alpha', 4], [5]] + """ + return self._inner.segments() + + def local_segments(self) -> List[List[Union[str, int]]]: + """ + Returns a list of local segments of the version. It does not + contain the non-local segment of the version. + + Examples + -------- + >>> v = VersionWithSource("1.2dev.3-alpha4.5+6.8") + >>> v.local_segments() + [[6], [8]] + >>> v2 = VersionWithSource("1.2dev.3-alpha4.5+6.8", v.version) + >>> v2.local_segments() + [[6], [8]] + """ + return self._inner.local_segments() + + def as_major_minor(self) -> Optional[Tuple[int, int]]: + """ + Returns the major and minor segments from the version. + Requires a minimum of 2 segments in version to be split + into major and minor, returns `None` otherwise. + + Examples + -------- + >>> v = VersionWithSource('1.0') + >>> v.as_major_minor() + (1, 0) + >>> v2 = VersionWithSource('1.0', v.version) + >>> v2.as_major_minor() + (1, 0) + """ + return self._inner.as_major_minor() + + @property + def is_dev(self) -> bool: + """ + Returns true if the version contains a component name "dev", + dev versions are sorted before non-dev version. + + Examples + -------- + >>> v = VersionWithSource('1.0.1dev') + >>> v.is_dev + True + >>> v_non_dev = VersionWithSource('1.0.1') + >>> v_non_dev >= v + True + >>> v2 = VersionWithSource('1.0.1dev', v.version) + >>> v2.is_dev + True + >>> v2_non_dev = VersionWithSource('1.0.1', v_non_dev.version) + >>> v2_non_dev >= v2 + True + """ + return self._inner.is_dev() + + def starts_with(self, other: VersionWithSource) -> bool: + """ + Checks if the version and local segment start + same as other version. + + Examples + -------- + >>> v1 = VersionWithSource('1.0.1') + >>> v2 = VersionWithSource('1.0') + >>> v1.starts_with(v2) + True + >>> v3 = VersionWithSource('1.0.1', v1.version) + >>> v4 = VersionWithSource('1.0', v2.version) + >>> v3.starts_with(v4) + True + """ + return self._inner.starts_with(other._inner) + + def compatible_with(self, other: VersionWithSource) -> bool: + """ + Checks if this version is compatible with other version. + Minor versions changes are compatible with older versions, + major version changes are breaking and will not be compatible. + + Examples + -------- + >>> v1 = VersionWithSource('1.0') + >>> v2 = VersionWithSource('1.2') + >>> v_major = VersionWithSource('2.0') + >>> v1.compatible_with(v2) + False + >>> v2.compatible_with(v1) + True + >>> v_major.compatible_with(v2) + False + >>> v2.compatible_with(v_major) + False + """ + return self._inner.compatible_with(other._inner) + + def pop_segments(self, n: int = 1) -> VersionWithSource: + """ + Pops `n` number of segments from the version and returns + the new version. Raises `InvalidVersionError` if version + becomes invalid due to the operation. + + Examples + -------- + >>> v = VersionWithSource('2!1.0.1') + >>> v.pop_segments() # `n` defaults to 1 if left empty + VersionWithSource("2!1.0") + >>> v.pop_segments(2) # old version is still usable + VersionWithSource("2!1") + >>> v.pop_segments(3) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + exceptions.InvalidVersionException: new Version must have atleast 1 valid + segment + """ + new_py_version = self._inner.pop_segments(n) + if new_py_version: + return self._from_py_version_with_source(new_py_version) + else: + raise InvalidVersionError("new Version must have atleast 1 valid segment") + + def with_segments(self, start: int, stop: int) -> VersionWithSource: + """ + Returns new version with with segments ranging from `start` to `stop`. + `stop` is exclusive. Raises `InvalidVersionError` if the provided range + is invalid. + + Examples + -------- + >>> v = VersionWithSource('2!1.2.3') + >>> v.with_segments(0, 2) + VersionWithSource("2!1.2") + """ + new_py_version = self._inner.with_segments(start, stop) + if new_py_version: + return self._from_py_version_with_source(new_py_version) + else: + raise InvalidVersionError("Invalid segment range provided") + + @property + def segment_count(self) -> int: + """ + Returns the number of segments in the version. + This does not include epoch or local segment of the version + + Examples + -------- + >>> v = VersionWithSource('2!1.2.3') + >>> v.segment_count + 3 + """ + return self._inner.segment_count() + + def strip_local(self) -> VersionWithSource: + """ + Returns a new version with local segment stripped. + + Examples + -------- + >>> v = VersionWithSource('1.2.3+4.alpha-5') + >>> v.strip_local() + VersionWithSource("1.2.3") + """ + return self._from_py_version_with_source(self._inner.strip_local()) + + @classmethod + def _from_py_version_with_source( + cls, py_version_with_source: PyVersionWithSource + ) -> VersionWithSource: + """Construct Rattler VersionWithSource from FFI PyVersionWithSource object.""" + version = cls.__new__(cls) + version._inner = py_version_with_source + return version + + def __str__(self): + """ + Returns the string representation of the version + + Examples + -------- + >>> str(VersionWithSource("1.2.3")) + '1.2.3' + """ + return self._inner.as_str() + + def __repr__(self): + """ + Returns a representation of the version + + Examples + -------- + >>> VersionWithSource("1.2.3") + VersionWithSource("1.2.3") + """ + return f'{type(self).__name__}("{self._inner.as_str()}")' + + def __hash__(self) -> int: + """ + Computes the hash of this instance. + + Examples + -------- + >>> hash(VersionWithSource("1.2.3")) == hash(VersionWithSource("1.2.3")) + True + >>> hash(VersionWithSource("1.2.3")) == hash(VersionWithSource("3.2.1")) + False + >>> hash(VersionWithSource("1")) == hash(VersionWithSource("1.0.0")) + False + """ + return self._inner.__hash__() + + def __eq__(self, other: VersionWithSource) -> bool: + """ + Returns True if this instance represents the same version as `other`. + + Examples + -------- + >>> VersionWithSource("1.2.3") == VersionWithSource("1.2.3") + True + >>> VersionWithSource("3.2.1") == VersionWithSource("1.2.3") + False + >>> VersionWithSource("1") == VersionWithSource("1.0.0") + False + """ + return self._inner == other._inner + + def __ne__(self, other: VersionWithSource) -> bool: + """ + Returns True if this instance represents the same version as `other`. + + Examples + -------- + >>> VersionWithSource("1.2.3") != VersionWithSource("1.2.3") + False + >>> VersionWithSource("3.2.1") != VersionWithSource("1.2.3") + True + >>> VersionWithSource("1") != VersionWithSource("1.0.0") + True + """ + return self._inner != other._inner + + def __gt__(self, other: VersionWithSource) -> bool: + """ + Returns True if this instance should be ordered *after* `other`. + + Examples + -------- + >>> VersionWithSource("1.2.3") > VersionWithSource("1.2.3") + False + >>> VersionWithSource("1.2.4") > VersionWithSource("1.2.3") + True + >>> VersionWithSource("1.2.3.1") > VersionWithSource("1.2.3") + True + >>> VersionWithSource("3.2.1") > VersionWithSource("1.2.3") + True + >>> VersionWithSource("1") > VersionWithSource("1.0.0") + False + """ + return self._inner > other._inner + + def __lt__(self, other: VersionWithSource) -> bool: + """ + Returns True if this instance should be ordered *before* `other`. + + Examples + -------- + >>> VersionWithSource("1.2.3") < VersionWithSource("1.2.3") + False + >>> VersionWithSource("1.2.3") < VersionWithSource("1.2.4") + True + >>> VersionWithSource("1.2.3") < VersionWithSource("1.2.3.1") + True + >>> VersionWithSource("3.2.1") < VersionWithSource("1.2.3") + False + >>> VersionWithSource("1") < VersionWithSource("1.0.0") + True + """ + return self._inner < other._inner + + def __ge__(self, other: VersionWithSource) -> bool: + """ + Returns True if this instance should be ordered *after* or at the same location + as `other`. + + Examples + -------- + >>> VersionWithSource("1.2.3") >= VersionWithSource("1.2.3") + True + >>> VersionWithSource("1.2.4") >= VersionWithSource("1.2.3") + True + >>> VersionWithSource("1.2.3.1") >= VersionWithSource("1.2.3") + True + >>> VersionWithSource("3.2.1") >= VersionWithSource("1.2.3") + True + >>> VersionWithSource("1.2.3") >= VersionWithSource("3.2.1") + False + >>> VersionWithSource("1") >= VersionWithSource("1.0.0") + False + """ + return self._inner >= other._inner + + def __le__(self, other: VersionWithSource) -> bool: + """ + Returns True if this instance should be ordered *before* or at the same + location as `other`. + + Examples + -------- + >>> VersionWithSource("1.2.3") <= VersionWithSource("1.2.3") + True + >>> VersionWithSource("1.2.3") <= VersionWithSource("1.2.4") + True + >>> VersionWithSource("1.2.3") <= VersionWithSource("1.2.3.1") + True + >>> VersionWithSource("3.2.1") <= VersionWithSource("1.2.3") + False + >>> VersionWithSource("1") <= VersionWithSource("1.0.0") + True + """ + return self._inner <= other._inner diff --git a/py-rattler/src/version/mod.rs b/py-rattler/src/version/mod.rs index 3efa36f85..f046aab91 100644 --- a/py-rattler/src/version/mod.rs +++ b/py-rattler/src/version/mod.rs @@ -1,5 +1,5 @@ mod component; -mod version_with_source; +mod with_source; use crate::PyRattlerError; use component::PyComponent; @@ -11,7 +11,7 @@ use std::{ str::FromStr, }; -pub use version_with_source::PyVersionWithSource; +pub use with_source::PyVersionWithSource; #[pyclass(subclass)] #[repr(transparent)] diff --git a/py-rattler/src/version/version_with_source.rs b/py-rattler/src/version/with_source.rs similarity index 67% rename from py-rattler/src/version/version_with_source.rs rename to py-rattler/src/version/with_source.rs index 2c9656f7a..2d9719a1e 100644 --- a/py-rattler/src/version/version_with_source.rs +++ b/py-rattler/src/version/with_source.rs @@ -1,4 +1,9 @@ -use pyo3::{pyclass, pymethods, PyClassInitializer}; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; + +use pyo3::{basic::CompareOp, pyclass, pymethods, PyClassInitializer}; use rattler_conda_types::VersionWithSource; use crate::version::PyVersion; @@ -39,4 +44,16 @@ impl PyVersionWithSource { pub fn as_str(&self) -> String { self.inner.as_str().into_owned() } + + /// Compute the hash of the version. + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.inner.hash(&mut hasher); + hasher.finish() + } + + /// Performs comparison between this version and another. + pub fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { + op.matches(self.inner.cmp(&other.inner)) + } } From 84b12e1377228a08b7db871b627a80417e0e325e Mon Sep 17 00:00:00 2001 From: Wackyator Date: Mon, 4 Sep 2023 11:46:50 +0530 Subject: [PATCH 3/6] refactor: VersionWithSource just holds source and version --- py-rattler/rattler/version/with_source.py | 431 ++-------------------- 1 file changed, 37 insertions(+), 394 deletions(-) diff --git a/py-rattler/rattler/version/with_source.py b/py-rattler/rattler/version/with_source.py index 9b2b95b28..0f133f217 100644 --- a/py-rattler/rattler/version/with_source.py +++ b/py-rattler/rattler/version/with_source.py @@ -1,11 +1,9 @@ from __future__ import annotations -from typing import List, Optional, Tuple, Union +from typing import Optional from rattler.version import Version -from rattler.rattler import PyVersionWithSource, InvalidVersionError - class VersionWithSource: """ @@ -15,30 +13,31 @@ class VersionWithSource: are represented by the same [`Version`] but you still want to be able to distinguish them. - The string `1.0` and `1.01` represent the same version. When you print - the parsed version though it will come out as `1.0`. You loose the - original representation. This struct stores the original source string. + The string `1.1` and `1.01` represent the same version. When you print + the parsed version though it will come out as `1.1`. You loose the + original representation. This class stores the original source string, + which can be accessed by `source` property. """ def __init__(self, source: str, version: Optional[Version] = None): - if isinstance(source, str): - if version is None: - version = Version(source) + if not isinstance(source, str): + raise TypeError( + "VersionWithSource constructor received unsupported type " + f" {type(source).__name__!r} for the `source` parameter" + ) - if isinstance(version, Version): - # maybe we should use _inner for inner FFI objects everywhere? - self._inner = PyVersionWithSource(version._version, source) - else: - raise TypeError( - "VersionWithSource constructor received unsupported type " - f" {type(version).__name__!r} for the `version` parameter" - ) - else: + if version is None: + version = Version(source) + + if not isinstance(version, Version): raise TypeError( "VersionWithSource constructor received unsupported type " - f" {type(version).__name__!r} for the `source` parameter" + f" {type(version).__name__!r} for the `version` parameter" ) + self._source = source + self._version = version + @property def version(self) -> Version: """ @@ -46,402 +45,46 @@ def version(self) -> Version: Examples -------- - >>> v = VersionWithSource("1.0.0") + >>> v = VersionWithSource("1.01") >>> v.version - Version("1.0.0") - >>> v2 = VersionWithSource("1.0.0", v.version) + Version("1.1") + >>> v2 = VersionWithSource("1.01", v.version) >>> v2.version - Version("1.0.0") - """ - return Version._from_py_version(self._inner.version()) - - @property - def epoch(self) -> Optional[int]: - """ - Gets the epoch of the version or `None` if the epoch was not defined. - - Examples - -------- - >>> v = VersionWithSource('2!1.0') - >>> v.epoch - 2 - >>> v2 = VersionWithSource('2!1.0', v.version) - >>> v2.epoch - 2 - """ - return self._inner.epoch() - - def bump(self) -> VersionWithSource: - """ - Returns a new version where the last numerical segment of this version has - been bumped. - - Examples - -------- - >>> v = VersionWithSource('1.0') - >>> v.bump() - VersionWithSource("1.1") - >>> v2 = VersionWithSource('1.0', v.version) - >>> v2.bump() - VersionWithSource("1.1") - """ - return VersionWithSource._from_py_version_with_source(self._inner.bump()) - - @property - def has_local(self) -> bool: - """ - Returns true if this version has a local segment defined. - The local part of a version is the part behind the (optional) `+`. - - Examples - -------- - >>> v = VersionWithSource('1.0+3.2-alpha0') - >>> v.has_local - True - >>> v2 = VersionWithSource('1.0') - >>> v2.has_local - False - >>> v3 = VersionWithSource('1.0+3.2-alpha0', v.version) - >>> v3.has_local - True - >>> v4 = VersionWithSource('1.0', v2.version) - >>> v4.has_local - False - """ - return self._inner.has_local() - - def segments(self) -> List[List[Union[str, int]]]: - """ - Returns a list of segments of the version. It does not contain - the local segment of the version. - - Examples - -------- - >>> v = VersionWithSource("1.2dev.3-alpha4.5+6.8") - >>> v.segments() - [[1], [2, 'dev'], [3], [0, 'alpha', 4], [5]] - >>> v2 = VersionWithSource("1.2dev.3-alpha4.5+6.8", v.version) - >>> v2.segments() - [[1], [2, 'dev'], [3], [0, 'alpha', 4], [5]] - """ - return self._inner.segments() - - def local_segments(self) -> List[List[Union[str, int]]]: - """ - Returns a list of local segments of the version. It does not - contain the non-local segment of the version. - - Examples - -------- - >>> v = VersionWithSource("1.2dev.3-alpha4.5+6.8") - >>> v.local_segments() - [[6], [8]] - >>> v2 = VersionWithSource("1.2dev.3-alpha4.5+6.8", v.version) - >>> v2.local_segments() - [[6], [8]] - """ - return self._inner.local_segments() - - def as_major_minor(self) -> Optional[Tuple[int, int]]: - """ - Returns the major and minor segments from the version. - Requires a minimum of 2 segments in version to be split - into major and minor, returns `None` otherwise. - - Examples - -------- - >>> v = VersionWithSource('1.0') - >>> v.as_major_minor() - (1, 0) - >>> v2 = VersionWithSource('1.0', v.version) - >>> v2.as_major_minor() - (1, 0) - """ - return self._inner.as_major_minor() - - @property - def is_dev(self) -> bool: - """ - Returns true if the version contains a component name "dev", - dev versions are sorted before non-dev version. - - Examples - -------- - >>> v = VersionWithSource('1.0.1dev') - >>> v.is_dev - True - >>> v_non_dev = VersionWithSource('1.0.1') - >>> v_non_dev >= v - True - >>> v2 = VersionWithSource('1.0.1dev', v.version) - >>> v2.is_dev - True - >>> v2_non_dev = VersionWithSource('1.0.1', v_non_dev.version) - >>> v2_non_dev >= v2 - True - """ - return self._inner.is_dev() - - def starts_with(self, other: VersionWithSource) -> bool: - """ - Checks if the version and local segment start - same as other version. - - Examples - -------- - >>> v1 = VersionWithSource('1.0.1') - >>> v2 = VersionWithSource('1.0') - >>> v1.starts_with(v2) - True - >>> v3 = VersionWithSource('1.0.1', v1.version) - >>> v4 = VersionWithSource('1.0', v2.version) - >>> v3.starts_with(v4) - True - """ - return self._inner.starts_with(other._inner) - - def compatible_with(self, other: VersionWithSource) -> bool: - """ - Checks if this version is compatible with other version. - Minor versions changes are compatible with older versions, - major version changes are breaking and will not be compatible. - - Examples - -------- - >>> v1 = VersionWithSource('1.0') - >>> v2 = VersionWithSource('1.2') - >>> v_major = VersionWithSource('2.0') - >>> v1.compatible_with(v2) - False - >>> v2.compatible_with(v1) - True - >>> v_major.compatible_with(v2) - False - >>> v2.compatible_with(v_major) - False - """ - return self._inner.compatible_with(other._inner) - - def pop_segments(self, n: int = 1) -> VersionWithSource: - """ - Pops `n` number of segments from the version and returns - the new version. Raises `InvalidVersionError` if version - becomes invalid due to the operation. - - Examples - -------- - >>> v = VersionWithSource('2!1.0.1') - >>> v.pop_segments() # `n` defaults to 1 if left empty - VersionWithSource("2!1.0") - >>> v.pop_segments(2) # old version is still usable - VersionWithSource("2!1") - >>> v.pop_segments(3) # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - exceptions.InvalidVersionException: new Version must have atleast 1 valid - segment - """ - new_py_version = self._inner.pop_segments(n) - if new_py_version: - return self._from_py_version_with_source(new_py_version) - else: - raise InvalidVersionError("new Version must have atleast 1 valid segment") - - def with_segments(self, start: int, stop: int) -> VersionWithSource: - """ - Returns new version with with segments ranging from `start` to `stop`. - `stop` is exclusive. Raises `InvalidVersionError` if the provided range - is invalid. - - Examples - -------- - >>> v = VersionWithSource('2!1.2.3') - >>> v.with_segments(0, 2) - VersionWithSource("2!1.2") + Version("1.1") """ - new_py_version = self._inner.with_segments(start, stop) - if new_py_version: - return self._from_py_version_with_source(new_py_version) - else: - raise InvalidVersionError("Invalid segment range provided") + return self._version @property - def segment_count(self) -> int: + def source(self) -> str: """ - Returns the number of segments in the version. - This does not include epoch or local segment of the version + Returns the `source` current object was used to create. Examples -------- - >>> v = VersionWithSource('2!1.2.3') - >>> v.segment_count - 3 + >>> v = VersionWithSource("1.01.01") + >>> v.source + '1.01.01' """ - return self._inner.segment_count() + return self._source - def strip_local(self) -> VersionWithSource: - """ - Returns a new version with local segment stripped. - - Examples - -------- - >>> v = VersionWithSource('1.2.3+4.alpha-5') - >>> v.strip_local() - VersionWithSource("1.2.3") - """ - return self._from_py_version_with_source(self._inner.strip_local()) - - @classmethod - def _from_py_version_with_source( - cls, py_version_with_source: PyVersionWithSource - ) -> VersionWithSource: - """Construct Rattler VersionWithSource from FFI PyVersionWithSource object.""" - version = cls.__new__(cls) - version._inner = py_version_with_source - return version - - def __str__(self): + def __str__(self) -> str: """ Returns the string representation of the version Examples -------- - >>> str(VersionWithSource("1.2.3")) - '1.2.3' + >>> str(VersionWithSource("1.02.3")) + '1.02.3' """ - return self._inner.as_str() + return self._source - def __repr__(self): + def __repr__(self) -> str: """ Returns a representation of the version Examples -------- - >>> VersionWithSource("1.2.3") - VersionWithSource("1.2.3") - """ - return f'{type(self).__name__}("{self._inner.as_str()}")' - - def __hash__(self) -> int: - """ - Computes the hash of this instance. - - Examples - -------- - >>> hash(VersionWithSource("1.2.3")) == hash(VersionWithSource("1.2.3")) - True - >>> hash(VersionWithSource("1.2.3")) == hash(VersionWithSource("3.2.1")) - False - >>> hash(VersionWithSource("1")) == hash(VersionWithSource("1.0.0")) - False - """ - return self._inner.__hash__() - - def __eq__(self, other: VersionWithSource) -> bool: - """ - Returns True if this instance represents the same version as `other`. - - Examples - -------- - >>> VersionWithSource("1.2.3") == VersionWithSource("1.2.3") - True - >>> VersionWithSource("3.2.1") == VersionWithSource("1.2.3") - False - >>> VersionWithSource("1") == VersionWithSource("1.0.0") - False - """ - return self._inner == other._inner - - def __ne__(self, other: VersionWithSource) -> bool: - """ - Returns True if this instance represents the same version as `other`. - - Examples - -------- - >>> VersionWithSource("1.2.3") != VersionWithSource("1.2.3") - False - >>> VersionWithSource("3.2.1") != VersionWithSource("1.2.3") - True - >>> VersionWithSource("1") != VersionWithSource("1.0.0") - True - """ - return self._inner != other._inner - - def __gt__(self, other: VersionWithSource) -> bool: - """ - Returns True if this instance should be ordered *after* `other`. - - Examples - -------- - >>> VersionWithSource("1.2.3") > VersionWithSource("1.2.3") - False - >>> VersionWithSource("1.2.4") > VersionWithSource("1.2.3") - True - >>> VersionWithSource("1.2.3.1") > VersionWithSource("1.2.3") - True - >>> VersionWithSource("3.2.1") > VersionWithSource("1.2.3") - True - >>> VersionWithSource("1") > VersionWithSource("1.0.0") - False - """ - return self._inner > other._inner - - def __lt__(self, other: VersionWithSource) -> bool: - """ - Returns True if this instance should be ordered *before* `other`. - - Examples - -------- - >>> VersionWithSource("1.2.3") < VersionWithSource("1.2.3") - False - >>> VersionWithSource("1.2.3") < VersionWithSource("1.2.4") - True - >>> VersionWithSource("1.2.3") < VersionWithSource("1.2.3.1") - True - >>> VersionWithSource("3.2.1") < VersionWithSource("1.2.3") - False - >>> VersionWithSource("1") < VersionWithSource("1.0.0") - True - """ - return self._inner < other._inner - - def __ge__(self, other: VersionWithSource) -> bool: - """ - Returns True if this instance should be ordered *after* or at the same location - as `other`. - - Examples - -------- - >>> VersionWithSource("1.2.3") >= VersionWithSource("1.2.3") - True - >>> VersionWithSource("1.2.4") >= VersionWithSource("1.2.3") - True - >>> VersionWithSource("1.2.3.1") >= VersionWithSource("1.2.3") - True - >>> VersionWithSource("3.2.1") >= VersionWithSource("1.2.3") - True - >>> VersionWithSource("1.2.3") >= VersionWithSource("3.2.1") - False - >>> VersionWithSource("1") >= VersionWithSource("1.0.0") - False - """ - return self._inner >= other._inner - - def __le__(self, other: VersionWithSource) -> bool: - """ - Returns True if this instance should be ordered *before* or at the same - location as `other`. - - Examples - -------- - >>> VersionWithSource("1.2.3") <= VersionWithSource("1.2.3") - True - >>> VersionWithSource("1.2.3") <= VersionWithSource("1.2.4") - True - >>> VersionWithSource("1.2.3") <= VersionWithSource("1.2.3.1") - True - >>> VersionWithSource("3.2.1") <= VersionWithSource("1.2.3") - False - >>> VersionWithSource("1") <= VersionWithSource("1.0.0") - True + >>> VersionWithSource("1.02.3") + VersionWithSource("1.02.3") """ - return self._inner <= other._inner + return f'{type(self).__name__}("{self.__str__()}")' From b9ca8e667c89193142c6996709c271e8989e5346 Mon Sep 17 00:00:00 2001 From: Wackyator Date: Thu, 7 Sep 2023 13:33:48 +0530 Subject: [PATCH 4/6] fix: change VersionWithSource constructor to accept a Union[str, Version] --- py-rattler/rattler/version/with_source.py | 26 ++++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/py-rattler/rattler/version/with_source.py b/py-rattler/rattler/version/with_source.py index 0f133f217..63842200b 100644 --- a/py-rattler/rattler/version/with_source.py +++ b/py-rattler/rattler/version/with_source.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Optional +from typing import Optional, Union from rattler.version import Version @@ -19,24 +19,20 @@ class VersionWithSource: which can be accessed by `source` property. """ - def __init__(self, source: str, version: Optional[Version] = None): - if not isinstance(source, str): - raise TypeError( - "VersionWithSource constructor received unsupported type " - f" {type(source).__name__!r} for the `source` parameter" - ) - - if version is None: - version = Version(source) - - if not isinstance(version, Version): + def __init__(self, version: Union[str, Version]): + if not isinstance(version, (str, Version)): raise TypeError( "VersionWithSource constructor received unsupported type " f" {type(version).__name__!r} for the `version` parameter" ) - self._source = source - self._version = version + if isinstance(version, str): + self._source = version + self._version = Version(version) + + if isinstance(version, Version): + self._source = str(version) + self._version = version @property def version(self) -> Version: @@ -48,7 +44,7 @@ def version(self) -> Version: >>> v = VersionWithSource("1.01") >>> v.version Version("1.1") - >>> v2 = VersionWithSource("1.01", v.version) + >>> v2 = VersionWithSource(v.version) >>> v2.version Version("1.1") """ From dfed77744301e444607af7ec5a0ce11c6bd3f707 Mon Sep 17 00:00:00 2001 From: Wackyator Date: Thu, 7 Sep 2023 13:39:51 +0530 Subject: [PATCH 5/6] fix: remove comparision from PyVersionWithSource --- py-rattler/src/version/with_source.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/py-rattler/src/version/with_source.rs b/py-rattler/src/version/with_source.rs index 2d9719a1e..dd9c3f5f6 100644 --- a/py-rattler/src/version/with_source.rs +++ b/py-rattler/src/version/with_source.rs @@ -3,7 +3,7 @@ use std::{ hash::{Hash, Hasher}, }; -use pyo3::{basic::CompareOp, pyclass, pymethods, PyClassInitializer}; +use pyo3::{pyclass, pymethods, PyClassInitializer}; use rattler_conda_types::VersionWithSource; use crate::version::PyVersion; @@ -51,9 +51,4 @@ impl PyVersionWithSource { self.inner.hash(&mut hasher); hasher.finish() } - - /// Performs comparison between this version and another. - pub fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { - op.matches(self.inner.cmp(&other.inner)) - } } From 9aa07a6882e16a47eb1635bb4a6123234536b19f Mon Sep 17 00:00:00 2001 From: Wackyator Date: Thu, 7 Sep 2023 17:24:40 +0530 Subject: [PATCH 6/6] fix: remove unused PyVersionWithSource --- py-rattler/rattler/version/with_source.py | 2 +- py-rattler/src/lib.rs | 3 +- py-rattler/src/version/mod.rs | 5 +-- py-rattler/src/version/with_source.rs | 54 ----------------------- 4 files changed, 3 insertions(+), 61 deletions(-) delete mode 100644 py-rattler/src/version/with_source.rs diff --git a/py-rattler/rattler/version/with_source.py b/py-rattler/rattler/version/with_source.py index 63842200b..eb62fd047 100644 --- a/py-rattler/rattler/version/with_source.py +++ b/py-rattler/rattler/version/with_source.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Optional, Union +from typing import Union from rattler.version import Version diff --git a/py-rattler/src/lib.rs b/py-rattler/src/lib.rs index 38ebf94f2..c73b6fd19 100644 --- a/py-rattler/src/lib.rs +++ b/py-rattler/src/lib.rs @@ -10,14 +10,13 @@ use error::{ use match_spec::PyMatchSpec; use nameless_match_spec::PyNamelessMatchSpec; use repo_data::package_record::PyPackageRecord; -use version::{PyVersion, PyVersionWithSource}; +use version::PyVersion; use pyo3::prelude::*; #[pymodule] fn rattler(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::().unwrap(); - m.add_class::().unwrap(); m.add_class::().unwrap(); m.add_class::().unwrap(); diff --git a/py-rattler/src/version/mod.rs b/py-rattler/src/version/mod.rs index f046aab91..922e876ad 100644 --- a/py-rattler/src/version/mod.rs +++ b/py-rattler/src/version/mod.rs @@ -1,5 +1,4 @@ mod component; -mod with_source; use crate::PyRattlerError; use component::PyComponent; @@ -11,9 +10,7 @@ use std::{ str::FromStr, }; -pub use with_source::PyVersionWithSource; - -#[pyclass(subclass)] +#[pyclass] #[repr(transparent)] #[derive(Clone)] pub struct PyVersion { diff --git a/py-rattler/src/version/with_source.rs b/py-rattler/src/version/with_source.rs deleted file mode 100644 index dd9c3f5f6..000000000 --- a/py-rattler/src/version/with_source.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, -}; - -use pyo3::{pyclass, pymethods, PyClassInitializer}; -use rattler_conda_types::VersionWithSource; - -use crate::version::PyVersion; - -#[pyclass(extends=PyVersion, subclass)] -#[repr(transparent)] -#[derive(Clone)] -pub struct PyVersionWithSource { - pub(crate) inner: VersionWithSource, -} - -impl From for PyVersionWithSource { - fn from(value: VersionWithSource) -> Self { - Self { inner: value } - } -} - -impl From for VersionWithSource { - fn from(value: PyVersionWithSource) -> Self { - value.inner - } -} - -#[pymethods] -impl PyVersionWithSource { - #[new] - pub fn new(version: &PyVersion, source: String) -> pyo3::PyClassInitializer { - PyClassInitializer::from(version.clone()) - .add_subclass(VersionWithSource::new(version.inner.clone(), source).into()) - } - - /// Returns the `PyVersion` from current object. - pub fn version(&self) -> PyVersion { - self.inner.version().clone().into() - } - - /// Returns a string representation of `PyVersionWithSource`. - pub fn as_str(&self) -> String { - self.inner.as_str().into_owned() - } - - /// Compute the hash of the version. - fn __hash__(&self) -> u64 { - let mut hasher = DefaultHasher::new(); - self.inner.hash(&mut hasher); - hasher.finish() - } -}