use crate::loader as core_loader;
use crate::sampler::RandomSampler;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::{PyTraverseError, PyVisit};
use crate::python::collator::PyCollator;
use crate::python::dataset::{Fetcher, PyDataset, len_py};
use crate::python::iterator::{PyDataloaderIter, PyIterInner};
use crate::python::pool::{run_attached, track};
use crate::python::sampler::PySampler;
type CorePyLoader = core_loader::DataLoader<PyDataset, PySampler, PyCollator>;
#[pyclass(name = "PyDataloader", module = "dataloader_rs")]
pub struct PyDataloader {
inner: CorePyLoader,
}
#[pymethods]
impl PyDataloader {
#[new]
#[pyo3(signature = (
dataset,
batch_size=1,
prefetch_depth=2,
shuffle=false,
sampler=None,
num_workers=0,
collate_fn=None,
drop_last=false,
seed=None,
items_in_parallel=1
))]
#[allow(clippy::too_many_arguments)]
fn new(
py: Python<'_>,
dataset: PyDataset,
batch_size: usize,
prefetch_depth: usize,
shuffle: bool,
sampler: Option<Py<PyAny>>,
num_workers: usize,
collate_fn: Option<Py<PyAny>>,
drop_last: bool,
seed: Option<u64>,
items_in_parallel: usize,
) -> PyResult<Self> {
if batch_size == 0 {
return Err(PyValueError::new_err("batch_size must be > 0"));
}
if prefetch_depth == 0 {
return Err(PyValueError::new_err("prefetch_depth must be > 0"));
}
if sampler.is_some() && shuffle {
return Err(PyValueError::new_err(
"sampler and shuffle are mutually exclusive",
));
}
if seed.is_some() && !shuffle {
return Err(PyValueError::new_err("seed only applies when shuffle=True"));
}
if items_in_parallel == 0 {
return Err(PyValueError::new_err("items_in_parallel must be > 0"));
}
if num_workers.max(1).checked_mul(items_in_parallel).is_none() {
return Err(PyValueError::new_err(
"num_workers * items_in_parallel is too large",
));
}
let sampler = match sampler {
Some(sampler) => PySampler::python(py, sampler)?,
None if shuffle => {
PySampler::random(seed.map_or_else(RandomSampler::from_entropy, RandomSampler::new))
}
None => PySampler::sequential(),
};
let inner = core_loader::DataLoader::builder(dataset)
.batch_size(batch_size)
.prefetch_depth(prefetch_depth)
.drop_last(drop_last)
.num_workers(num_workers)
.items_in_parallel(items_in_parallel)
.thread_main(run_attached)
.sampler(sampler)
.collator(PyCollator::new(collate_fn))
.build();
if let Some(pool) = inner.pool_handle() {
track(pool);
}
Ok(Self { inner })
}
fn __iter__(slf: Py<Self>, py: Python<'_>) -> PyResult<PyDataloaderIter> {
let mut loader = slf.borrow_mut(py);
let core = &mut loader.inner;
len_py(core.dataset(), py)?;
let chunks = core.epoch_chunks();
if let Some(err) = core.sampler_mut().take_error() {
return Err(err);
}
let inner = if core.has_pool() {
let epoch = core.iter_chunks(chunks);
if let Some(pool) = core.pool_handle() {
track(pool);
}
PyIterInner::Threaded(epoch)
} else {
PyIterInner::Direct {
chunks: chunks.into_iter(),
fetcher: Fetcher::new(core.dataset(), py)?,
collator: core.collator().clone_ref(py),
}
};
drop(loader);
Ok(PyDataloaderIter { _owner: slf, inner })
}
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(self.inner.dataset())?;
visit.call(self.inner.sampler().python_sampler())?;
visit.call(self.inner.collator().collate_fn())
}
fn __len__(&self, py: Python<'_>) -> PyResult<usize> {
match self.inner.sampler().python_sampler() {
Some(sampler) => Ok(self.inner.num_batches(sampler.bind(py).len()?)),
None => {
len_py(self.inner.dataset(), py)?;
Ok(self.inner.batch_len())
}
}
}
}