Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions kernels-data/bindings/python/kernels_data.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ __all__ = [
"DigestAlgorithm",
"GitHash",
"KernelBuilderVersion",
"KernelDependency",
"KernelName",
"KernelVersion",
"Metadata",
"Digest",
"DigestViolation",
Expand Down Expand Up @@ -280,6 +282,48 @@ class DigestValidationError(Exception):
"""The individual digest violations."""
...

class KernelVersion:
"""A kernel version: either a numeric version or a git revision string."""

@final
class Version(KernelVersion):
"""A numeric kernel version."""

version: int
__match_args__ = ("version",)
def __new__(cls, version: int) -> "KernelVersion.Version": ...

@final
class Revision(KernelVersion):
"""A git revision (e.g. commit SHA or branch/tag name)."""

revision: str
__match_args__ = ("revision",)
def __new__(cls, revision: str) -> "KernelVersion.Revision": ...

def __repr__(self) -> str: ...
def __eq__(self, value: object, /) -> bool: ...
def __hash__(self) -> int: ...

@final
class KernelDependency:
"""A dependency on another kernel."""

def __new__(cls, repo_id: str, version: KernelVersion) -> "KernelDependency": ...
@property
def repo_id(self) -> str:
"""Identifier of the kernel repository this dependency points to."""
...

@property
def version(self) -> KernelVersion:
"""Version specifier for the dependency."""
...

def __repr__(self) -> str: ...
def __eq__(self, value: object, /) -> bool: ...
def __hash__(self) -> int: ...

@final
class Metadata:
"""Parsed `metadata.json` for a kernel build variant."""
Expand Down Expand Up @@ -317,6 +361,8 @@ class Metadata:
@property
def python_depends(self) -> list[str]: ...
@property
def kernel_depends(self) -> list[KernelDependency]: ...
@property
def backend(self) -> BackendInfo: ...
@property
def digest(self) -> Optional[Digest]: ...
Expand Down
86 changes: 84 additions & 2 deletions kernels-data/bindings/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::io::BufReader;
use std::path::PathBuf;
use std::str::FromStr;

use kernels_data::config::{Backend, KernelName};
use kernels_data::config::{Backend, KernelDependency, KernelName, KernelVersion};
use kernels_data::digest::{Digest, DigestAlgorithm, DigestViolation};
use kernels_data::metadata::{BackendInfo, GitHash, KernelBuilderVersion, Metadata, Provenance};
use kernels_data::version::Version;
Expand Down Expand Up @@ -312,6 +312,78 @@ impl PyProvenance {
}
}

/// A kernel version: either a numeric version or a git revision string.
#[pyclass(name = "KernelVersion", frozen, eq, hash)]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum PyKernelVersion {
Version { version: usize },
Revision { revision: String },
}

impl From<KernelVersion> for PyKernelVersion {
fn from(v: KernelVersion) -> Self {
match v {
KernelVersion::Version(n) => Self::Version { version: n },
KernelVersion::Revision(s) => Self::Revision { revision: s },
}
}
}

#[pymethods]
impl PyKernelVersion {
fn __repr__(&self) -> String {
match self {
Self::Version { version } => format!("KernelVersion.Version(version={version})"),
Self::Revision { revision } => {
format!("KernelVersion.Revision(revision={revision:?})")
}
}
}
}

/// A dependency on another kernel.
#[pyclass(name = "KernelDependency", frozen, eq, hash)]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct PyKernelDependency {
repo_id: String,
version: PyKernelVersion,
}

impl From<KernelDependency> for PyKernelDependency {
fn from(d: KernelDependency) -> Self {
Self {
repo_id: d.repo_id,
version: d.version.into(),
}
}
}

#[pymethods]
impl PyKernelDependency {
#[new]
fn new(repo_id: String, version: PyKernelVersion) -> Self {
Self { repo_id, version }
}

#[getter]
fn repo_id(&self) -> &str {
&self.repo_id
}

#[getter]
fn version(&self) -> PyKernelVersion {
self.version.clone()
}

fn __repr__(&self) -> String {
format!(
"KernelDependency(repo_id={:?}, version={})",
self.repo_id,
self.version.__repr__()
)
}
}

/// Parsed `metadata.json` for a kernel build variant.
#[pyclass(name = "Metadata", frozen)]
#[derive(Clone, Debug)]
Expand All @@ -323,6 +395,7 @@ struct PyMetadata {
upstream: Option<String>,
source: Option<String>,
python_depends: Vec<String>,
kernel_depends: Vec<PyKernelDependency>,
backend: PyBackendInfo,
digest: Option<PyDigest>,
provenance: Option<PyProvenance>,
Expand All @@ -338,6 +411,7 @@ impl From<Metadata> for PyMetadata {
upstream: m.upstream.map(|u| u.as_url().to_string()),
source: m.source.map(|u| u.as_url().to_string()),
python_depends: m.python_depends,
kernel_depends: m.kernel_depends.into_iter().map(Into::into).collect(),
backend: m.backend.into(),
digest: m.digest.map(Into::into),
provenance: m.provenance.map(Into::into),
Expand Down Expand Up @@ -409,6 +483,11 @@ impl PyMetadata {
&self.python_depends
}

#[getter]
fn kernel_depends(&self) -> Vec<PyKernelDependency> {
self.kernel_depends.clone()
}

#[getter]
fn backend(&self) -> PyBackendInfo {
self.backend.clone()
Expand All @@ -426,14 +505,15 @@ impl PyMetadata {

fn __repr__(&self) -> String {
format!(
"Metadata(id={}, name={:?}, version={:?}, license={:?}, upstream={:?}, source={:?}, python_depends={:?}, backend={}, digest={}, provenance={})",
"Metadata(id={}, name={:?}, version={:?}, license={:?}, upstream={:?}, source={:?}, python_depends={:?}, kernel_depends={:?}, backend={}, digest={}, provenance={})",
self.id,
self.name,
self.version,
self.license,
self.upstream,
self.source,
self.python_depends,
self.kernel_depends,
self.backend.__repr__(),
self.digest
.as_ref()
Expand Down Expand Up @@ -668,6 +748,8 @@ fn kernels_data_py(m: &PyBound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyGitHash>()?;
m.add_class::<PyKernelBuilderVersion>()?;
m.add_class::<PyKernelName>()?;
m.add_class::<PyKernelVersion>()?;
m.add_class::<PyKernelDependency>()?;
m.add_class::<PyMetadata>()?;
m.add_class::<PyVersion>()?;
m.add_class::<PyDigestAlgorithm>()?;
Expand Down
1 change: 1 addition & 0 deletions kernels-data/bindings/python/stubtest-allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ kernels_data\.kernels_data
# `@disjoint_base` marker only lives in `typing_extensions`, which this
# dependency-free extension package does not pull in, so the stub omits it.
kernels_data\.DigestViolation
kernels_data\.KernelVersion

# `DigestValidationError.violations` is attached as a per-instance attribute by
# the `Digest.validate` binding (via `setattr`), so it is not visible on the
Expand Down
47 changes: 46 additions & 1 deletion kernels-data/bindings/python/tests/test_kernels_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

import pytest

from kernels_data import Backend, KernelName, Metadata, Version
from kernels_data import (
Backend,
KernelDependency,
KernelName,
KernelVersion,
Metadata,
Version,
)


def _write_metadata(path, **fields):
Expand Down Expand Up @@ -200,3 +207,41 @@ def test_metadata_load(tmp_path):
def test_metadata_load_missing_file(tmp_path):
with pytest.raises(OSError):
Metadata.read_from_file(tmp_path / "does-not-exist.json")


def test_kernel_version_eq_and_hash():
assert KernelVersion.Version(1) == KernelVersion.Version(1)
assert KernelVersion.Revision("abc") == KernelVersion.Revision("abc")
assert KernelVersion.Version(1) != KernelVersion.Version(2)
assert KernelVersion.Version(1) != KernelVersion.Revision("1")
assert hash(KernelVersion.Version(1)) == hash(KernelVersion.Version(1))
assert hash(KernelVersion.Revision("abc")) == hash(KernelVersion.Revision("abc"))
assert {KernelVersion.Version(1), KernelVersion.Version(1)} == {
KernelVersion.Version(1)
}


def test_kernel_dependency_eq_and_hash():
a = KernelDependency(repo_id="foo/bar", version=KernelVersion.Version(1))
b = KernelDependency(repo_id="foo/bar", version=KernelVersion.Version(1))
c = KernelDependency(repo_id="foo/bar", version=KernelVersion.Revision("deadbeef"))
d = KernelDependency(repo_id="other/repo", version=KernelVersion.Version(1))

assert a == b
assert hash(a) == hash(b)
assert a != c
assert a != d

# Distinct instances with equal content collapse in sets/dicts.
assert {a, b, c, d} == {a, c, d}

cache: dict[KernelDependency, int] = {a: 1}
assert cache[b] == 1


def test_kernel_dependency_is_immutable():
dep = KernelDependency(repo_id="foo/bar", version=KernelVersion.Version(1))
with pytest.raises(AttributeError):
dep.repo_id = "other/repo"
with pytest.raises(AttributeError):
dep.version = KernelVersion.Version(2)
Loading
Loading