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
1 change: 1 addition & 0 deletions python_example/maketrees/python/maketrees/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from ._maketrees import maketrees
from ._maketrees import raise_error
from ._maketrees import clear_shared_tables


def make_treeseq_with_metadata() -> tskit.TreeSequence:
Expand Down
12 changes: 12 additions & 0 deletions python_example/maketrees/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,16 @@ mod maketrees {
let holder = tskit2tskit::SharedTableCollection::new(py, f64::NAN)?;
Ok(holder.into_python_tables(py)?)
}

#[pyfunction]
fn clear_shared_tables(py: Python<'_>, pytables: Py<PyAny>) -> PyResult<()> {
let mut holder =
unsafe { tskit2tskit::SharedTableCollection::new_from_tables(py, pytables) }?;
unsafe {
holder.with_mut_tables(|tables| {
tables.clear(0).unwrap();
})
};
Ok(())
}
}
8 changes: 8 additions & 0 deletions python_example/maketrees/tests/test_maketrees.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ def test_metadata():
assert m['data'] == "I am a mutation"


def test_mutable_sharing_of_tables():
tables = tskit.TableCollection(100)
tables.nodes.add_row(0, 0.0, -1, -1)
assert tables.nodes.num_rows == 1
maketrees.clear_shared_tables(tables)
assert tables.nodes.num_rows == 0


def test_raise():
try:
_ = maketrees.raise_error()
Expand Down
34 changes: 34 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,40 @@ impl SharedTableCollection {
})
}

/// Constructor to borrow data from a table collection from tskit-python
///
/// # Parameters
///
/// * `pytables`: a `tskit.TableCollection` from `tskit-python`
///
/// # Safety
///
/// * `tskit-python` and `tskit-rust` must share the same memory layout
/// for `tsk_table_collection_t`.
pub unsafe fn new_from_tables<'py>(
py: Python<'py>,
pytables: Py<PyAny>,
) -> Result<Self, Error> {
let ll_tables = pytables.getattr(py, "_ll_tables")?;
assert!(!ll_tables.as_ptr().is_null());
// SAFETY: ll_tables is a valid pointer to the low-level type.
// (Else we'd have gotten an error above.)
// The pointer is not NULL and it MUST have been initialized
// by tskit-python.
let ptr = unsafe { read_tsk_ptr(ll_tables.as_ptr()) };
assert!(!ptr.is_null());
// SAFETY: ptr is not NULL
assert!(!unsafe { *ptr }.is_null());
// SAFETY: nothing is NULL
// The pointee has been initialized w/o error over in Python
let tables =
unsafe { tskit::TableCollection::new_from_raw(std::ptr::NonNull::new(*ptr).unwrap()) }?;
Ok(Self {
tables: Some(tables),
pytables: Some(pytables),
})
}

unsafe fn as_rust(&self) -> &tskit::TableCollection {
self.tables.as_ref().unwrap()
}
Expand Down
21 changes: 21 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,24 @@ fn test_mutable_holder_into_tree_sequence() {
let _pyts = holder.into_python_tree_sequence(py).unwrap();
})
}

#[test]
fn test_borrow_tables_from_python() {
Python::attach(|py| {
py.run(cr"import tskit", None, None).unwrap();
py.run(cr"tables = tskit.TableCollection(100) ", None, None)
.unwrap();
let pytables = py
.eval(c"tables", None, None)
.map_err(|e| {
e.print_and_set_sys_last_vars(py);
})
.unwrap()
.unbind();
let mut holder =
unsafe { tskit2tskit::SharedTableCollection::new_from_tables(py, pytables) }.unwrap();
unsafe { holder.with_tables(|tables| assert_eq!(tables.sequence_length(), 100.)) };
unsafe { holder.with_mut_tables(|tables| tables.add_node(0, 0., -1, -1).unwrap()) };
py.run(cr"assert tables.nodes.num_rows == 1", None, None).unwrap();
});
}
Loading