Skip to content

Commit 33c019e

Browse files
committed
Fix maxmemory for PyPy
!test
1 parent 53daa9e commit 33c019e

3 files changed

Lines changed: 48 additions & 2 deletions

File tree

python/cachebox/_cachebox.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ def __init__(
8585
iterable (Union[Cache, dict, tuple, Generator, None], optional): Initial data to populate the cache. Defaults to None.
8686
capacity (int, optional): Pre-allocate hash table capacity to minimize reallocations. Defaults to 0.
8787
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
88+
On PyPy, it works same as `maxsize` if objects do not support `__sizeof__`
89+
method.
8890
8991
Creates a new cache with specified size constraints and optional initial data. The cache can be pre-sized
9092
to improve performance when the number of expected elements is known in advance.
@@ -599,8 +601,9 @@ def __init__(
599601
iterable (dict or Iterable[tuple], optional): Initial data to populate the cache. Defaults to None.
600602
capacity (int, optional): Preallocated capacity for the cache to minimize reallocations. Defaults to 0.
601603
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
602-
When maxmemory is set, updates can evict any key, including
603-
the updated key.
604+
When maxmemory is set, updates can evict any key, including the updated key.
605+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
606+
does not have a `__sizeof__` method.
604607
605608
Note:
606609
- The cache size limit is immutable after initialization.
@@ -872,6 +875,8 @@ def __init__(
872875
iterable (dict | Iterable[tuple], optional): Initial data to populate the cache.
873876
capacity (int, optional): Pre-allocated capacity for the cache to minimize reallocations.
874877
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
878+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
879+
does not have a `__sizeof__` method.
875880
876881
Notes:
877882
- The cache size is immutable after initialization.
@@ -1152,6 +1157,8 @@ def __init__(
11521157
iterable (dict or Iterable[tuple], optional): Initial data to populate the cache.
11531158
capacity (int, optional): Initial hash table capacity to minimize reallocations. Defaults to 0.
11541159
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
1160+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
1161+
does not have a `__sizeof__` method.
11551162
11561163
The cache uses a thread-safe LFU eviction policy, removing least frequently accessed items when the cache reaches its maximum size.
11571164
"""
@@ -1443,6 +1450,8 @@ def __init__(
14431450
iterable: Optional initial items to populate the cache, can be a dict or iterable of tuples.
14441451
capacity: Optional initial capacity for the underlying cache storage. Defaults to 0.
14451452
maxmemory: Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
1453+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
1454+
does not have a `__sizeof__` method.
14461455
14471456
Raises:
14481457
ValueError: If the time-to-live (ttl) is not a positive number.
@@ -1819,6 +1828,8 @@ def __init__(
18191828
ttl (float or timedelta or datetime, optional): Time-to-live duration for `iterable` items.
18201829
capacity (int, optional): Preallocated capacity for the cache to minimize reallocations.
18211830
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
1831+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
1832+
does not have a `__sizeof__` method.
18221833
18231834
Raises:
18241835
ValueError: If provided TTL is zero or negative.

python/tests/mixin.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,24 @@ def test_maxmemory_enforced(self):
127127
assert k2 in cache
128128
assert cache.memory() <= cache.maxmemory
129129

130+
def test_maxmemory_enforced_base_types(self):
131+
size_of_int = sys.getsizeof(1, 1)
132+
133+
cache = self.CACHE(0, **self.KWARGS, maxmemory=size_of_int * 10)
134+
135+
for i in range(5):
136+
cache[i] = i
137+
138+
if self.NO_POLICY:
139+
with pytest.raises(OverflowError):
140+
cache[10] = 10
141+
142+
assert 1 in cache
143+
else:
144+
cache[10] = 10
145+
assert 10 in cache
146+
assert cache.memory() <= cache.maxmemory
147+
130148
def test_update_overflow_preserves_entry(self):
131149
cache = self.CACHE(0, **self.KWARGS, maxmemory=60)
132150

src/common.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,29 @@ macro_rules! extract_pickle_tuple {
178178
}
179179

180180
#[inline]
181+
#[cfg(not(PyPy))]
181182
pub fn pyobject_size(py: pyo3::Python<'_>, obj: &pyo3::Py<pyo3::PyAny>) -> pyo3::PyResult<usize> {
182183
use pyo3::types::PyAnyMethods;
183184

184185
obj.bind(py).call_method0("__sizeof__")?.extract::<usize>()
185186
}
186187

188+
#[inline]
189+
#[cfg(PyPy)]
190+
pub fn pyobject_size(py: pyo3::Python<'_>, obj: &pyo3::Py<pyo3::PyAny>) -> pyo3::PyResult<usize> {
191+
use pyo3::types::PyAnyMethods;
192+
193+
static SIZEOF_METHOD_NAME: &'static str = "__sizeof__";
194+
195+
// PyPy does not support __sizeof__ or sys.getsizeof
196+
let has_sizeof = obj.bind(py).getattr_opt(SIZEOF_METHOD_NAME)?;
197+
198+
match has_sizeof {
199+
Some(x) => x.call0()?.extract::<usize>(),
200+
None => Ok(1),
201+
}
202+
}
203+
187204
#[inline]
188205
pub fn entry_size(
189206
py: pyo3::Python<'_>,

0 commit comments

Comments
 (0)