From ab5b73361751bf22a06d336448ba8bd004dc3c57 Mon Sep 17 00:00:00 2001 From: awolverp Date: Tue, 29 Sep 2026 20:17:17 +0330 Subject: [PATCH 1/2] Fix #96 --- AGENTS.md | 42 ++++++++++ Cargo.toml | 2 +- cachebox/_cachebox.py | 1 - cachebox/_wrappers.py | 7 +- src/hashbrown/alloc.rs | 2 +- src/hashbrown/raw.rs | 48 +++++++----- src/internal/onceinit.rs | 32 ++++---- src/lib.rs | 2 + src/pyclasses/cache.rs | 138 +++++++++++++++++---------------- src/pyclasses/fifocache.rs | 136 ++++++++++++++++---------------- src/pyclasses/lfucache.rs | 138 +++++++++++++++++---------------- src/pyclasses/lrucache.rs | 138 +++++++++++++++++---------------- src/pyclasses/rrcache.rs | 134 ++++++++++++++++---------------- src/pyclasses/ttlcache.rs | 155 +++++++++++++++++++------------------ src/pyclasses/vttlcache.rs | 145 +++++++++++++++++----------------- 15 files changed, 604 insertions(+), 516 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..65689bb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,42 @@ +# cachebox + +In-memory cache for Python. The hot path is a PyO3 extension (`cachebox._core`); `cachebox/` is the public package. + +## Working rules + +- Keep diffs small and match the surrounding style. Do not add a new language, framework, or packaging tool. +- After a behavior change, rebuild with maturin and run pytest. +- Do not edit `target/`, `*.so`, `__pycache__`, `.venv`, or other build output. Do not edit `Cargo.lock` or `uv.lock` unless a dependency changed. +- Do not read or write secrets or `.env`. +- `src/hashbrown/` is vendored hashbrown (see `LICENSE-THIRD-PARTY`). Change it only when the table itself must change. +- v6 is the public API. A breaking change needs a note in `docs/docs/migration.md` and a matching update to `cachebox/_core.pyi` and the MkDocs pages. +- `use-small-offset` is a test-only Cargo feature. Release and publish builds must not enable it. + +## Layout + +- `src/policies/` — eviction (Cache, FIFO, RR, LRU, LFU, TTL, VTTL). +- `src/pyclasses/` — one PyO3 class per policy, plus key/value/item iterators. +- `src/internal/` — pickle, `OnceInit` (`__new__` / `__init__`), linked list, hash helpers. +- `cachebox/_cachebox.py` — public `TTLCache` and `VTTLCache` (subclasses of the Rust types). Other classes are re-exported from `_core`. +- `cachebox/utils.py` and `_wrappers.py` — `@cached`, key makers, stampede locks. +- `tests/` — pytest mixins shared across implementations. Rust `#[cfg(test)]` covers the vendored table only. + +## Commands + +```bash +uv venv .venv && uv pip install --group ci +maturin develop --features use-small-offset # local tests; CI does this +pytest -v -n auto # CI also sets HYPOTHESIS_PROFILE=slow +typos --config typos.toml +zizmor .github/ +mkdocs serve --config-file docs/mkdocs.yml +``` + +`maturin develop --release` is the documented source install. Wheels are built only by `.github/workflows/CI.yml` on a tag. + +## Style + +- `rustfmt.toml` sets `imports_granularity = "Item"`. +- Rust edition is whatever `Cargo.toml` says. Edition 2024 denies `unsafe_op_in_unsafe_fn`; the crate allows it in `src/lib.rs`. +- Python docstrings are Google style. MkDocs (`mkdocstrings`) renders them. +- Clippy warns on `dbg!` and `print!`. Clippy, rustfmt, and mypy are not CI jobs. diff --git a/Cargo.toml b/Cargo.toml index a062ee8..8e7ede2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cachebox" version = "6.2.7" -edition = "2021" +edition = "2024" description = "The fastest memoizing and caching Python library written in Rust" readme = "README.md" license = "MIT" diff --git a/cachebox/_cachebox.py b/cachebox/_cachebox.py index 2cc4f3f..0909262 100644 --- a/cachebox/_cachebox.py +++ b/cachebox/_cachebox.py @@ -1,5 +1,4 @@ import threading -import time import typing from datetime import datetime, timedelta diff --git a/cachebox/_wrappers.py b/cachebox/_wrappers.py index ed81393..8f94733 100644 --- a/cachebox/_wrappers.py +++ b/cachebox/_wrappers.py @@ -1,15 +1,14 @@ import inspect import typing from collections import namedtuple -from contextlib import AbstractAsyncContextManager, AbstractContextManager from collections.abc import Callable, Hashable +from contextlib import AbstractAsyncContextManager, AbstractContextManager from cachebox._core import BaseCacheImpl, Cache _PostProcess: typing.TypeAlias = Callable[[typing.Any], typing.Any] -_Callback: typing.TypeAlias = Callable[ - [int, typing.Any, typing.Any], typing.Any -] +_Callback: typing.TypeAlias = Callable[[int, typing.Any, typing.Any], typing.Any] + class _Lock: __slots__ = ("_lock", "waiters") diff --git a/src/hashbrown/alloc.rs b/src/hashbrown/alloc.rs index 4504544..5bed63e 100644 --- a/src/hashbrown/alloc.rs +++ b/src/hashbrown/alloc.rs @@ -1,8 +1,8 @@ -pub(crate) use self::inner::do_alloc; #[cfg(test)] pub(crate) use self::inner::AllocError; pub(crate) use self::inner::Allocator; pub(crate) use self::inner::Global; +pub(crate) use self::inner::do_alloc; mod inner { #[cfg(test)] diff --git a/src/hashbrown/raw.rs b/src/hashbrown/raw.rs index 1cc39c8..6002058 100644 --- a/src/hashbrown/raw.rs +++ b/src/hashbrown/raw.rs @@ -1,12 +1,12 @@ +use super::TryReserveError; use super::control::BitMaskIter; use super::control::Group; use super::control::Tag; use super::control::TagSliceExt; -use super::scopeguard::guard; use super::scopeguard::ScopeGuard; +use super::scopeguard::guard; use super::util::likely; use super::util::unlikely; -use super::TryReserveError; use core::array; use core::iter::FusedIterator; use core::marker::PhantomData; @@ -14,14 +14,14 @@ use core::mem; use core::ptr; use core::ptr::NonNull; use core::slice; -use std::alloc::handle_alloc_error; use std::alloc::Layout; +use std::alloc::handle_alloc_error; -use super::alloc::do_alloc; #[cfg(test)] use super::alloc::AllocError; use super::alloc::Allocator; use super::alloc::Global; +use super::alloc::do_alloc; #[inline] unsafe fn offset_from(to: *const T, from: *const T) -> usize { @@ -193,6 +193,9 @@ fn bucket_mask_to_capacity(bucket_mask: usize) -> usize { // Keep in mind that the bucket mask is one less than the bucket count. bucket_mask } else { + // `bucket_mask` is bounded by the maximum allocation size, so it can + // never be `usize::MAX` and the `+ 1` below cannot overflow. + debug_assert!(bucket_mask != usize::MAX); // For larger tables we reserve 12.5% of the slots as empty. ((bucket_mask + 1) / 8) * 7 } @@ -1466,21 +1469,24 @@ impl RawTable { /// should be dropped using a `RawIter` before freeing the allocation. #[cfg_attr(feature = "inline-more", inline)] pub fn into_allocation(self) -> Option<(NonNull, Layout, A)> { - let alloc = if self.table.is_empty_singleton() { + let this = mem::ManuallyDrop::new(self); + // SAFETY: `this` is never dropped, so ownership of the allocator is + // moved out exactly once. If the table never allocated, the allocator + // is dropped here rather than being leaked. + let alloc = unsafe { ptr::read(&raw const this.alloc) }; + if this.table.is_empty_singleton() { None } else { let (layout, ctrl_offset) = { - let option = Self::TABLE_LAYOUT.calculate_layout_for(self.table.num_buckets()); + let option = Self::TABLE_LAYOUT.calculate_layout_for(this.table.num_buckets()); unsafe { option.unwrap_unchecked() } }; Some(( - unsafe { NonNull::new_unchecked(self.table.ctrl.as_ptr().sub(ctrl_offset).cast()) }, + unsafe { NonNull::new_unchecked(this.table.ctrl.as_ptr().sub(ctrl_offset).cast()) }, layout, - unsafe { ptr::read(&raw const self.alloc) }, + alloc, )) - }; - mem::forget(self); - alloc + } } } @@ -4331,10 +4337,12 @@ mod test_map { Some(i) ); } - assert!(table - .find(i + 100, |x| Ok::<_, ()>(*x == i + 100)) - .unwrap() - .is_none()); + assert!( + table + .find(i + 100, |x| Ok::<_, ()>(*x == i + 100)) + .unwrap() + .is_none() + ); } rehash_in_place(&mut table, hasher); @@ -4349,10 +4357,12 @@ mod test_map { Some(i) ); } - assert!(table - .find(i + 100, |x| Ok::<_, ()>(*x == i + 100)) - .unwrap() - .is_none()); + assert!( + table + .find(i + 100, |x| Ok::<_, ()>(*x == i + 100)) + .unwrap() + .is_none() + ); } } diff --git a/src/internal/onceinit.rs b/src/internal/onceinit.rs index 6df2817..0cd9839 100644 --- a/src/internal/onceinit.rs +++ b/src/internal/onceinit.rs @@ -9,8 +9,8 @@ use std::cell; use std::mem; -use std::sync::atomic; use std::sync::Arc; +use std::sync::atomic; const UNINIT: u8 = 0; const RUNNING: u8 = 1; @@ -73,11 +73,11 @@ impl OnceInit { /// Intended to be called from the PyO3 `__init__` handler once the Python-side /// arguments have been validated and the Rust value can be constructed. /// - /// # Panics + /// # Errors /// - /// Panics if `set` has already been called on this instance. + /// Returns an error if `set` has already been called on this instance. #[inline] - pub fn set(&self, val: T) { + pub fn set(&self, val: T) -> pyo3::PyResult<()> { if self .0 .state @@ -89,25 +89,26 @@ impl OnceInit { ) .is_err() { - already_init_panic(); + return Err(already_init_exception()); } // SAFETY: we own the RUNNING token — no other thread can write value. unsafe { (*self.0.value.get()).write(val) }; self.0.state.store(INIT, atomic::Ordering::Release); + Ok(()) } /// Returns an immutable reference to initialized value. /// - /// # Panics + /// # Errors /// - /// Panics if called before [`set`](Self::set) has completed. + /// Returns an error if called before [`set`](Self::set) has completed. #[inline] - pub fn get(&self) -> &T { + pub fn get(&self) -> pyo3::PyResult<&T> { if crate::hashbrown::util::likely(self.0.state.load(atomic::Ordering::Acquire) == INIT) { // SAFETY: state == INIT guarantees `value` was fully written and is valid. - unsafe { (*self.0.value.get()).assume_init_ref() } + Ok(unsafe { (*self.0.value.get()).assume_init_ref() }) } else { - not_init_panic() + Err(not_init_exception()) } } } @@ -145,14 +146,17 @@ impl Drop for OnceInit { /// rarely-executed stub and does not bloat the hot path of [`lock`](OnceInit::lock). #[cold] #[inline(never)] -fn not_init_panic() -> ! { - panic!("Object not initialized (__init__ not called)") +fn not_init_exception() -> pyo3::PyErr { + new_py_error!( + PyRuntimeError, + "Object not initialized (__init__ not called)" + ) } /// Marked `#[cold]` and `#[inline(never)]` so it is compiled as a separate, /// rarely-executed stub and does not bloat the hot path of [`set`](OnceInit::set). #[cold] #[inline(never)] -fn already_init_panic() -> ! { - panic!("Object already initialized") +fn already_init_exception() -> pyo3::PyErr { + new_py_error!(PyRuntimeError, "Object already initialized") } diff --git a/src/lib.rs b/src/lib.rs index d5cad38..5891d92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(unsafe_op_in_unsafe_fn)] + #[macro_use] mod macro_rules; mod hashbrown; diff --git a/src/pyclasses/cache.rs b/src/pyclasses/cache.rs index 36a0ade..3ca5e88 100644 --- a/src/pyclasses/cache.rs +++ b/src/pyclasses/cache.rs @@ -105,59 +105,61 @@ impl PyCache { } }; - self.0.set(wrapped); + self.0.set(wrapped)?; extend_result } #[getter] #[inline] - fn maxsize(&self) -> usize { - let inner = self.0.get(); - inner.shared().maxsize() + fn maxsize(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.shared().maxsize()) } #[inline] - fn current_size(&self) -> usize { - let inner = self.0.get(); - inner.policy().current_size() + fn current_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.policy().current_size()) } #[inline] - fn remaining_size(&self) -> usize { - let inner = self.0.get(); - inner.remaining_size() + fn remaining_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.remaining_size()) } #[getter] #[inline] - fn getsizeof(&self, py: pyo3::Python) -> Option { - let inner = self.0.get(); - inner.shared().getsizeof().clone_ref(py).into() + fn getsizeof(&self, py: pyo3::Python) -> pyo3::PyResult> { + let inner = self.0.get()?; + Ok(inner.shared().getsizeof().clone_ref(py).into()) } /// Returns the number of elements the map can hold without reallocating. #[inline] - fn capacity(&self) -> usize { - let inner = self.0.get(); + fn capacity(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().capacity() + Ok(policy.table().capacity()) } /// Returns the number of entries currently in the cache. #[inline] - fn __len__(&self) -> usize { - let inner = self.0.get(); + fn __len__(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().len() + Ok(policy.table().len()) } #[inline] fn __sizeof__(&self) -> usize { const FIXED_SIZE: usize = size_of::>(); - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return FIXED_SIZE; + }; let policy = inner.policy(); FIXED_SIZE + (policy.table().capacity() * size_of::()) @@ -165,7 +167,10 @@ impl PyCache { #[inline] fn __bool__(&self) -> bool { - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return false; + }; + let policy = inner.policy(); !policy.table().is_empty() @@ -180,27 +185,27 @@ impl PyCache { #[inline] fn contains(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; inner.contains(py, &key) } /// Returns `True` if cache is empty. #[inline] - fn is_empty(&self) -> bool { - let inner = self.0.get(); + fn is_empty(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().is_empty() + Ok(policy.table().is_empty()) } /// Returns `True` when the cumulative size has reached the maxsize limit. #[inline] - fn is_full(&self) -> bool { - let inner = self.0.get(); + fn is_full(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let shared = inner.shared(); let policy = inner.policy(); - policy.current_size() >= shared.maxsize() + Ok(policy.current_size() >= shared.maxsize()) } /// Equals to `self[key] = value`, but returns a value: @@ -217,7 +222,7 @@ impl PyCache { key: alias::PyObject, value: alias::PyObject, ) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let handle = nopolicy::Handle::new(py, inner.shared().getsizeof(), key, value)?; let old_handle = inner.insert(py, handle)?.map(|x| x.into_value()); @@ -234,7 +239,7 @@ impl PyCache { return Ok(()); } - let inner = slf.0.get(); + let inner = slf.0.get()?; let getsizeof = inner.shared().getsizeof().clone_ref(py); inner.extend( @@ -276,7 +281,7 @@ impl PyCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(x) = policy.get(py, &key, inner.shared())? { @@ -299,7 +304,7 @@ impl PyCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); match policy.get(py, &key, inner.shared())? { @@ -330,7 +335,7 @@ impl PyCache { // 3. Else -> insert default -> return default let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -377,7 +382,7 @@ impl PyCache { // 3. Else -> call factory -> insert returned value -> return it let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); { @@ -419,7 +424,7 @@ impl PyCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(x) = inner.remove(py, &key)? { return Ok(x.into_value()); @@ -437,7 +442,7 @@ impl PyCache { fn __delitem__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<()> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; match inner.remove(py, &key)? { Some(_) => Ok(()), None => Err(new_py_error!( @@ -451,7 +456,7 @@ impl PyCache { /// /// NOTE: `Cache` always raises `NotImplementedError` because has neither policy nor algorithm to evict items. fn popitem(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -468,22 +473,23 @@ impl PyCache { py: pyo3::Python, n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.drain(py, n) } /// Shrinks the internal allocation as close to the current length as possible. #[inline] - fn shrink_to_fit(&self) { - let inner = self.0.get(); + fn shrink_to_fit(&self) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.shrink_to_fit(inner.shared()); + Ok(()) } /// Removes all entries from the table and resets the cumulative size to zero. #[pyo3(signature=(*, reuse=false))] - fn clear(&self, reuse: bool) { - let inner = self.0.get(); + fn clear(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -492,6 +498,8 @@ impl PyCache { if !reuse { policy.shrink_to_fit(shared); } + + Ok(()) } fn __eq__( @@ -503,8 +511,8 @@ impl PyCache { return Ok(true); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -526,8 +534,8 @@ impl PyCache { return Ok(false); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -543,7 +551,7 @@ impl PyCache { } fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -559,7 +567,7 @@ impl PyCache { } fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -574,7 +582,7 @@ impl PyCache { } fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -594,7 +602,7 @@ impl PyCache { } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let cloned = inner.clone_ref(py); let result = Self(onceinit::OnceInit::new(cloned)); @@ -607,18 +615,18 @@ impl PyCache { } fn __getstate__(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.build_pickle(py).map(|x| x.into()) } fn __setstate__(&self, py: pyo3::Python, state: alias::PyObject) -> pyo3::PyResult<()> { let wrapped = Wrapped::from_pickle(py, state)?; - self.0.set(wrapped); + self.0.set(wrapped)?; Ok(()) } - fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> String { - let inner = slf.0.get(); + fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> pyo3::PyResult { + let inner = slf.0.get()?; let shared = inner.shared(); let policy = inner.policy(); @@ -637,20 +645,19 @@ impl PyCache { }; let items = utils::items_to_str(iter, policy.table().len()).unwrap(); - format!( + Ok(format!( "{}[maxsize={}]({})", unsafe { utils::get_type_name(py, slf.as_ptr()) }, shared.maxsize(), items - ) + )) } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing for visit return Ok(()); - } - - let inner = self.0.get(); + }; let Some(policy) = inner.try_policy() else { return Ok(()); }; @@ -665,13 +672,12 @@ impl PyCache { } fn __clear__(&self) { - if !self.0.is_initialized() { - return; + if let Ok(inner) = self.0.get() { + let mut policy = inner.policy(); + policy.clear(inner.shared()); + } else { + // object is not initialized, so there's nothing to clear } - - let inner = self.0.get(); - let mut policy = inner.policy(); - policy.clear(inner.shared()); } } diff --git a/src/pyclasses/fifocache.rs b/src/pyclasses/fifocache.rs index 6a25e72..569bb21 100644 --- a/src/pyclasses/fifocache.rs +++ b/src/pyclasses/fifocache.rs @@ -111,60 +111,62 @@ impl PyFIFOCache { } }; - self.0.set(wrapped); + self.0.set(wrapped)?; extend_result } #[getter] #[inline] - fn maxsize(&self) -> usize { - let inner = self.0.get(); - inner.shared().maxsize() + fn maxsize(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.shared().maxsize()) } #[inline] - fn current_size(&self) -> usize { - let inner = self.0.get(); - inner.policy().current_size() + fn current_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.policy().current_size()) } #[inline] - fn remaining_size(&self) -> usize { - let inner = self.0.get(); - inner.remaining_size() + fn remaining_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.remaining_size()) } #[getter] #[inline] - fn getsizeof(&self, py: pyo3::Python) -> Option { - let inner = self.0.get(); - inner.shared().getsizeof().clone_ref(py).into() + fn getsizeof(&self, py: pyo3::Python) -> pyo3::PyResult> { + let inner = self.0.get()?; + Ok(inner.shared().getsizeof().clone_ref(py).into()) } /// Returns the number of elements the map can hold without reallocating. #[inline] - fn capacity(&self) -> usize { - let inner = self.0.get(); + fn capacity(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().capacity().min(policy.entries().capacity()) + Ok(policy.table().capacity().min(policy.entries().capacity())) } /// Returns the number of entries currently in the cache. #[inline] - fn __len__(&self) -> usize { - let inner = self.0.get(); + fn __len__(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); debug_assert!(policy.table().len() == policy.entries().len()); - policy.table().len() + Ok(policy.table().len()) } #[inline] fn __sizeof__(&self) -> usize { const FIXED_SIZE: usize = size_of::>(); - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return FIXED_SIZE; + }; let policy = inner.policy(); let table_cap = policy.table().capacity() * size_of::(); @@ -174,7 +176,9 @@ impl PyFIFOCache { #[inline] fn __bool__(&self) -> bool { - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return false; + }; let policy = inner.policy(); !policy.table().is_empty() @@ -189,27 +193,27 @@ impl PyFIFOCache { #[inline] fn contains(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; inner.contains(py, &key) } /// Returns `True` if cache is empty. #[inline] - fn is_empty(&self) -> bool { - let inner = self.0.get(); + fn is_empty(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().is_empty() + Ok(policy.table().is_empty()) } /// Returns `True` when the cumulative size has reached the maxsize limit. #[inline] - fn is_full(&self) -> bool { - let inner = self.0.get(); + fn is_full(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let shared = inner.shared(); let policy = inner.policy(); - policy.current_size() >= shared.maxsize() + Ok(policy.current_size() >= shared.maxsize()) } /// Equals to `self[key] = value`, but returns a value: @@ -223,7 +227,7 @@ impl PyFIFOCache { key: alias::PyObject, value: alias::PyObject, ) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let handle = fifopolicy::Handle::new(py, inner.shared().getsizeof(), key, value)?; let old_handle = inner.insert(py, handle)?.map(|x| x.into_value()); @@ -240,7 +244,7 @@ impl PyFIFOCache { return Ok(()); } - let inner = slf.0.get(); + let inner = slf.0.get()?; let getsizeof = inner.shared().getsizeof().clone_ref(py); inner.extend( @@ -282,7 +286,7 @@ impl PyFIFOCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(x) = policy.get(py, &key, inner.shared())? { @@ -305,7 +309,7 @@ impl PyFIFOCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); match policy.get(py, &key, inner.shared())? { @@ -336,7 +340,7 @@ impl PyFIFOCache { // 3. Else -> insert default -> return default let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -383,7 +387,7 @@ impl PyFIFOCache { // 3. Else -> call factory -> insert returned value -> return it let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); { @@ -425,7 +429,7 @@ impl PyFIFOCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(x) = inner.remove(py, &key)? { return Ok(x.into_value()); @@ -443,7 +447,7 @@ impl PyFIFOCache { fn __delitem__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<()> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; match inner.remove(py, &key)? { Some(_) => Ok(()), None => Err(new_py_error!( @@ -455,7 +459,7 @@ impl PyFIFOCache { /// Remove and return a (key, value) pair as a 2-tuple. fn popitem(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -472,22 +476,23 @@ impl PyFIFOCache { py: pyo3::Python, n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.drain(py, n) } /// Shrinks the internal allocation as close to the current length as possible. #[inline] - fn shrink_to_fit(&self) { - let inner = self.0.get(); + fn shrink_to_fit(&self) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.shrink_to_fit(inner.shared()); + Ok(()) } /// Removes all entries from the table and resets the cumulative size to zero. #[pyo3(signature=(*, reuse=false))] - fn clear(&self, reuse: bool) { - let inner = self.0.get(); + fn clear(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -496,6 +501,7 @@ impl PyFIFOCache { if !reuse { policy.shrink_to_fit(shared); } + Ok(()) } fn __eq__( @@ -507,8 +513,8 @@ impl PyFIFOCache { return Ok(true); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -530,8 +536,8 @@ impl PyFIFOCache { return Ok(false); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -547,7 +553,7 @@ impl PyFIFOCache { } fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -562,7 +568,7 @@ impl PyFIFOCache { } fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -577,7 +583,7 @@ impl PyFIFOCache { } fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -597,7 +603,7 @@ impl PyFIFOCache { } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let cloned = inner.clone_ref(py); let result = Self(onceinit::OnceInit::new(cloned)); @@ -610,18 +616,18 @@ impl PyFIFOCache { } fn __getstate__(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.build_pickle(py).map(|x| x.into()) } fn __setstate__(&self, py: pyo3::Python, state: alias::PyObject) -> pyo3::PyResult<()> { let wrapped = Wrapped::from_pickle(py, state)?; - self.0.set(wrapped); + self.0.set(wrapped)?; Ok(()) } - fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> String { - let inner = slf.0.get(); + fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> pyo3::PyResult { + let inner = slf.0.get()?; let shared = inner.shared(); let policy = inner.policy(); @@ -634,12 +640,12 @@ impl PyFIFOCache { }); let items = utils::items_to_str(iter, policy.table().len()).unwrap(); - format!( + Ok(format!( "{}[maxsize={}]({})", unsafe { utils::get_type_name(py, slf.as_ptr()) }, shared.maxsize(), items - ) + )) } #[pyo3(signature = (n=0))] @@ -648,7 +654,7 @@ impl PyFIFOCache { py: pyo3::Python, mut n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; let policy = inner.policy(); if n < 0 { @@ -665,7 +671,7 @@ impl PyFIFOCache { } fn last(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; let policy = inner.policy(); match policy.entries().back() { Some(handle) => Ok(handle.key().as_ref().clone_ref(py)), @@ -674,11 +680,10 @@ impl PyFIFOCache { } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing for visit return Ok(()); - } - - let inner = self.0.get(); + }; let Some(policy) = inner.try_policy() else { return Ok(()); }; @@ -691,11 +696,10 @@ impl PyFIFOCache { } fn __clear__(&self) { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing to clear return; - } - - let inner = self.0.get(); + }; let mut policy = inner.policy(); policy.clear(inner.shared()); } diff --git a/src/pyclasses/lfucache.rs b/src/pyclasses/lfucache.rs index a465ecf..be9aeea 100644 --- a/src/pyclasses/lfucache.rs +++ b/src/pyclasses/lfucache.rs @@ -127,60 +127,62 @@ impl PyLFUCache { } }; - self.0.set(wrapped); + self.0.set(wrapped)?; extend_result } #[getter] #[inline] - fn maxsize(&self) -> usize { - let inner = self.0.get(); - inner.shared().maxsize() + fn maxsize(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.shared().maxsize()) } #[inline] - fn current_size(&self) -> usize { - let inner = self.0.get(); - inner.policy().current_size() + fn current_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.policy().current_size()) } #[inline] - fn remaining_size(&self) -> usize { - let inner = self.0.get(); - inner.remaining_size() + fn remaining_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.remaining_size()) } #[getter] #[inline] - fn getsizeof(&self, py: pyo3::Python) -> Option { - let inner = self.0.get(); - inner.shared().getsizeof().clone_ref(py).into() + fn getsizeof(&self, py: pyo3::Python) -> pyo3::PyResult> { + let inner = self.0.get()?; + Ok(inner.shared().getsizeof().clone_ref(py).into()) } /// Returns the number of elements the map can hold without reallocating. #[inline] - fn capacity(&self) -> usize { - let inner = self.0.get(); + fn capacity(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().capacity() + Ok(policy.table().capacity()) } /// Returns the number of entries currently in the cache. #[inline] - fn __len__(&self) -> usize { - let inner = self.0.get(); + fn __len__(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); debug_assert!(policy.table().len() == policy.heap().len()); - policy.table().len() + Ok(policy.table().len()) } #[inline] fn __sizeof__(&self) -> usize { const FIXED_SIZE: usize = size_of::>(); - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return FIXED_SIZE; + }; let policy = inner.policy(); let table_cap = policy.table().capacity() * 8; @@ -191,7 +193,9 @@ impl PyLFUCache { #[inline] fn __bool__(&self) -> bool { - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return false; + }; let policy = inner.policy(); !policy.table().is_empty() @@ -206,27 +210,27 @@ impl PyLFUCache { #[inline] fn contains(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; inner.contains(py, &key) } /// Returns `True` if cache is empty. #[inline] - fn is_empty(&self) -> bool { - let inner = self.0.get(); + fn is_empty(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().is_empty() + Ok(policy.table().is_empty()) } /// Returns `True` when the cumulative size has reached the maxsize limit. #[inline] - fn is_full(&self) -> bool { - let inner = self.0.get(); + fn is_full(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let shared = inner.shared(); let policy = inner.policy(); - policy.current_size() >= shared.maxsize() + Ok(policy.current_size() >= shared.maxsize()) } /// Equals to `self[key] = value`, but returns a value: @@ -240,7 +244,7 @@ impl PyLFUCache { key: alias::PyObject, value: alias::PyObject, ) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let handle = lfupolicy::FrequencyHandle::new(py, inner.shared().getsizeof(), key, value, 0)?; @@ -258,7 +262,7 @@ impl PyLFUCache { return Ok(()); } - let inner = slf.0.get(); + let inner = slf.0.get()?; let getsizeof = inner.shared().getsizeof().clone_ref(py); inner.extend( @@ -300,7 +304,7 @@ impl PyLFUCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(x) = policy.get(py, &key, inner.shared())? { @@ -323,7 +327,7 @@ impl PyLFUCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); match policy.get(py, &key, inner.shared())? { @@ -354,7 +358,7 @@ impl PyLFUCache { // 3. Else -> insert default -> return default let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -402,7 +406,7 @@ impl PyLFUCache { // 3. Else -> call factory -> insert returned value -> return it let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); { @@ -445,7 +449,7 @@ impl PyLFUCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(x) = inner.remove(py, &key)? { return Ok(x.into_value()); @@ -463,7 +467,7 @@ impl PyLFUCache { fn __delitem__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<()> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; match inner.remove(py, &key)? { Some(_) => Ok(()), None => Err(new_py_error!( @@ -475,7 +479,7 @@ impl PyLFUCache { /// Remove and return a (key, value) pair as a 2-tuple. fn popitem(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -492,21 +496,22 @@ impl PyLFUCache { py: pyo3::Python, n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.drain(py, n) } /// Shrinks the internal allocation as close to the current length as possible. #[inline] - fn shrink_to_fit(&self) { - let inner = self.0.get(); + fn shrink_to_fit(&self) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.shrink_to_fit(inner.shared()); + Ok(()) } /// Removes all entries from the table and resets the cumulative size to zero. #[pyo3(signature=(*, reuse=false))] - fn clear(&self, reuse: bool) { - let inner = self.0.get(); + fn clear(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -515,6 +520,7 @@ impl PyLFUCache { if !reuse { policy.shrink_to_fit(shared); } + Ok(()) } fn __eq__( @@ -526,8 +532,8 @@ impl PyLFUCache { return Ok(true); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -549,8 +555,8 @@ impl PyLFUCache { return Ok(false); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -566,7 +572,7 @@ impl PyLFUCache { } fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let mut policy = inner.policy(); let gv = inner.shared().generation_version(); @@ -582,7 +588,7 @@ impl PyLFUCache { } fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let mut policy = inner.policy(); let gv = inner.shared().generation_version(); @@ -598,7 +604,7 @@ impl PyLFUCache { } fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let mut policy = inner.policy(); let gv = inner.shared().generation_version(); @@ -621,7 +627,7 @@ impl PyLFUCache { fn items_with_frequency( slf: pyo3::Bound<'_, Self>, ) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let mut policy = inner.policy(); let gv = inner.shared().generation_version(); @@ -637,7 +643,7 @@ impl PyLFUCache { } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let cloned = inner.clone_ref(py); let result = Self(onceinit::OnceInit::new(cloned)); @@ -650,18 +656,18 @@ impl PyLFUCache { } fn __getstate__(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.build_pickle(py).map(|x| x.into()) } fn __setstate__(&self, py: pyo3::Python, state: alias::PyObject) -> pyo3::PyResult<()> { let wrapped = Wrapped::from_pickle(py, state)?; - self.0.set(wrapped); + self.0.set(wrapped)?; Ok(()) } - fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> String { - let inner = slf.0.get(); + fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> pyo3::PyResult { + let inner = slf.0.get()?; let shared = inner.shared(); let policy = inner.policy(); @@ -683,12 +689,12 @@ impl PyLFUCache { }; let items = utils::items_to_str(iter, policy.table().len()).unwrap(); - format!( + Ok(format!( "{}[maxsize={}]({})", unsafe { utils::get_type_name(py, slf.as_ptr()) }, shared.maxsize(), items - ) + )) } #[pyo3(signature = (key, default=utils::OptionalArgument::Undefined))] @@ -700,7 +706,7 @@ impl PyLFUCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let policy = inner.policy(); if let Some(x) = policy.peek(py, &key)? { @@ -722,7 +728,7 @@ impl PyLFUCache { py: pyo3::Python, mut n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if n < 0 { @@ -739,11 +745,10 @@ impl PyLFUCache { } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing for visit return Ok(()); - } - - let inner = self.0.get(); + }; let Some(policy) = inner.try_policy() else { return Ok(()); }; @@ -758,11 +763,10 @@ impl PyLFUCache { } fn __clear__(&self) { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing to clear return; - } - - let inner = self.0.get(); + }; let mut policy = inner.policy(); policy.clear(inner.shared()); } diff --git a/src/pyclasses/lrucache.rs b/src/pyclasses/lrucache.rs index 7d14895..2a4b758 100644 --- a/src/pyclasses/lrucache.rs +++ b/src/pyclasses/lrucache.rs @@ -137,60 +137,62 @@ impl PyLRUCache { } }; - self.0.set(wrapped); + self.0.set(wrapped)?; extend_result } #[getter] #[inline] - fn maxsize(&self) -> usize { - let inner = self.0.get(); - inner.shared().maxsize() + fn maxsize(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.shared().maxsize()) } #[inline] - fn current_size(&self) -> usize { - let inner = self.0.get(); - inner.policy().current_size() + fn current_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.policy().current_size()) } #[inline] - fn remaining_size(&self) -> usize { - let inner = self.0.get(); - inner.remaining_size() + fn remaining_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.remaining_size()) } #[getter] #[inline] - fn getsizeof(&self, py: pyo3::Python) -> Option { - let inner = self.0.get(); - inner.shared().getsizeof().clone_ref(py).into() + fn getsizeof(&self, py: pyo3::Python) -> pyo3::PyResult> { + let inner = self.0.get()?; + Ok(inner.shared().getsizeof().clone_ref(py).into()) } /// Returns the number of elements the map can hold without reallocating. #[inline] - fn capacity(&self) -> usize { - let inner = self.0.get(); + fn capacity(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().capacity().max(policy.list().capacity()) + Ok(policy.table().capacity().max(policy.list().capacity())) } /// Returns the number of entries currently in the cache. #[inline] - fn __len__(&self) -> usize { - let inner = self.0.get(); + fn __len__(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); debug_assert!(policy.table().len() == policy.list().len()); - policy.table().len() + Ok(policy.table().len()) } #[inline] fn __sizeof__(&self) -> usize { const FIXED_SIZE: usize = size_of::>(); - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return FIXED_SIZE; + }; let policy = inner.policy(); let table_cap = policy.table().capacity() * 8; @@ -201,7 +203,9 @@ impl PyLRUCache { #[inline] fn __bool__(&self) -> bool { - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return false; + }; let policy = inner.policy(); !policy.table().is_empty() @@ -216,27 +220,27 @@ impl PyLRUCache { #[inline] fn contains(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; inner.contains(py, &key) } /// Returns `True` if cache is empty. #[inline] - fn is_empty(&self) -> bool { - let inner = self.0.get(); + fn is_empty(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().is_empty() + Ok(policy.table().is_empty()) } /// Returns `True` when the cumulative size has reached the maxsize limit. #[inline] - fn is_full(&self) -> bool { - let inner = self.0.get(); + fn is_full(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let shared = inner.shared(); let policy = inner.policy(); - policy.current_size() >= shared.maxsize() + Ok(policy.current_size() >= shared.maxsize()) } /// Equals to `self[key] = value`, but returns a value: @@ -250,7 +254,7 @@ impl PyLRUCache { key: alias::PyObject, value: alias::PyObject, ) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let handle = lrupolicy::Handle::new(py, inner.shared().getsizeof(), key, value)?; let old_handle = inner.insert(py, handle)?.map(|x| x.into_value()); @@ -267,7 +271,7 @@ impl PyLRUCache { return Ok(()); } - let inner = slf.0.get(); + let inner = slf.0.get()?; let getsizeof = inner.shared().getsizeof().clone_ref(py); inner.extend( @@ -309,7 +313,7 @@ impl PyLRUCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(x) = policy.get(py, &key, inner.shared())? { @@ -332,7 +336,7 @@ impl PyLRUCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); match policy.get(py, &key, inner.shared())? { @@ -363,7 +367,7 @@ impl PyLRUCache { // 3. Else -> insert default -> return default let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -410,7 +414,7 @@ impl PyLRUCache { // 3. Else -> call factory -> insert returned value -> return it let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); { @@ -452,7 +456,7 @@ impl PyLRUCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(x) = inner.remove(py, &key)? { return Ok(x.into_value()); @@ -470,7 +474,7 @@ impl PyLRUCache { fn __delitem__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<()> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; match inner.remove(py, &key)? { Some(_) => Ok(()), None => Err(new_py_error!( @@ -482,7 +486,7 @@ impl PyLRUCache { /// Remove and return a (key, value) pair as a 2-tuple. fn popitem(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -499,22 +503,23 @@ impl PyLRUCache { py: pyo3::Python, n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.drain(py, n) } /// Shrinks the internal allocation as close to the current length as possible. #[inline] - fn shrink_to_fit(&self) { - let inner = self.0.get(); + fn shrink_to_fit(&self) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.shrink_to_fit(inner.shared()); + Ok(()) } /// Removes all entries from the table and resets the cumulative size to zero. #[pyo3(signature=(*, reuse=false))] - fn clear(&self, reuse: bool) { - let inner = self.0.get(); + fn clear(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -523,6 +528,7 @@ impl PyLRUCache { if !reuse { policy.shrink_to_fit(shared); } + Ok(()) } fn __eq__( @@ -534,8 +540,8 @@ impl PyLRUCache { return Ok(true); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -557,8 +563,8 @@ impl PyLRUCache { return Ok(false); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -574,7 +580,7 @@ impl PyLRUCache { } fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -589,7 +595,7 @@ impl PyLRUCache { } fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -604,7 +610,7 @@ impl PyLRUCache { } fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -624,7 +630,7 @@ impl PyLRUCache { } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let cloned = inner.clone_ref(py); let result = Self(onceinit::OnceInit::new(cloned)); @@ -637,18 +643,18 @@ impl PyLRUCache { } fn __getstate__(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.build_pickle(py).map(|x| x.into()) } fn __setstate__(&self, py: pyo3::Python, state: alias::PyObject) -> pyo3::PyResult<()> { let wrapped = Wrapped::from_pickle(py, state)?; - self.0.set(wrapped); + self.0.set(wrapped)?; Ok(()) } - fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> String { - let inner = slf.0.get(); + fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> pyo3::PyResult { + let inner = slf.0.get()?; let shared = inner.shared(); let policy = inner.policy(); @@ -664,12 +670,12 @@ impl PyLRUCache { }; let items = utils::items_to_str(iter, policy.table().len()).unwrap(); - format!( + Ok(format!( "{}[maxsize={}]({})", unsafe { utils::get_type_name(py, slf.as_ptr()) }, shared.maxsize(), items - ) + )) } #[pyo3(signature = (key, default=utils::OptionalArgument::Undefined))] @@ -681,7 +687,7 @@ impl PyLRUCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let policy = inner.policy(); if let Some(x) = policy.peek(py, &key)? { @@ -699,7 +705,7 @@ impl PyLRUCache { #[inline] fn least_recently_used(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; let policy = inner.policy(); match policy.list().cursor_front() { @@ -710,7 +716,7 @@ impl PyLRUCache { #[inline] fn most_recently_used(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; let policy = inner.policy(); match policy.list().cursor_back() { @@ -720,11 +726,10 @@ impl PyLRUCache { } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing for visit return Ok(()); - } - - let inner = self.0.get(); + }; let Some(policy) = inner.try_policy() else { return Ok(()); }; @@ -739,11 +744,10 @@ impl PyLRUCache { } fn __clear__(&self) { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing to clear return; - } - - let inner = self.0.get(); + }; let mut policy = inner.policy(); policy.clear(inner.shared()); } diff --git a/src/pyclasses/rrcache.rs b/src/pyclasses/rrcache.rs index f5756f0..b4e628c 100644 --- a/src/pyclasses/rrcache.rs +++ b/src/pyclasses/rrcache.rs @@ -109,59 +109,61 @@ impl PyRRCache { } }; - self.0.set(wrapped); + self.0.set(wrapped)?; extend_result } #[getter] #[inline] - fn maxsize(&self) -> usize { - let inner = self.0.get(); - inner.shared().maxsize() + fn maxsize(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.shared().maxsize()) } #[inline] - fn current_size(&self) -> usize { - let inner = self.0.get(); - inner.policy().current_size() + fn current_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.policy().current_size()) } #[inline] - fn remaining_size(&self) -> usize { - let inner = self.0.get(); - inner.remaining_size() + fn remaining_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.remaining_size()) } #[getter] #[inline] - fn getsizeof(&self, py: pyo3::Python) -> Option { - let inner = self.0.get(); - inner.shared().getsizeof().clone_ref(py).into() + fn getsizeof(&self, py: pyo3::Python) -> pyo3::PyResult> { + let inner = self.0.get()?; + Ok(inner.shared().getsizeof().clone_ref(py).into()) } /// Returns the number of elements the map can hold without reallocating. #[inline] - fn capacity(&self) -> usize { - let inner = self.0.get(); + fn capacity(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().capacity() + Ok(policy.table().capacity()) } /// Returns the number of entries currently in the cache. #[inline] - fn __len__(&self) -> usize { - let inner = self.0.get(); + fn __len__(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().len() + Ok(policy.table().len()) } #[inline] fn __sizeof__(&self) -> usize { const FIXED_SIZE: usize = size_of::>(); - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return FIXED_SIZE; + }; let policy = inner.policy(); FIXED_SIZE + policy.table().capacity() * size_of::() @@ -169,7 +171,9 @@ impl PyRRCache { #[inline] fn __bool__(&self) -> bool { - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return false; + }; let policy = inner.policy(); !policy.table().is_empty() @@ -184,27 +188,27 @@ impl PyRRCache { #[inline] fn contains(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; inner.contains(py, &key) } /// Returns `True` if cache is empty. #[inline] - fn is_empty(&self) -> bool { - let inner = self.0.get(); + fn is_empty(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().is_empty() + Ok(policy.table().is_empty()) } /// Returns `True` when the cumulative size has reached the maxsize limit. #[inline] - fn is_full(&self) -> bool { - let inner = self.0.get(); + fn is_full(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let shared = inner.shared(); let policy = inner.policy(); - policy.current_size() >= shared.maxsize() + Ok(policy.current_size() >= shared.maxsize()) } /// Equals to `self[key] = value`, but returns a value: @@ -221,7 +225,7 @@ impl PyRRCache { key: alias::PyObject, value: alias::PyObject, ) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let handle = rrpolicy::Handle::new(py, inner.shared().getsizeof(), key, value)?; let old_handle = inner.insert(py, handle)?.map(|x| x.into_value()); @@ -238,7 +242,7 @@ impl PyRRCache { return Ok(()); } - let inner = slf.0.get(); + let inner = slf.0.get()?; let getsizeof = inner.shared().getsizeof().clone_ref(py); inner.extend( @@ -280,7 +284,7 @@ impl PyRRCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(x) = policy.get(py, &key, inner.shared())? { @@ -303,7 +307,7 @@ impl PyRRCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); match policy.get(py, &key, inner.shared())? { @@ -334,7 +338,7 @@ impl PyRRCache { // 3. Else -> insert default -> return default let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -381,7 +385,7 @@ impl PyRRCache { // 3. Else -> call factory -> insert returned value -> return it let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); { @@ -423,7 +427,7 @@ impl PyRRCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(x) = inner.remove(py, &key)? { return Ok(x.into_value()); @@ -441,7 +445,7 @@ impl PyRRCache { fn __delitem__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<()> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; match inner.remove(py, &key)? { Some(_) => Ok(()), None => Err(new_py_error!( @@ -455,7 +459,7 @@ impl PyRRCache { /// /// NOTE: `Cache` always raises `NotImplementedError` because has neither policy nor algorithm to evict items. fn popitem(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -472,22 +476,23 @@ impl PyRRCache { py: pyo3::Python, n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.drain(py, n) } /// Shrinks the internal allocation as close to the current length as possible. #[inline] - fn shrink_to_fit(&self) { - let inner = self.0.get(); + fn shrink_to_fit(&self) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.shrink_to_fit(inner.shared()); + Ok(()) } /// Removes all entries from the table and resets the cumulative size to zero. #[pyo3(signature=(*, reuse=false))] - fn clear(&self, reuse: bool) { - let inner = self.0.get(); + fn clear(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -496,6 +501,7 @@ impl PyRRCache { if !reuse { policy.shrink_to_fit(shared); } + Ok(()) } fn __eq__( @@ -507,8 +513,8 @@ impl PyRRCache { return Ok(true); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -530,8 +536,8 @@ impl PyRRCache { return Ok(false); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -547,7 +553,7 @@ impl PyRRCache { } fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -562,7 +568,7 @@ impl PyRRCache { } fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -577,7 +583,7 @@ impl PyRRCache { } fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let gv = inner.shared().generation_version().clone(); let initial_gv = gv.get(); @@ -597,7 +603,7 @@ impl PyRRCache { } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let cloned = inner.clone_ref(py); let result = Self(onceinit::OnceInit::new(cloned)); @@ -610,18 +616,18 @@ impl PyRRCache { } fn __getstate__(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.build_pickle(py).map(|x| x.into()) } fn __setstate__(&self, py: pyo3::Python, state: alias::PyObject) -> pyo3::PyResult<()> { let wrapped = Wrapped::from_pickle(py, state)?; - self.0.set(wrapped); + self.0.set(wrapped)?; Ok(()) } - fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> String { - let inner = slf.0.get(); + fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> pyo3::PyResult { + let inner = slf.0.get()?; let shared = inner.shared(); let policy = inner.policy(); @@ -640,17 +646,17 @@ impl PyRRCache { }; let items = utils::items_to_str(iter, policy.table().len()).unwrap(); - format!( + Ok(format!( "{}[maxsize={}]({})", unsafe { utils::get_type_name(py, slf.as_ptr()) }, shared.maxsize(), items - ) + )) } #[inline] fn random_key(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; let policy = inner.policy(); if policy.table().is_empty() { @@ -666,11 +672,10 @@ impl PyRRCache { } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing for visit return Ok(()); - } - - let inner = self.0.get(); + }; let Some(policy) = inner.try_policy() else { return Ok(()); }; @@ -685,11 +690,10 @@ impl PyRRCache { } fn __clear__(&self) { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing to clear return; - } - - let inner = self.0.get(); + }; let mut policy = inner.policy(); policy.clear(inner.shared()); } diff --git a/src/pyclasses/ttlcache.rs b/src/pyclasses/ttlcache.rs index b3dc695..4200f88 100644 --- a/src/pyclasses/ttlcache.rs +++ b/src/pyclasses/ttlcache.rs @@ -90,73 +90,75 @@ impl PyTTLCache { } }; - self.0.set(wrapped); + self.0.set(wrapped)?; extend_result } #[getter] #[inline] - fn maxsize(&self) -> usize { - let inner = self.0.get(); - inner.shared().maxsize() + fn maxsize(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.shared().maxsize()) } #[inline] - fn current_size(&self) -> usize { - let inner = self.0.get(); + fn current_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.expire(inner.shared().generation_version()); - policy.current_size() + Ok(policy.current_size()) } #[inline] - fn remaining_size(&self) -> usize { - let inner = self.0.get(); + fn remaining_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; { let mut policy = inner.policy(); policy.expire(inner.shared().generation_version()); } - inner.remaining_size() + Ok(inner.remaining_size()) } #[getter] #[inline] - fn getsizeof(&self, py: pyo3::Python) -> Option { - let inner = self.0.get(); - inner.shared().getsizeof().clone_ref(py).into() + fn getsizeof(&self, py: pyo3::Python) -> pyo3::PyResult> { + let inner = self.0.get()?; + Ok(inner.shared().getsizeof().clone_ref(py).into()) } #[getter] #[inline] - fn global_ttl(&self) -> f64 { - let inner = self.0.get(); - unsafe { inner.shared().global_ttl().unwrap_unchecked().as_secs_f64() } + fn global_ttl(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(unsafe { inner.shared().global_ttl().unwrap_unchecked().as_secs_f64() }) } /// Returns the number of elements the map can hold without reallocating. #[inline] - fn capacity(&self) -> usize { - let inner = self.0.get(); + fn capacity(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().capacity().min(policy.entries().capacity()) + Ok(policy.table().capacity().min(policy.entries().capacity())) } /// Returns the number of entries currently in the cache. #[inline] - fn __len__(&self) -> usize { - let inner = self.0.get(); + fn __len__(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); debug_assert!(policy.table().len() == policy.entries().len()); - policy.table().len() + Ok(policy.table().len()) } #[inline] fn __sizeof__(&self) -> usize { const FIXED_SIZE: usize = size_of::>(); - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return FIXED_SIZE; + }; let policy = inner.policy(); let table_cap = policy.table().capacity() * size_of::(); @@ -167,7 +169,9 @@ impl PyTTLCache { #[inline] fn __bool__(&self) -> bool { - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return false; + }; let policy = inner.policy(); !policy.table().is_empty() @@ -182,27 +186,27 @@ impl PyTTLCache { #[inline] fn contains(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; inner.contains(py, &key) } /// Returns `True` if cache is empty. #[inline] - fn is_empty(&self) -> bool { - let inner = self.0.get(); + fn is_empty(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().is_empty() + Ok(policy.table().is_empty()) } /// Returns `True` when the cumulative size has reached the maxsize limit. #[inline] - fn is_full(&self) -> bool { - let inner = self.0.get(); + fn is_full(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let shared = inner.shared(); let policy = inner.policy(); - policy.current_size() >= shared.maxsize() + Ok(policy.current_size() >= shared.maxsize()) } /// Equals to `self[key] = value`, but returns a value: @@ -216,7 +220,7 @@ impl PyTTLCache { key: alias::PyObject, value: alias::PyObject, ) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let handle = ttlpolicy::ExpiringHandle::new( py, @@ -240,7 +244,7 @@ impl PyTTLCache { return Ok(()); } - let inner = slf.0.get(); + let inner = slf.0.get()?; let shared = inner.shared(); let ttl: utils::ExpiresAt = unsafe { shared.global_ttl().unwrap_unchecked().into() }; @@ -285,7 +289,7 @@ impl PyTTLCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(x) = policy.get(py, &key, inner.shared())? { @@ -308,7 +312,7 @@ impl PyTTLCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); match policy.get(py, &key, inner.shared())? { @@ -339,7 +343,7 @@ impl PyTTLCache { // 3. Else -> insert default -> return default let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -387,7 +391,7 @@ impl PyTTLCache { // 3. Else -> call factory -> insert returned value -> return it let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); { @@ -430,7 +434,7 @@ impl PyTTLCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(x) = inner.remove(py, &key)? { return Ok(x.into_value()); @@ -448,7 +452,7 @@ impl PyTTLCache { fn __delitem__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<()> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; match inner.remove(py, &key)? { Some(_) => Ok(()), None => Err(new_py_error!( @@ -460,7 +464,7 @@ impl PyTTLCache { /// Remove and return a (key, value) pair as a 2-tuple. fn popitem(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -477,22 +481,23 @@ impl PyTTLCache { py: pyo3::Python, n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.drain(py, n) } /// Shrinks the internal allocation as close to the current length as possible. #[inline] - fn shrink_to_fit(&self) { - let inner = self.0.get(); + fn shrink_to_fit(&self) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.shrink_to_fit(inner.shared()); + Ok(()) } /// Removes all entries from the table and resets the cumulative size to zero. #[pyo3(signature=(*, reuse=false))] - fn clear(&self, reuse: bool) { - let inner = self.0.get(); + fn clear(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -501,6 +506,7 @@ impl PyTTLCache { if !reuse { policy.shrink_to_fit(shared); } + Ok(()) } fn __eq__( @@ -512,8 +518,8 @@ impl PyTTLCache { return Ok(true); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -535,8 +541,8 @@ impl PyTTLCache { return Ok(false); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -552,7 +558,7 @@ impl PyTTLCache { } fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let iter = inner.policy().iter(inner.shared()); @@ -570,7 +576,7 @@ impl PyTTLCache { } fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let iter = inner.policy().iter(inner.shared()); @@ -588,7 +594,7 @@ impl PyTTLCache { } fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let iter = inner.policy().iter(inner.shared()); @@ -611,7 +617,7 @@ impl PyTTLCache { } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let cloned = inner.clone_ref(py); let result = Self(onceinit::OnceInit::new(cloned)); @@ -625,18 +631,18 @@ impl PyTTLCache { } fn __getstate__(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.build_pickle(py).map(|x| x.into()) } fn __setstate__(&self, py: pyo3::Python, state: alias::PyObject) -> pyo3::PyResult<()> { let wrapped = Wrapped::from_pickle(py, state)?; - self.0.set(wrapped); + self.0.set(wrapped)?; Ok(()) } - fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> String { - let inner = slf.0.get(); + fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> pyo3::PyResult { + let inner = slf.0.get()?; let shared = inner.shared(); let policy = inner.policy(); @@ -654,18 +660,18 @@ impl PyTTLCache { }); let items = utils::items_to_str(iter, policy.table().len()).unwrap(); - format!( + Ok(format!( "{}[maxsize={}]({})", unsafe { utils::get_type_name(py, slf.as_ptr()) }, shared.maxsize(), items - ) + )) } #[inline] #[pyo3(signature=(*, reuse=false))] - fn expire(&self, reuse: bool) { - let inner = self.0.get(); + fn expire(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -674,6 +680,7 @@ impl PyTTLCache { if !reuse { policy.shrink_to_fit(shared); } + Ok(()) } #[pyo3(signature = (n=0))] @@ -682,7 +689,7 @@ impl PyTTLCache { py: pyo3::Python, mut n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); policy.expire(inner.shared().generation_version()); @@ -701,7 +708,7 @@ impl PyTTLCache { } fn last(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); policy.expire(inner.shared().generation_version()); @@ -721,7 +728,7 @@ impl PyTTLCache { ) -> pyo3::PyResult<(alias::PyObject, f64)> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(x) = policy.get(py, &key, inner.shared())? { @@ -754,7 +761,7 @@ impl PyTTLCache { ) -> pyo3::PyResult<(alias::PyObject, f64)> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(x) = inner.remove(py, &key)? { let dur = x @@ -775,7 +782,7 @@ impl PyTTLCache { } fn popitem_with_expire(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject, f64)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -793,7 +800,7 @@ impl PyTTLCache { fn items_with_expire( slf: pyo3::Bound<'_, Self>, ) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let iter = inner.policy().iter(inner.shared()); @@ -811,11 +818,10 @@ impl PyTTLCache { } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing for visit return Ok(()); - } - - let inner = self.0.get(); + }; let Some(policy) = inner.try_policy() else { return Ok(()); }; @@ -828,11 +834,10 @@ impl PyTTLCache { } fn __clear__(&self) { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing to clear return; - } - - let inner = self.0.get(); + }; let mut policy = inner.policy(); policy.clear(inner.shared()); } diff --git a/src/pyclasses/vttlcache.rs b/src/pyclasses/vttlcache.rs index cb38bf8..c2fbf43 100644 --- a/src/pyclasses/vttlcache.rs +++ b/src/pyclasses/vttlcache.rs @@ -84,67 +84,69 @@ impl PyVTTLCache { } }; - self.0.set(wrapped); + self.0.set(wrapped)?; extend_result } #[getter] #[inline] - fn maxsize(&self) -> usize { - let inner = self.0.get(); - inner.shared().maxsize() + fn maxsize(&self) -> pyo3::PyResult { + let inner = self.0.get()?; + Ok(inner.shared().maxsize()) } #[inline] - fn current_size(&self) -> usize { - let inner = self.0.get(); + fn current_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.expire(inner.shared().generation_version()); - policy.current_size() + Ok(policy.current_size()) } #[inline] - fn remaining_size(&self) -> usize { - let inner = self.0.get(); + fn remaining_size(&self) -> pyo3::PyResult { + let inner = self.0.get()?; { let mut policy = inner.policy(); policy.expire(inner.shared().generation_version()); } - inner.remaining_size() + Ok(inner.remaining_size()) } #[getter] #[inline] - fn getsizeof(&self, py: pyo3::Python) -> Option { - let inner = self.0.get(); - inner.shared().getsizeof().clone_ref(py).into() + fn getsizeof(&self, py: pyo3::Python) -> pyo3::PyResult> { + let inner = self.0.get()?; + Ok(inner.shared().getsizeof().clone_ref(py).into()) } /// Returns the number of elements the map can hold without reallocating. #[inline] - fn capacity(&self) -> usize { - let inner = self.0.get(); + fn capacity(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().capacity() + Ok(policy.table().capacity()) } /// Returns the number of entries currently in the cache. #[inline] - fn __len__(&self) -> usize { - let inner = self.0.get(); + fn __len__(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); debug_assert!(policy.table().len() == policy.heap().len()); - policy.table().len() + Ok(policy.table().len()) } #[inline] fn __sizeof__(&self) -> usize { const FIXED_SIZE: usize = size_of::>(); - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return FIXED_SIZE; + }; let policy = inner.policy(); let table_cap = policy.table().capacity() * 8; @@ -155,7 +157,9 @@ impl PyVTTLCache { #[inline] fn __bool__(&self) -> bool { - let inner = self.0.get(); + let Ok(inner) = self.0.get() else { + return false; + }; let policy = inner.policy(); !policy.table().is_empty() @@ -170,27 +174,27 @@ impl PyVTTLCache { #[inline] fn contains(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; inner.contains(py, &key) } /// Returns `True` if cache is empty. #[inline] - fn is_empty(&self) -> bool { - let inner = self.0.get(); + fn is_empty(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let policy = inner.policy(); - policy.table().is_empty() + Ok(policy.table().is_empty()) } /// Returns `True` when the cumulative size has reached the maxsize limit. #[inline] - fn is_full(&self) -> bool { - let inner = self.0.get(); + fn is_full(&self) -> pyo3::PyResult { + let inner = self.0.get()?; let shared = inner.shared(); let policy = inner.policy(); - policy.current_size() >= shared.maxsize() + Ok(policy.current_size() >= shared.maxsize()) } /// Equals to `self[key] = value`, but returns a value: @@ -211,7 +215,7 @@ impl PyVTTLCache { None => None, }; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let handle = vttlpolicy::ExpiringHandle::new(py, shared.getsizeof(), ttl, key, value)?; @@ -236,7 +240,7 @@ impl PyVTTLCache { None => None, }; - let inner = slf.0.get(); + let inner = slf.0.get()?; let shared = inner.shared(); let getsizeof = shared.getsizeof().clone_ref(py); @@ -268,7 +272,7 @@ impl PyVTTLCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(x) = policy.get(py, &key, inner.shared())? { @@ -291,7 +295,7 @@ impl PyVTTLCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); match policy.get(py, &key, inner.shared())? { @@ -320,7 +324,7 @@ impl PyVTTLCache { }; let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -364,7 +368,7 @@ impl PyVTTLCache { }; let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let shared = inner.shared(); { @@ -404,7 +408,7 @@ impl PyVTTLCache { ) -> pyo3::PyResult { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(x) = inner.remove(py, &key)? { return Ok(x.into_value()); @@ -422,7 +426,7 @@ impl PyVTTLCache { fn __delitem__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<()> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; match inner.remove(py, &key)? { Some(_) => Ok(()), None => Err(new_py_error!( @@ -434,7 +438,7 @@ impl PyVTTLCache { /// Remove and return a (key, value) pair as a 2-tuple. fn popitem(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -451,22 +455,23 @@ impl PyVTTLCache { py: pyo3::Python, n: pyo3::ffi::Py_ssize_t, ) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.drain(py, n) } /// Shrinks the internal allocation as close to the current length as possible. #[inline] - fn shrink_to_fit(&self) { - let inner = self.0.get(); + fn shrink_to_fit(&self) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let mut policy = inner.policy(); policy.shrink_to_fit(inner.shared()); + Ok(()) } /// Removes all entries from the table and resets the cumulative size to zero. #[pyo3(signature=(*, reuse=false))] - fn clear(&self, reuse: bool) { - let inner = self.0.get(); + fn clear(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -475,6 +480,7 @@ impl PyVTTLCache { if !reuse { policy.shrink_to_fit(shared); } + Ok(()) } fn __eq__( @@ -486,8 +492,8 @@ impl PyVTTLCache { return Ok(true); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -509,8 +515,8 @@ impl PyVTTLCache { return Ok(false); } - let self_inner = slf.0.get(); - let other_inner = other.0.get(); + let self_inner = slf.0.get()?; + let other_inner = other.0.get()?; let self_policy = self_inner.policy(); let other_policy = other_inner.policy(); @@ -526,7 +532,7 @@ impl PyVTTLCache { } fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let mut policy = inner.policy(); let gv = inner.shared().generation_version(); @@ -542,7 +548,7 @@ impl PyVTTLCache { } fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let mut policy = inner.policy(); let gv = inner.shared().generation_version(); @@ -558,7 +564,7 @@ impl PyVTTLCache { } fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let mut policy = inner.policy(); let gv = inner.shared().generation_version(); @@ -579,7 +585,7 @@ impl PyVTTLCache { } fn copy(&self, py: pyo3::Python) -> pyo3::PyResult> { - let inner = self.0.get(); + let inner = self.0.get()?; let cloned = inner.clone_ref(py); let result = Self(onceinit::OnceInit::new(cloned)); @@ -593,18 +599,18 @@ impl PyVTTLCache { } fn __getstate__(&self, py: pyo3::Python) -> pyo3::PyResult { - let inner = self.0.get(); + let inner = self.0.get()?; inner.build_pickle(py).map(|x| x.into()) } fn __setstate__(&self, py: pyo3::Python, state: alias::PyObject) -> pyo3::PyResult<()> { let wrapped = Wrapped::from_pickle(py, state)?; - self.0.set(wrapped); + self.0.set(wrapped)?; Ok(()) } - fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> String { - let inner = slf.0.get(); + fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> pyo3::PyResult { + let inner = slf.0.get()?; let shared = inner.shared(); let policy = inner.policy(); @@ -628,18 +634,18 @@ impl PyVTTLCache { }; let items = utils::items_to_str(iter, policy.table().len()).unwrap(); - format!( + Ok(format!( "{}[maxsize={}]({})", unsafe { utils::get_type_name(py, slf.as_ptr()) }, shared.maxsize(), items - ) + )) } #[inline] #[pyo3(signature=(*, reuse=false))] - fn expire(&self, reuse: bool) { - let inner = self.0.get(); + fn expire(&self, reuse: bool) -> pyo3::PyResult<()> { + let inner = self.0.get()?; let shared = inner.shared(); let mut policy = inner.policy(); @@ -648,6 +654,7 @@ impl PyVTTLCache { if !reuse { policy.shrink_to_fit(shared); } + Ok(()) } #[pyo3(signature = (key, default=utils::OptionalArgument::Undefined))] @@ -659,7 +666,7 @@ impl PyVTTLCache { ) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); if let Some(handle) = policy.get(py, &key, inner.shared())? { @@ -699,7 +706,7 @@ impl PyVTTLCache { ) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> { let key = utils::PrecomputedHashObject::new(py, key)?; - let inner = self.0.get(); + let inner = self.0.get()?; if let Some(handle) = inner.remove(py, &key)? { let dur = match handle.expires_at() { @@ -730,7 +737,7 @@ impl PyVTTLCache { &self, py: pyo3::Python, ) -> pyo3::PyResult<(alias::PyObject, alias::PyObject, alias::PyObject)> { - let inner = self.0.get(); + let inner = self.0.get()?; let mut policy = inner.policy(); let handle = policy.evict(inner.shared())?; @@ -755,7 +762,7 @@ impl PyVTTLCache { fn items_with_expire( slf: pyo3::Bound<'_, Self>, ) -> pyo3::PyResult> { - let inner = slf.get().0.get(); + let inner = slf.get().0.get()?; let mut policy = inner.policy(); let gv = inner.shared().generation_version(); @@ -771,11 +778,10 @@ impl PyVTTLCache { } fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing for visit return Ok(()); - } - - let inner = self.0.get(); + }; let Some(policy) = inner.try_policy() else { return Ok(()); }; @@ -790,11 +796,10 @@ impl PyVTTLCache { } fn __clear__(&self) { - if !self.0.is_initialized() { + let Ok(inner) = self.0.get() else { + // object is not initialized, so there's nothing to clear return; - } - - let inner = self.0.get(); + }; let mut policy = inner.policy(); policy.clear(inner.shared()); } From acafadfef76526a7667166b8d0ea8025b7a080a4 Mon Sep 17 00:00:00 2001 From: awolverp Date: Tue, 29 Sep 2026 20:25:10 +0330 Subject: [PATCH 2/2] Update tests --- tests/conftest.py | 19 +++++++++++++++++++ tests/mixins.py | 6 ++++-- tests/test_impls.py | 15 ++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4c8a3a2..36c43a5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,8 @@ +import pytest from hypothesis import HealthCheck, settings +import cachebox + # Register a custom profile that suppresses the health check settings.register_profile( "global_fuzz_settings", suppress_health_check=[HealthCheck.differing_executors] @@ -7,3 +10,19 @@ # Load the profile globally for the entire test run settings.load_profile("global_fuzz_settings") + + +ALL_CACHE_TYPES = [ + cachebox.Cache, + cachebox.FIFOCache, + cachebox.RRCache, + cachebox.LRUCache, + cachebox.LFUCache, + cachebox.TTLCache, + cachebox.VTTLCache, +] + + +@pytest.fixture(params=ALL_CACHE_TYPES) +def cache_cls(request: pytest.FixtureRequest) -> type[cachebox.BaseCacheImpl]: + return request.param diff --git a/tests/mixins.py b/tests/mixins.py index 9583603..5b73496 100644 --- a/tests/mixins.py +++ b/tests/mixins.py @@ -86,7 +86,7 @@ def test_capacity_param(self): assert c.capacity() >= 10 def test_getsizeof_stored(self): - sizer = lambda k, v: len(v) # noqa: E731 + sizer = lambda k, v: len(v) c = self.create_cache(maxsize=100, getsizeof=sizer) assert c.getsizeof is sizer @@ -567,7 +567,9 @@ def test_walking_a_cache_nothing_else_holds(self): timeout=60, ) - assert done.stdout.strip() == "ok", done.stderr or f"exit code {done.returncode}" + assert done.stdout.strip() == "ok", ( + done.stderr or f"exit code {done.returncode}" + ) @pytest.mark.skipif( platform.python_implementation() == "PyPy", diff --git a/tests/test_impls.py b/tests/test_impls.py index 87b9f8a..59fbf59 100644 --- a/tests/test_impls.py +++ b/tests/test_impls.py @@ -576,7 +576,9 @@ def test_read_under_live_iterator_invalidates_it(self): timeout=60, ) - assert done.stdout.strip() == "ok", done.stderr or f"exit code {done.returncode}" + assert done.stdout.strip() == "ok", ( + done.stderr or f"exit code {done.returncode}" + ) def test_does_not_evict_recently_read_key(self): c = self.create_cache(3) @@ -1924,3 +1926,14 @@ def test_sweep_interval(self): assert len(obj) == 3 time.sleep(3.5) assert len(obj) == 0 + + +def test_onceinit_doesnt_panic(cache_cls: type[cachebox.BaseCacheImpl]): + class Child(cache_cls): + def __init__(self) -> None: + pass + + c = Child() + + with pytest.raises(RuntimeError, match="not"): + c.is_full()