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
93 changes: 65 additions & 28 deletions cachebox/_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ __version__: typing.Final[str]
KT = typing.TypeVar("KT", bound=typing.Hashable)
VT = typing.TypeVar("VT")
DT = typing.TypeVar("DT")
T_co = typing.TypeVar("T_co", covariant=True)

_IterableType: typing.TypeAlias = (
typing.Dict[KT, VT]
Expand All @@ -17,6 +18,42 @@ _IterableType: typing.TypeAlias = (
| typing.Iterable[typing.Tuple[KT, VT]]
)

class CacheIterator(typing.Iterator[T_co]):
"""
What ``keys()``, ``values()`` and ``items()`` return.

This is a one-shot iterator, not a ``dict`` view: it walks the cache once
and is empty after that. It knows how many items it still has to yield, so
``len()`` and ``bool()`` work on it, but it does not support ``in`` or set
operations the way ``dict.keys()`` does.

Warning:
Do not modify the cache while one of these is alive. Every method
raises ``RuntimeError`` if the cache changed.
"""

def __next__(self) -> T_co: ...
def __len__(self) -> int:
"""
Returns how many items are left to yield.

For ``TTLCache`` and ``VTTLCache`` this skips expired entries, which
takes O(n).

Returns:
The number of items left.
"""
...

def __bool__(self) -> bool:
"""
Returns whether any item is left to yield.

Returns:
``True`` if at least one item is left.
"""
...

class BaseCacheImpl(typing.Generic[KT, VT]):
"""
Base implementation for cache classes.
Expand Down Expand Up @@ -221,10 +258,10 @@ class BaseCacheImpl(typing.Generic[KT, VT]):

def __eq__(self, other: typing.Any) -> bool: ...
def __ne__(self, other: typing.Any) -> bool: ...
def items(self) -> typing.Iterable[typing.Tuple[KT, VT]]: ...
def values(self) -> typing.Iterable[VT]: ...
def keys(self) -> typing.Iterable[KT]: ...
def __iter__(self) -> typing.Iterator[KT]: ...
def items(self) -> CacheIterator[typing.Tuple[KT, VT]]: ...
def values(self) -> CacheIterator[VT]: ...
def keys(self) -> CacheIterator[KT]: ...
def __iter__(self) -> CacheIterator[KT]: ...
def copy(self) -> typing.Self: ...
def __copy__(self) -> typing.Self: ...
def __getstate__(self) -> object: ...
Expand Down Expand Up @@ -410,7 +447,7 @@ class Cache(BaseCacheImpl[KT, VT]):
"""
...

def items(self) -> typing.Iterable[typing.Tuple[KT, VT]]:
def items(self) -> CacheIterator[typing.Tuple[KT, VT]]:
"""
Returns an iterable of the cache's ``(key, value)`` pairs.

Expand All @@ -422,7 +459,7 @@ class Cache(BaseCacheImpl[KT, VT]):
"""
...

def keys(self) -> typing.Iterable[KT]:
def keys(self) -> CacheIterator[KT]:
"""
Returns an iterable of the cache's keys.

Expand All @@ -434,7 +471,7 @@ class Cache(BaseCacheImpl[KT, VT]):
"""
...

def values(self) -> typing.Iterable[VT]:
def values(self) -> CacheIterator[VT]:
"""
Returns an iterable of the cache's values.

Expand Down Expand Up @@ -593,7 +630,7 @@ class FIFOCache(BaseCacheImpl[KT, VT]):
"""
...

def items(self) -> typing.Iterable[typing.Tuple[KT, VT]]:
def items(self) -> CacheIterator[typing.Tuple[KT, VT]]:
"""
Returns an ordered iterable of the cache's ``(key, value)`` pairs.

Expand All @@ -605,7 +642,7 @@ class FIFOCache(BaseCacheImpl[KT, VT]):
"""
...

def keys(self) -> typing.Iterable[KT]:
def keys(self) -> CacheIterator[KT]:
"""
Returns an ordered iterable of the cache's keys.

Expand All @@ -617,7 +654,7 @@ class FIFOCache(BaseCacheImpl[KT, VT]):
"""
...

def values(self) -> typing.Iterable[VT]:
def values(self) -> CacheIterator[VT]:
"""
Returns an ordered iterable of the cache's values.

Expand Down Expand Up @@ -808,7 +845,7 @@ class RRCache(BaseCacheImpl[KT, VT]):
"""
...

def items(self) -> typing.Iterable[typing.Tuple[KT, VT]]:
def items(self) -> CacheIterator[typing.Tuple[KT, VT]]:
"""
Returns an iterable of the cache's ``(key, value)`` pairs.

Expand All @@ -820,7 +857,7 @@ class RRCache(BaseCacheImpl[KT, VT]):
"""
...

def keys(self) -> typing.Iterable[KT]:
def keys(self) -> CacheIterator[KT]:
"""
Returns an iterable of the cache's keys.

Expand All @@ -832,7 +869,7 @@ class RRCache(BaseCacheImpl[KT, VT]):
"""
...

def values(self) -> typing.Iterable[VT]:
def values(self) -> CacheIterator[VT]:
"""
Returns an iterable of the cache's values.

Expand Down Expand Up @@ -1016,7 +1053,7 @@ class LRUCache(BaseCacheImpl[KT, VT]):
"""
...

def items(self) -> typing.Iterable[typing.Tuple[KT, VT]]:
def items(self) -> CacheIterator[typing.Tuple[KT, VT]]:
"""
Returns an ordered iterable of the cache's ``(key, value)`` pairs.

Expand All @@ -1028,7 +1065,7 @@ class LRUCache(BaseCacheImpl[KT, VT]):
"""
...

def keys(self) -> typing.Iterable[KT]:
def keys(self) -> CacheIterator[KT]:
"""
Returns an ordered iterable of the cache's keys.

Expand All @@ -1040,7 +1077,7 @@ class LRUCache(BaseCacheImpl[KT, VT]):
"""
...

def values(self) -> typing.Iterable[VT]:
def values(self) -> CacheIterator[VT]:
"""
Returns an ordered iterable of the cache's values.

Expand Down Expand Up @@ -1265,7 +1302,7 @@ class LFUCache(BaseCacheImpl[KT, VT]):
"""
...

def items(self) -> typing.Iterable[typing.Tuple[KT, VT]]:
def items(self) -> CacheIterator[typing.Tuple[KT, VT]]:
"""
Returns an ordered iterable of the cache's ``(key, value)`` pairs.

Expand All @@ -1277,7 +1314,7 @@ class LFUCache(BaseCacheImpl[KT, VT]):
"""
...

def keys(self) -> typing.Iterable[KT]:
def keys(self) -> CacheIterator[KT]:
"""
Returns an ordered iterable of the cache's keys.

Expand All @@ -1289,7 +1326,7 @@ class LFUCache(BaseCacheImpl[KT, VT]):
"""
...

def values(self) -> typing.Iterable[VT]:
def values(self) -> CacheIterator[VT]:
"""
Returns an ordered iterable of the cache's values.

Expand All @@ -1301,7 +1338,7 @@ class LFUCache(BaseCacheImpl[KT, VT]):
"""
...

def items_with_frequency(self) -> typing.Iterable[typing.Tuple[KT, VT, int]]:
def items_with_frequency(self) -> CacheIterator[typing.Tuple[KT, VT, int]]:
"""
Returns an ordered iterable of the cache's ``(key, value)`` pairs with their
frequency counter.
Expand Down Expand Up @@ -1473,7 +1510,7 @@ class TTLCache(BaseCacheImpl[KT, VT]):
"""
...

def items(self) -> typing.Iterable[typing.Tuple[KT, VT]]:
def items(self) -> CacheIterator[typing.Tuple[KT, VT]]:
"""
Returns an ordered iterable of the cache's ``(key, value)`` pairs.

Expand All @@ -1485,7 +1522,7 @@ class TTLCache(BaseCacheImpl[KT, VT]):
"""
...

def keys(self) -> typing.Iterable[KT]:
def keys(self) -> CacheIterator[KT]:
"""
Returns an ordered iterable of the cache's keys.

Expand All @@ -1497,7 +1534,7 @@ class TTLCache(BaseCacheImpl[KT, VT]):
"""
...

def values(self) -> typing.Iterable[VT]:
def values(self) -> CacheIterator[VT]:
"""
Returns an ordered iterable of the cache's values.

Expand Down Expand Up @@ -1599,7 +1636,7 @@ class TTLCache(BaseCacheImpl[KT, VT]):
"""
...

def items_with_expire(self) -> typing.Iterable[typing.Tuple[KT, VT, float]]:
def items_with_expire(self) -> CacheIterator[typing.Tuple[KT, VT, float]]:
"""
Returns an ordered iterable of items with their remaining TTL.

Expand Down Expand Up @@ -1741,7 +1778,7 @@ class VTTLCache(BaseCacheImpl[KT, VT]):
KeyError: If the cache is empty.
"""

def items(self) -> typing.Iterable[typing.Tuple[KT, VT]]:
def items(self) -> CacheIterator[typing.Tuple[KT, VT]]:
"""
Returns an ordered iterable of the cache's ``(key, value)`` pairs.

Expand All @@ -1753,7 +1790,7 @@ class VTTLCache(BaseCacheImpl[KT, VT]):
"""
...

def keys(self) -> typing.Iterable[KT]:
def keys(self) -> CacheIterator[KT]:
"""
Returns an ordered iterable of the cache's keys.

Expand All @@ -1765,7 +1802,7 @@ class VTTLCache(BaseCacheImpl[KT, VT]):
"""
...

def values(self) -> typing.Iterable[VT]:
def values(self) -> CacheIterator[VT]:
"""
Returns an ordered iterable of the cache's values.

Expand Down Expand Up @@ -1838,7 +1875,7 @@ class VTTLCache(BaseCacheImpl[KT, VT]):
"""
...

def items_with_expire(self) -> typing.Iterable[typing.Tuple[KT, VT, float | None]]:
def items_with_expire(self) -> CacheIterator[typing.Tuple[KT, VT, float | None]]:
"""
Returns an ordered iterable of items with their remaining TTL.

Expand Down
18 changes: 18 additions & 0 deletions src/internal/lazyheap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,24 @@ impl<T> Iterator for RawIter<T> {
}
}
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let left = self.first.len() + self.second.len();
(left, Some(left))
}
}

impl<T> ExactSizeIterator for RawIter<T> {}

impl<T> Clone for RawIter<T> {
#[inline]
fn clone(&self) -> Self {
Self {
first: self.first.clone(),
second: self.second.clone(),
}
}
}

unsafe impl<T: Send + Send> Send for LazyHeap<T> {}
Expand Down
2 changes: 2 additions & 0 deletions src/internal/linked_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,8 @@ impl<T> Iterator for RawIter<T> {
}
}

impl<T> ExactSizeIterator for RawIter<T> {}

unsafe impl<T: Send + Send> Send for LinkedList<T> {}
unsafe impl<T: Sync + Sync> Sync for LinkedList<T> {}
unsafe impl<T: Send + Send> Send for RawIter<T> {}
Expand Down
38 changes: 38 additions & 0 deletions src/internal/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,26 @@ impl<T> Iterator for RawSliceIter<T> {
Some(value)
}
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let left = self.len - self.index;
(left, Some(left))
}
}

impl<T> ExactSizeIterator for RawSliceIter<T> {}

// Cloning gives a second cursor over the same elements; it never touches them.
impl<T> Clone for RawSliceIter<T> {
#[inline]
fn clone(&self) -> Self {
Self {
pointer: self.pointer,
index: self.index,
len: self.len,
}
}
}

unsafe impl<T: Sync> Send for RawSliceIter<T> {}
Expand Down Expand Up @@ -514,4 +534,22 @@ impl<T> Iterator for RawVecDequeIter<T> {
}
}
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let left = self.first.len() + self.second.len();
(left, Some(left))
}
}

impl<T> ExactSizeIterator for RawVecDequeIter<T> {}

impl<T> Clone for RawVecDequeIter<T> {
#[inline]
fn clone(&self) -> Self {
Self {
first: self.first.clone(),
second: self.second.clone(),
}
}
}
27 changes: 27 additions & 0 deletions src/macro_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@ macro_rules! implement_pyclass {
};
}

/// Implements the generation-version guard shared by every cache view type.
///
/// # Example
///
/// ```ignore
/// implement_view_guard!(PyCacheKeys);
/// ```
#[macro_export]
macro_rules! implement_view_guard {
($name:ident) => {
impl $name {
/// Fails if the cache changed after this view was created.
#[inline]
fn check_generation(&self) -> pyo3::PyResult<()> {
if self.initial_gv == self.gv.get() {
return Ok(());
}

Err($crate::new_py_error!(
PyRuntimeError,
"cache size changed during iteration"
))
}
}
};
}

/// Creates a new [`PyErr`] of the given exception type.
#[macro_export]
macro_rules! new_py_error {
Expand Down
Loading