use crate::{dataset::Dataset, error::Result};
use pyo3::exceptions::{PyNotImplementedError, PyValueError};
use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyTuple};
#[pyclass(frozen, subclass)]
pub struct PyDatasetBase;
#[pymethods]
impl PyDatasetBase {
#[new]
#[pyo3(signature = (*_args, **_kwargs))]
fn new(_args: &Bound<'_, PyTuple>, _kwargs: Option<&Bound<'_, PyDict>>) -> Self {
Self
}
fn __len__(&self) -> PyResult<usize> {
Err(PyNotImplementedError::new_err(
"PyDataset.__len__ must be implemented by subclasses",
))
}
fn __getitem__(&self, _index: usize) -> PyResult<Py<PyAny>> {
Err(PyNotImplementedError::new_err(
"PyDataset.__getitem__ must be implemented by subclasses",
))
}
}
pub(crate) type PyDataset = Py<PyDatasetBase>;
pub(crate) fn len_py(dataset: &PyDataset, py: Python<'_>) -> PyResult<usize> {
dataset
.call_method0(py, intern!(py, "__len__"))?
.extract(py)
}
pub(crate) fn get_item_py(
dataset: &PyDataset,
py: Python<'_>,
index: usize,
) -> PyResult<Py<PyAny>> {
dataset.call_method1(py, intern!(py, "__getitem__"), (index,))
}
pub(crate) enum Fetcher {
Batched(Py<PyAny>),
PerItem(Py<PyAny>),
}
impl Fetcher {
pub(crate) fn new(dataset: &PyDataset, py: Python<'_>) -> PyResult<Self> {
let dataset = dataset.bind(py);
Ok(match dataset.getattr_opt(intern!(py, "__getitems__"))? {
Some(getitems) if getitems.is_truthy()? => Self::Batched(getitems.unbind()),
_ => Self::PerItem(dataset.getattr(intern!(py, "__getitem__"))?.unbind()),
})
}
pub(crate) fn method(&self) -> &Py<PyAny> {
match self {
Self::Batched(method) | Self::PerItem(method) => method,
}
}
pub(crate) fn fetch(&self, py: Python<'_>, indices: &[usize]) -> PyResult<Vec<Py<PyAny>>> {
match self {
Self::PerItem(getitem) => indices.iter().map(|&i| getitem.call1(py, (i,))).collect(),
Self::Batched(getitems) => {
let samples = getitems.bind(py).call1((PyList::new(py, indices)?,))?;
let samples = samples
.try_iter()?
.map(|sample| sample.map(Bound::unbind))
.collect::<PyResult<Vec<_>>>()?;
if samples.len() != indices.len() {
return Err(PyValueError::new_err(format!(
"__getitems__ returned {} samples for {} indices",
samples.len(),
indices.len()
)));
}
Ok(samples)
}
}
}
}
impl Dataset for PyDataset {
type Item = Py<PyAny>;
fn get(&self, index: usize) -> Result<Self::Item> {
Python::attach(|py| get_item_py(self, py, index).map_err(|e| e.into()))
}
fn get_batch(&self, indices: &[usize]) -> Result<Vec<Py<PyAny>>> {
Python::attach(|py| Fetcher::new(self, py)?.fetch(py, indices)).map_err(Into::into)
}
fn len(&self) -> usize {
Python::attach(|py| {
len_py(self, py).unwrap_or_else(|e| panic!("PyDataset.__len__ failed: {e}"))
})
}
}