Skip to content

Commit

Permalink
feat: expose *Record.noarch in Python bindings (#635)
Browse files Browse the repository at this point in the history
Closes #630

I barely know what I'm doing so please feel free to suggest a different
API or even commit directly to my branch :) Thanks!
  • Loading branch information
jaimergp authored May 2, 2024
1 parent b88c3e9 commit e1eea9a
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 3 deletions.
5 changes: 5 additions & 0 deletions crates/rattler_conda_types/src/no_arch_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ impl NoArchType {
self.kind() == Some(NoArchKind::Python)
}

/// Returns true if this instance is a Generic noarch type
pub fn is_generic(&self) -> bool {
self.kind() == Some(NoArchKind::Generic)
}

/// Constructs a Python noarch instance.
pub fn python() -> Self {
Self(Some(RawNoArchType::Python))
Expand Down
140 changes: 140 additions & 0 deletions py-rattler/rattler/package/no_arch_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
from __future__ import annotations
from typing import Optional

from rattler.rattler import PyNoArchType


class NoArchType:
_noarch: PyNoArchType

def __init__(self, noarch: Optional[str] = None) -> None:
if noarch is None:
self._noarch = PyNoArchType.none()
self._source = None
elif noarch == "python":
self._noarch = PyNoArchType.python()
self._source = "python"
elif noarch == "generic":
self._noarch = PyNoArchType.generic()
self._source = "generic"
else:
raise ValueError(
"NoArchType constructor received unsupported value " f"{noarch} for the `noarch` parameter"
)

@classmethod
def _from_py_no_arch_type(cls, py_no_arch_type: PyNoArchType) -> NoArchType:
"""Construct Rattler NoArchType from FFI PyNoArchType object."""
no_arch_type = cls.__new__(cls)
no_arch_type._noarch = py_no_arch_type
no_arch_type._source = py_no_arch_type
return no_arch_type

@property
def generic(self) -> bool:
"""
Return whether this NoArchType is 'generic'
>>> NoArchType('generic').generic
True
>>> NoArchType('generic').python
False
>>>
"""
return self._noarch.is_generic

@property
def none(self) -> bool:
"""
Return whether this NoArchType is set
>>> NoArchType(None).none
True
>>> NoArchType(None).python
False
>>>
"""
return self._noarch.is_none

@property
def python(self) -> bool:
"""
Return whether this NoArchType is 'python'
>>> NoArchType('python').python
True
>>> NoArchType('python').generic
False
>>>
"""
return self._noarch.is_python

def __hash__(self) -> int:
"""
Computes the hash of this instance.
Examples
--------
```python
>>> hash(NoArchType("python")) == hash(NoArchType("python"))
True
>>> hash(NoArchType("python")) == hash(NoArchType("generic"))
False
>>>
```
"""
return self._noarch.__hash__()

def __eq__(self, other: object) -> bool:
"""
Returns True if this instance represents the same NoArchType as `other`.
Examples
--------
```python
>>> NoArchType("python") == NoArchType("generic")
False
>>> NoArchType("python") == NoArchType("python")
True
>>> NoArchType("generic") == NoArchType("generic")
True
>>> NoArchType("python") == "python"
False
>>>
```
"""
if not isinstance(other, NoArchType):
return False

return self._noarch == other._noarch

def __ne__(self, other: object) -> bool:
"""
Returns True if this instance does not represents the same NoArchType as `other`.
Examples
--------
```python
>>> NoArchType("python") != NoArchType("python")
False
>>> NoArchType("python") != "python"
True
>>>
```
"""
if not isinstance(other, NoArchType):
return True

return self._noarch != other._noarch

def __repr__(self) -> str:
"""
Returns a representation of the NoArchType.
Examples
--------
```python
>>> p = NoArchType("python")
>>> p
NoArchType("python")
>>>
```
"""
return f'NoArchType("{self._source}")'
30 changes: 29 additions & 1 deletion py-rattler/rattler/repo_data/package_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import os
from typing import List, Optional
from rattler.match_spec.match_spec import MatchSpec
from rattler.package.no_arch_type import NoArchType
from rattler.package.package_name import PackageName

from rattler.rattler import PyRecord
from rattler.version.version import Version

Expand Down Expand Up @@ -287,6 +287,34 @@ def name(self) -> PackageName:
"""
return PackageName._from_py_package_name(self._record.name)

@property
def noarch(self) -> Optional[str]:
"""
The noarch type of the package.
Examples
--------
```python
>>> from rattler import PrefixRecord
>>> record = PrefixRecord.from_path(
... "../test-data/conda-meta/libsqlite-3.40.0-hcfcfb64_0.json"
... )
>>> record.noarch
>>> record = PrefixRecord.from_path(
... "../test-data/conda-meta/pip-23.0-pyhd8ed1ab_0.json"
... )
>>> record.noarch
'python'
>>>
```
"""
noarchtype = NoArchType._from_py_no_arch_type(self._record.noarch)
if noarchtype.python:
return "python"
if noarchtype.generic:
return "generic"
return None

@property
def platform(self) -> Optional[str]:
"""
Expand Down
4 changes: 4 additions & 0 deletions py-rattler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod match_spec;
mod meta;
mod nameless_match_spec;
mod networking;
mod no_arch_type;
mod package_name;
mod paths_json;
mod platform;
Expand Down Expand Up @@ -41,6 +42,7 @@ use lock::{
use match_spec::PyMatchSpec;
use nameless_match_spec::PyNamelessMatchSpec;
use networking::{authenticated_client::PyAuthenticatedClient, py_fetch_repo_data};
use no_arch_type::PyNoArchType;
use package_name::PyPackageName;
use paths_json::{PyFileMode, PyPathType, PyPathsEntry, PyPathsJson, PyPrefixPlaceholder};
use prefix_paths::{PyPrefixPathType, PyPrefixPaths, PyPrefixPathsEntry};
Expand Down Expand Up @@ -96,6 +98,8 @@ fn rattler(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<PyPrefixPathType>().unwrap();
m.add_class::<PyPrefixPaths>().unwrap();

m.add_class::<PyNoArchType>().unwrap();

m.add_class::<PyLockFile>().unwrap();
m.add_class::<PyEnvironment>().unwrap();
m.add_class::<PyLockChannel>().unwrap();
Expand Down
73 changes: 73 additions & 0 deletions py-rattler/src/no_arch_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};

use pyo3::{basic::CompareOp, pyclass, pymethods};
use rattler_conda_types::NoArchType;

#[pyclass]
#[derive(Clone)]
pub struct PyNoArchType {
pub inner: NoArchType,
}

impl From<NoArchType> for PyNoArchType {
fn from(value: NoArchType) -> Self {
Self { inner: value }
}
}

impl From<PyNoArchType> for NoArchType {
fn from(value: PyNoArchType) -> Self {
value.inner
}
}

#[pymethods]
impl PyNoArchType {
/// Constructs a new `NoArchType` of type `python`.
#[staticmethod]
pub fn python() -> Self {
NoArchType::python().into()
}

#[getter]
pub fn is_python(&self) -> bool {
self.inner.is_python()
}

/// Constructs a new `NoArchType` of type `generic`.
#[staticmethod]
pub fn generic() -> Self {
NoArchType::generic().into()
}

#[getter]
pub fn is_generic(&self) -> bool {
self.inner.is_generic()
}

/// Constructs a new `NoArchType` of type `none`.
#[staticmethod]
pub fn none() -> Self {
NoArchType::none().into()
}

#[getter]
pub fn is_none(&self) -> bool {
self.inner.is_none()
}

/// Compute the hash of the noarch type.
fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.inner.hash(&mut hasher);
hasher.finish()
}

/// Performs comparison between this noarch type and another.
pub fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
op.matches(self.inner.cmp(&other.inner))
}
}
10 changes: 8 additions & 2 deletions py-rattler/src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use rattler_conda_types::{
use rattler_digest::{parse_digest_from_hex, Md5, Sha256};

use crate::{
error::PyRattlerError, package_name::PyPackageName, prefix_paths::PyPrefixPaths,
version::PyVersion,
error::PyRattlerError, no_arch_type::PyNoArchType, package_name::PyPackageName,
prefix_paths::PyPrefixPaths, version::PyVersion,
};

/// Python bindings for `PrefixRecord`, `RepoDataRecord`, `PackageRecord`.
Expand Down Expand Up @@ -198,6 +198,12 @@ impl PyRecord {
self.as_package_record().subdir.clone()
}

/// The noarch type this package implements, if any.
#[getter]
pub fn noarch(&self) -> PyNoArchType {
self.as_package_record().noarch.into()
}

/// The date this entry was created.
#[getter]
pub fn timestamp(&self) -> Option<i64> {
Expand Down
2 changes: 2 additions & 0 deletions py-rattler/tests/unit/test_prefix_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ def test_load_prefix_record() -> None:
assert r.features is None
assert r.file_name == "tk-8.6.12-h8ffe710_0.tar.bz2"
assert len(r.files) == len(r.paths_data.paths) == 1099
assert r.subdir == "win-64"
assert r.noarch is None
paths = r.paths_data
assert isinstance(paths, PrefixPaths)
paths_with_placeholder = 0
Expand Down

0 comments on commit e1eea9a

Please sign in to comment.