cachebox 6.2.0__tar.gz → 6.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {cachebox-6.2.0 → cachebox-6.2.2}/.github/workflows/CI.yml +4 -6
  2. {cachebox-6.2.0 → cachebox-6.2.2}/.github/workflows/python-test.yml +12 -9
  3. {cachebox-6.2.0 → cachebox-6.2.2}/Cargo.lock +8 -1
  4. {cachebox-6.2.0 → cachebox-6.2.2}/Cargo.toml +2 -2
  5. {cachebox-6.2.0 → cachebox-6.2.2}/PKG-INFO +2 -1
  6. {cachebox-6.2.0 → cachebox-6.2.2}/README.md +1 -0
  7. {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/_wrappers.py +19 -35
  8. {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/utils.py +34 -9
  9. {cachebox-6.2.0 → cachebox-6.2.2}/requirements-dev.txt +1 -0
  10. cachebox-6.2.2/src/hashbrown/alloc.rs +21 -0
  11. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/raw.rs +4 -5
  12. cachebox-6.2.2/src/hashbrown/util.rs +23 -0
  13. {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/lazyheap.rs +1 -1
  14. {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/linked_list.rs +1 -1
  15. {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/onceinit.rs +1 -1
  16. {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/utils.rs +1 -1
  17. {cachebox-6.2.0 → cachebox-6.2.2}/src/lib.rs +4 -8
  18. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/fifopolicy.rs +2 -1
  19. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/ttlpolicy.rs +4 -7
  20. {cachebox-6.2.0 → cachebox-6.2.2}/tests/test_utils.py +124 -3
  21. cachebox-6.2.0/src/hashbrown/alloc.rs +0 -13
  22. cachebox-6.2.0/src/hashbrown/util.rs +0 -9
  23. {cachebox-6.2.0 → cachebox-6.2.2}/.github/workflows/docs.yml +0 -0
  24. {cachebox-6.2.0 → cachebox-6.2.2}/.gitignore +0 -0
  25. {cachebox-6.2.0 → cachebox-6.2.2}/LICENSE +0 -0
  26. {cachebox-6.2.0 → cachebox-6.2.2}/LICENSE-THIRD-PARTY +0 -0
  27. {cachebox-6.2.0 → cachebox-6.2.2}/build.rs +0 -0
  28. {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/__init__.py +0 -0
  29. {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/_cachebox.py +0 -0
  30. {cachebox-6.2.0 → cachebox-6.2.2}/cachebox/_core.pyi +0 -0
  31. {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/api/impls.md +0 -0
  32. {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/api/index.md +0 -0
  33. {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/api/utils.md +0 -0
  34. {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/getting-started.md +0 -0
  35. {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/index.md +0 -0
  36. {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/installation.md +0 -0
  37. {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/migration.md +0 -0
  38. {cachebox-6.2.0 → cachebox-6.2.2}/docs/docs/tips.md +0 -0
  39. {cachebox-6.2.0 → cachebox-6.2.2}/docs/mkdocs.yml +0 -0
  40. {cachebox-6.2.0 → cachebox-6.2.2}/pyproject.toml +0 -0
  41. {cachebox-6.2.0 → cachebox-6.2.2}/rustfmt.toml +0 -0
  42. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/bitmask.rs +0 -0
  43. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/generic.rs +0 -0
  44. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/lsx.rs +0 -0
  45. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/mod.rs +0 -0
  46. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/neon.rs +0 -0
  47. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/group/sse2.rs +0 -0
  48. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/mod.rs +0 -0
  49. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/control/tag.rs +0 -0
  50. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/mod.rs +0 -0
  51. {cachebox-6.2.0 → cachebox-6.2.2}/src/hashbrown/scopeguard.rs +0 -0
  52. {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/alias.rs +0 -0
  53. {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/mod.rs +0 -0
  54. {cachebox-6.2.0 → cachebox-6.2.2}/src/internal/pickle.rs +0 -0
  55. {cachebox-6.2.0 → cachebox-6.2.2}/src/macro_rules.rs +0 -0
  56. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/common.rs +0 -0
  57. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/lfupolicy.rs +0 -0
  58. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/lrupolicy.rs +0 -0
  59. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/mod.rs +0 -0
  60. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/nopolicy.rs +0 -0
  61. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/rrpolicy.rs +0 -0
  62. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/traits.rs +0 -0
  63. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/vttlpolicy.rs +0 -0
  64. {cachebox-6.2.0 → cachebox-6.2.2}/src/policies/wrapped.rs +0 -0
  65. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/base.rs +0 -0
  66. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/cache.rs +0 -0
  67. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/fifocache.rs +0 -0
  68. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/lfucache.rs +0 -0
  69. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/lrucache.rs +0 -0
  70. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/mod.rs +0 -0
  71. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/rrcache.rs +0 -0
  72. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/ttlcache.rs +0 -0
  73. {cachebox-6.2.0 → cachebox-6.2.2}/src/pyclasses/vttlcache.rs +0 -0
  74. {cachebox-6.2.0 → cachebox-6.2.2}/src/typeref.rs +0 -0
  75. {cachebox-6.2.0 → cachebox-6.2.2}/tests/__init__.py +0 -0
  76. {cachebox-6.2.0 → cachebox-6.2.2}/tests/conftest.py +0 -0
  77. {cachebox-6.2.0 → cachebox-6.2.2}/tests/mixins.py +0 -0
  78. {cachebox-6.2.0 → cachebox-6.2.2}/tests/test_impls.py +0 -0
@@ -57,7 +57,6 @@ jobs:
57
57
  args: --release --out dist --interpreter ${{ matrix.platform.interpreter }}
58
58
  sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
59
59
  manylinux: auto
60
- rust-toolchain: nightly
61
60
 
62
61
  - uses: actions/upload-artifact@v7.0.1
63
62
  with:
@@ -101,7 +100,6 @@ jobs:
101
100
  args: --release --out dist --interpreter ${{ matrix.platform.interpreter }}
102
101
  sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
103
102
  manylinux: musllinux_1_2
104
- rust-toolchain: nightly
105
103
 
106
104
  - uses: actions/upload-artifact@v7.0.1
107
105
  with:
@@ -117,7 +115,10 @@ jobs:
117
115
  target: x64,
118
116
  interpreter: "3.10 3.11 3.12 3.13 3.14 3.14t 3.15 3.15t pypy3.11",
119
117
  }
120
- - { target: x86, interpreter: "3.10 3.11 3.12 3.13 3.14 3.14t 3.15 3.15t" }
118
+ - {
119
+ target: x86,
120
+ interpreter: "3.10 3.11 3.12 3.13 3.14 3.14t 3.15 3.15t",
121
+ }
121
122
 
122
123
  steps:
123
124
  - uses: actions/checkout@v6.0.2
@@ -134,7 +135,6 @@ jobs:
134
135
  target: ${{ matrix.platform.target }}
135
136
  args: --release --out dist --interpreter ${{ matrix.platform.interpreter }}
136
137
  sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
137
- rust-toolchain: nightly
138
138
 
139
139
  - uses: actions/upload-artifact@v7.0.1
140
140
  with:
@@ -169,7 +169,6 @@ jobs:
169
169
  target: ${{ matrix.platform.target }}
170
170
  args: --release --out dist --interpreter ${{ matrix.platform.interpreter }}
171
171
  sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
172
- rust-toolchain: nightly
173
172
 
174
173
  - uses: actions/upload-artifact@v7.0.1
175
174
  with:
@@ -188,7 +187,6 @@ jobs:
188
187
  with:
189
188
  command: sdist
190
189
  args: --out dist
191
- rust-toolchain: nightly
192
190
 
193
191
  - name: Upload sdist
194
192
  uses: actions/upload-artifact@v7.0.1
@@ -16,7 +16,7 @@ env:
16
16
 
17
17
  jobs:
18
18
  test-python:
19
- name: test ${{ matrix.python-version }}
19
+ name: ${{ matrix.python-version }}
20
20
  runs-on: ubuntu-latest
21
21
  continue-on-error: ${{ endsWith(matrix.python-version, 't') }}
22
22
  strategy:
@@ -36,22 +36,24 @@ jobs:
36
36
  steps:
37
37
  - uses: actions/checkout@v6.0.2
38
38
 
39
- - uses: dtolnay/rust-toolchain@nightly
39
+ - uses: dtolnay/rust-toolchain@stable
40
40
 
41
41
  - uses: actions/setup-python@v6.2.0
42
42
  with:
43
43
  python-version: ${{ matrix.python-version }}
44
44
  allow-prereleases: true
45
45
 
46
- - run: pip install virtualenv pytest pytest-asyncio hypothesis maturin
46
+ - run: pip install virtualenv
47
+ - run: pip install -r requirements-dev.txt
47
48
  - run: virtualenv --no-vcs-ignore .venv
49
+
48
50
  - run: maturin develop --features use-small-offset
49
- - run: pytest -v
51
+ - run: pytest -v -n auto
50
52
  env:
51
53
  HYPOTHESIS_PROFILE: slow
52
54
 
53
55
  test-os:
54
- name: test on ${{ matrix.os }}
56
+ name: ${{ matrix.os }}
55
57
  runs-on: ${{ matrix.os }}-latest
56
58
  strategy:
57
59
  fail-fast: false
@@ -61,16 +63,17 @@ jobs:
61
63
  steps:
62
64
  - uses: actions/checkout@v6.0.2
63
65
 
64
- - uses: dtolnay/rust-toolchain@nightly
66
+ - uses: dtolnay/rust-toolchain@stable
65
67
 
66
68
  - uses: actions/setup-python@v6.2.0
67
69
  with:
68
70
  python-version: "3.13"
69
- allow-prereleases: true
70
71
 
71
- - run: pip install virtualenv pytest pytest-asyncio hypothesis maturin
72
+ - run: pip install virtualenv
73
+ - run: pip install -r requirements-dev.txt
72
74
  - run: virtualenv --no-vcs-ignore .venv
75
+
73
76
  - run: maturin develop --features use-small-offset
74
- - run: pytest -v
77
+ - run: pytest -v -n auto
75
78
  env:
76
79
  HYPOTHESIS_PROFILE: slow
@@ -2,6 +2,12 @@
2
2
  # It is not intended for manual editing.
3
3
  version = 4
4
4
 
5
+ [[package]]
6
+ name = "allocator-api2"
7
+ version = "0.4.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
10
+
5
11
  [[package]]
6
12
  name = "android_system_properties"
7
13
  version = "0.1.5"
@@ -31,8 +37,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
31
37
 
32
38
  [[package]]
33
39
  name = "cachebox"
34
- version = "6.2.0"
40
+ version = "6.2.2"
35
41
  dependencies = [
42
+ "allocator-api2",
36
43
  "cfg-if",
37
44
  "chrono",
38
45
  "fastrand",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "cachebox"
3
- version = "6.2.0"
3
+ version = "6.2.2"
4
4
  edition = "2021"
5
5
  description = "The fastest memoizing and caching Python library written in Rust"
6
6
  readme = "README.md"
@@ -22,12 +22,12 @@ panic = "abort"
22
22
  strip = true
23
23
 
24
24
  [dependencies]
25
+ allocator-api2 = "0.4.0"
25
26
  cfg-if = "1.0.4"
26
27
  chrono = "0.4.44"
27
28
  fastrand = "2.4.1"
28
29
  parking_lot = {version="0.12.5", default-features=false}
29
30
  pyo3 = {version="0.29", default-features=false, features=["macros", "chrono"]}
30
- # tokio = {version="1.52.3", default-features=false, features=["sync"]}
31
31
 
32
32
  [build-dependencies]
33
33
  pyo3-build-config = {version="0.29", default-features=false, features=["resolve-config"]}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachebox
3
- Version: 6.2.0
3
+ Version: 6.2.2
4
4
  Classifier: Programming Language :: Python :: Implementation :: CPython
5
5
  Classifier: Programming Language :: Python :: Implementation :: PyPy
6
6
  Classifier: Programming Language :: Python :: 3
@@ -41,6 +41,7 @@ Project-URL: Homepage, https://github.com/awolverp/cachebox
41
41
 
42
42
  [![License](https://img.shields.io/github/license/awolverp/cachebox.svg?style=flat-square)](https://github.com/awolverp/cachebox/blob/main/LICENSE)
43
43
  [![Downloads](https://img.shields.io/pypi/dm/cachebox?style=flat-square&color=%23314bb5)](https://pepy.tech/projects/cachebox)
44
+ [![Donate](https://img.shields.io/badge/donate-PayRequest-blue)](https://payrequest.me/ali-pooralijan-awolverp)
44
45
 
45
46
  </div>
46
47
 
@@ -10,6 +10,7 @@
10
10
 
11
11
  [![License](https://img.shields.io/github/license/awolverp/cachebox.svg?style=flat-square)](https://github.com/awolverp/cachebox/blob/main/LICENSE)
12
12
  [![Downloads](https://img.shields.io/pypi/dm/cachebox?style=flat-square&color=%23314bb5)](https://pepy.tech/projects/cachebox)
13
+ [![Donate](https://img.shields.io/badge/donate-PayRequest-blue)](https://payrequest.me/ali-pooralijan-awolverp)
13
14
 
14
15
  </div>
15
16
 
@@ -18,8 +18,10 @@ class _Lock:
18
18
 
19
19
  def __enter__(self) -> None:
20
20
  self.waiters += 1
21
- self._lock.__enter__()
22
- self.waiters -= 1
21
+ try:
22
+ self._lock.__enter__()
23
+ finally:
24
+ self.waiters -= 1
23
25
 
24
26
  def __exit__(self, *_) -> None:
25
27
  self._lock.__exit__(*_)
@@ -34,8 +36,10 @@ class _AsyncLock:
34
36
 
35
37
  async def __aenter__(self) -> None:
36
38
  self.waiters += 1
37
- await self._lock.__aenter__()
38
- self.waiters -= 1
39
+ try:
40
+ await self._lock.__aenter__()
41
+ finally:
42
+ self.waiters -= 1
39
43
 
40
44
  async def __aexit__(self, *_) -> None:
41
45
  await self._lock.__aexit__(*_)
@@ -102,7 +106,7 @@ def _cached_wrapper_without_lock(
102
106
 
103
107
  result = func(*args, **kwds)
104
108
  _cache.insert(key, result)
105
- hits += 1
109
+ misses += 1
106
110
  if callback is not None:
107
111
  callback(EVENT_MISS, key, result)
108
112
 
@@ -159,7 +163,7 @@ def _async_cached_wrapper_without_lock(
159
163
  # Passing `cachebox__ignore=True` bypasses the cache and
160
164
  # calls the function directly.
161
165
  if kwds.pop("cachebox__ignore", False):
162
- return func(*args, **kwds)
166
+ return await func(*args, **kwds)
163
167
 
164
168
  _cache: BaseCacheImpl = cache(args[0]) if cache_is_fn else cache # type: ignore[arg-type]
165
169
  key = _make_key(args, kwds)
@@ -266,17 +270,19 @@ def _cached_wrapper(
266
270
  except KeyError:
267
271
  try:
268
272
  result = func(*args, **kwds)
269
- except Exception as exc:
273
+ except BaseException as exc:
270
274
  if lock.waiters > 0:
271
275
  pending_errors[key] = exc
276
+
272
277
  raise
273
278
  else:
274
279
  _cache[key] = result
275
280
  misses += 1
276
281
  event = EVENT_MISS
277
282
 
278
- if lock.waiters == 0:
279
- locks.pop(key, None)
283
+ finally:
284
+ if lock.waiters == 0:
285
+ locks.pop(key, None)
280
286
 
281
287
  if callback is not None:
282
288
  callback(event, key, result)
@@ -319,29 +325,7 @@ def _async_cached_wrapper(
319
325
  hits = 0
320
326
  misses = 0
321
327
 
322
- # See _cached_wrapper
323
328
  locks: Cache[typing.Hashable, _AsyncLock] = Cache(0)
324
-
325
- # if lock_type is asyncio.Lock:
326
-
327
- # async def _get_lock(key: typing.Hashable):
328
- # return locks.setdefault(key, _AsyncLock(lock_type()))
329
-
330
- # else:
331
- # _lock_creation_guard = asyncio.Lock()
332
-
333
- # async def _get_lock(key: typing.Hashable):
334
- # lock = locks.get(key)
335
- # if lock is not None:
336
- # return lock
337
-
338
- # async with _lock_creation_guard:
339
- # lock = locks.get(key)
340
- # if lock is None:
341
- # locks[key] = lock = _AsyncLock(lock_type())
342
-
343
- # return lock
344
-
345
329
  pending_errors: dict[typing.Hashable, BaseException] = {}
346
330
 
347
331
  async def _wrapped(*args, **kwds):
@@ -381,7 +365,7 @@ def _async_cached_wrapper(
381
365
  except KeyError:
382
366
  try:
383
367
  result = await func(*args, **kwds)
384
- except Exception as exc:
368
+ except BaseException as exc:
385
369
  if lock.waiters > 0:
386
370
  pending_errors[key] = exc
387
371
  raise
@@ -389,9 +373,9 @@ def _async_cached_wrapper(
389
373
  _cache[key] = result
390
374
  misses += 1
391
375
  event = EVENT_MISS
392
-
393
- if lock.waiters == 0:
394
- locks.pop(key, None)
376
+ finally:
377
+ if lock.waiters == 0:
378
+ locks.pop(key, None)
395
379
 
396
380
  await _call_async_callback(callback, event, key, result)
397
381
 
@@ -73,12 +73,14 @@ def make_key(*args, **kwds) -> typing.Hashable:
73
73
  if not kwds:
74
74
  if len(args) == 1 and type(args[0]) in _FAST_TYPES:
75
75
  return args[0]
76
+
76
77
  return args
77
78
 
78
79
  key = args + (_KWDS_MARK,)
79
80
  for item in kwds.items():
80
81
  key += item
81
- return key[0] if len(key) == 1 and type(key[0]) in _FAST_TYPES else key
82
+
83
+ return key
82
84
 
83
85
 
84
86
  def make_hash_key(*args, **kwds) -> int:
@@ -155,8 +157,13 @@ class Frozen(BaseCacheImpl[KT, VT]): # pragma: no cover
155
157
  ignore: If ``True``, silently ignores modification attempts; if ``False``, raises
156
158
  ``TypeError`` when modification is attempted. Default is ``False``.
157
159
  """
158
- assert isinstance(cls, BaseCacheImpl)
159
- assert type(cls) is not Frozen
160
+ if not isinstance(cls, BaseCacheImpl):
161
+ raise TypeError(
162
+ f"expected a BaseCacheImpl instance, got {type(cls).__name__!r}"
163
+ )
164
+
165
+ if type(cls) is Frozen:
166
+ raise TypeError("cannot wrap an already-frozen cache")
160
167
 
161
168
  self.__cache = cls
162
169
  self.ignore = ignore
@@ -366,9 +373,16 @@ class Frozen(BaseCacheImpl[KT, VT]): # pragma: no cover
366
373
  def _cast_lock(
367
374
  iscoroutinefunction: bool,
368
375
  lock: (
369
- typing.Type[AbstractContextManager] | typing.Type[AbstractAsyncContextManager] | bool | None
376
+ typing.Type[AbstractContextManager]
377
+ | typing.Type[AbstractAsyncContextManager]
378
+ | bool
379
+ | None
370
380
  ) = True,
371
- ) -> typing.Type[AbstractContextManager] | typing.Type[AbstractAsyncContextManager] | None:
381
+ ) -> (
382
+ typing.Type[AbstractContextManager]
383
+ | typing.Type[AbstractAsyncContextManager]
384
+ | None
385
+ ):
372
386
  import _thread
373
387
  import asyncio
374
388
  import threading
@@ -381,12 +395,18 @@ def _cast_lock(
381
395
 
382
396
  if iscoroutinefunction:
383
397
  if not hasattr(lock, "__aenter__"):
384
- raise TypeError("For async functions, you cannot use a regular synchronous lock.")
398
+ raise TypeError(
399
+ "For async functions, you cannot use a regular synchronous lock."
400
+ )
385
401
 
386
402
  return typing.cast(typing.Type[AbstractAsyncContextManager], lock)
387
403
 
388
404
  # threading.Lock, threading.RLock and _thread.allocate_lock are function
389
- if lock is threading.Lock or lock is threading.RLock or lock is _thread.allocate_lock:
405
+ if (
406
+ lock is threading.Lock
407
+ or lock is threading.RLock
408
+ or lock is _thread.allocate_lock
409
+ ):
390
410
  return typing.cast(typing.Type[AbstractContextManager], lock)
391
411
 
392
412
  if not hasattr(lock, "__enter__"):
@@ -403,7 +423,10 @@ def cached(
403
423
  copy_level: int = 1,
404
424
  postprocess: _PostProcess | None = postprocess_copy_mutables,
405
425
  lock: (
406
- typing.Type[AbstractContextManager] | typing.Type[AbstractAsyncContextManager] | bool | None
426
+ typing.Type[AbstractContextManager]
427
+ | typing.Type[AbstractAsyncContextManager]
428
+ | bool
429
+ | None
407
430
  ) = True,
408
431
  ) -> typing.Callable[[FT], FT]:
409
432
  """
@@ -480,7 +503,9 @@ def cached(
480
503
  lock_type = _cast_lock(iscoroutinefunction, lock)
481
504
 
482
505
  if not iscoroutinefunction and inspect.iscoroutinefunction(callback):
483
- raise TypeError("For sync functions, you cannot use a asynchronous callback")
506
+ raise TypeError(
507
+ "For sync functions, you cannot use a asynchronous callback"
508
+ )
484
509
 
485
510
  if lock_type:
486
511
  builder = _async_cached_wrapper if iscoroutinefunction else _cached_wrapper
@@ -2,5 +2,6 @@ maturin
2
2
  pytest
3
3
  hypothesis
4
4
  pytest-asyncio
5
+ pytest-xdist
5
6
  mkdocs-material
6
7
  mkdocstrings[python]
@@ -0,0 +1,21 @@
1
+ pub(crate) use self::inner::do_alloc;
2
+ #[cfg(test)]
3
+ pub(crate) use self::inner::AllocError;
4
+ pub(crate) use self::inner::Allocator;
5
+ pub(crate) use self::inner::Global;
6
+
7
+ mod inner {
8
+ #[cfg(test)]
9
+ pub(crate) use allocator_api2::alloc::AllocError;
10
+ pub(crate) use allocator_api2::alloc::Allocator;
11
+ pub(crate) use allocator_api2::alloc::Global;
12
+ use core::alloc::Layout;
13
+ use core::ptr::NonNull;
14
+
15
+ pub(crate) fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<[u8]>, ()> {
16
+ match alloc.allocate(layout) {
17
+ Ok(ptr) => Ok(ptr),
18
+ Err(_) => Err(()),
19
+ }
20
+ }
21
+ }
@@ -4,7 +4,6 @@ use super::control::Tag;
4
4
  use super::control::TagSliceExt;
5
5
  use super::scopeguard::guard;
6
6
  use super::scopeguard::ScopeGuard;
7
- use super::util::invalid_mut;
8
7
  use super::util::likely;
9
8
  use super::util::unlikely;
10
9
  use super::TryReserveError;
@@ -333,7 +332,7 @@ impl<T> Bucket<T> {
333
332
  // won't overflow because index must be less than length (bucket_mask)
334
333
  // and bucket_mask is guaranteed to be less than `isize::MAX`
335
334
  // (see TableLayout::calculate_layout_for method)
336
- invalid_mut(index + 1)
335
+ ptr::without_provenance_mut(index + 1)
337
336
  } else {
338
337
  unsafe { base.as_ptr().sub(index) }
339
338
  };
@@ -422,7 +421,7 @@ impl<T> Bucket<T> {
422
421
  if T::IS_ZERO_SIZED {
423
422
  // Just return an arbitrary ZST pointer which is properly aligned
424
423
  // invalid pointer is good enough for ZST
425
- invalid_mut(mem::align_of::<T>())
424
+ ptr::without_provenance_mut(mem::align_of::<T>())
426
425
  } else {
427
426
  unsafe { self.ptr.as_ptr().sub(1) }
428
427
  }
@@ -470,7 +469,7 @@ impl<T> Bucket<T> {
470
469
  unsafe fn next_n(&self, offset: usize) -> Self {
471
470
  let ptr = if T::IS_ZERO_SIZED {
472
471
  // invalid pointer is good enough for ZST
473
- invalid_mut(self.ptr.as_ptr() as usize + offset)
472
+ ptr::without_provenance_mut(self.ptr.as_ptr() as usize + offset)
474
473
  } else {
475
474
  unsafe { self.ptr.as_ptr().sub(offset) }
476
475
  };
@@ -3479,7 +3478,7 @@ impl<T, A: Allocator + Default> Default for RawTable<T, A> {
3479
3478
  }
3480
3479
  }
3481
3480
 
3482
- unsafe impl<#[may_dangle] T, A: Allocator> Drop for RawTable<T, A> {
3481
+ impl<T, A: Allocator> Drop for RawTable<T, A> {
3483
3482
  #[cfg_attr(feature = "inline-more", inline)]
3484
3483
  fn drop(&mut self) {
3485
3484
  // SAFETY:
@@ -0,0 +1,23 @@
1
+ #[inline(always)]
2
+ #[cold]
3
+ fn cold_path() {}
4
+
5
+ #[inline(always)]
6
+ pub(crate) fn likely(b: bool) -> bool {
7
+ if b {
8
+ true
9
+ } else {
10
+ cold_path();
11
+ false
12
+ }
13
+ }
14
+
15
+ #[inline(always)]
16
+ pub(crate) fn unlikely(b: bool) -> bool {
17
+ if b {
18
+ cold_path();
19
+ true
20
+ } else {
21
+ false
22
+ }
23
+ }
@@ -242,7 +242,7 @@ impl<T> Default for LazyHeap<T> {
242
242
  }
243
243
  }
244
244
 
245
- unsafe impl<#[may_dangle] T> Drop for LazyHeap<T> {
245
+ impl<T> Drop for LazyHeap<T> {
246
246
  fn drop(&mut self) {
247
247
  struct DropGuard<'a, T>(&'a mut LazyHeap<T>);
248
248
 
@@ -296,7 +296,7 @@ impl<T> LinkedList<T> {
296
296
  }
297
297
  }
298
298
 
299
- unsafe impl<#[may_dangle] T> Drop for LinkedList<T> {
299
+ impl<T> Drop for LinkedList<T> {
300
300
  fn drop(&mut self) {
301
301
  struct DropGuard<'a, T>(&'a mut LinkedList<T>);
302
302
 
@@ -103,7 +103,7 @@ impl<T> OnceInit<T> {
103
103
  /// Panics if called before [`set`](Self::set) has completed.
104
104
  #[inline]
105
105
  pub fn get(&self) -> &T {
106
- if std::hint::likely(self.0.state.load(atomic::Ordering::Acquire) == INIT) {
106
+ if crate::hashbrown::util::likely(self.0.state.load(atomic::Ordering::Acquire) == INIT) {
107
107
  // SAFETY: state == INIT guarantees `value` was fully written and is valid.
108
108
  unsafe { (*self.0.value.get()).assume_init_ref() }
109
109
  } else {
@@ -15,7 +15,7 @@ pub unsafe fn pyobject_hash(
15
15
  arg1: *mut pyo3::ffi::PyObject,
16
16
  ) -> pyo3::PyResult<u64> {
17
17
  let py_hash = pyo3::ffi::PyObject_Hash(arg1);
18
- if std::hint::unlikely(py_hash == -1) {
18
+ if crate::hashbrown::util::unlikely(py_hash == -1) {
19
19
  // SAFETY: PyObject_Hash never returns -1 on success.
20
20
  return Err(pyo3::PyErr::take(py).unwrap());
21
21
  }
@@ -1,7 +1,3 @@
1
- #![feature(allocator_api)]
2
- #![feature(dropck_eyepatch)]
3
- #![feature(likely_unlikely)]
4
-
5
1
  #[macro_use]
6
2
  mod macro_rules;
7
3
  mod hashbrown;
@@ -71,22 +67,22 @@ mod _core {
71
67
  #[pymodule_export]
72
68
  use crate::pyclasses::ttlcache::PyTTLCacheItems;
73
69
  #[pymodule_export]
70
+ use crate::pyclasses::ttlcache::PyTTLCacheItemsWithExpire;
71
+ #[pymodule_export]
74
72
  use crate::pyclasses::ttlcache::PyTTLCacheKeys;
75
73
  #[pymodule_export]
76
74
  use crate::pyclasses::ttlcache::PyTTLCacheValues;
77
- #[pymodule_export]
78
- use crate::pyclasses::ttlcache::PyTTLCacheItemsWithExpire;
79
75
 
80
76
  #[pymodule_export]
81
77
  use crate::pyclasses::vttlcache::PyVTTLCache;
82
78
  #[pymodule_export]
83
79
  use crate::pyclasses::vttlcache::PyVTTLCacheItems;
84
80
  #[pymodule_export]
81
+ use crate::pyclasses::vttlcache::PyVTTLCacheItemsWithExpire;
82
+ #[pymodule_export]
85
83
  use crate::pyclasses::vttlcache::PyVTTLCacheKeys;
86
84
  #[pymodule_export]
87
85
  use crate::pyclasses::vttlcache::PyVTTLCacheValues;
88
- #[pymodule_export]
89
- use crate::pyclasses::vttlcache::PyVTTLCacheItemsWithExpire;
90
86
 
91
87
  #[pymodule_init]
92
88
  pub fn init(m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> {
@@ -299,7 +299,8 @@ impl PolicyExt for FIFOPolicy {
299
299
  let front = unsafe { front.unwrap_unchecked() };
300
300
 
301
301
  let eq = |index: &usize| Ok::<_, pyo3::PyErr>(*index - self.front_offset == 0);
302
- if std::hint::unlikely(self.table.remove_entry(front.key().hash(), eq)?.is_none()) {
302
+ let removed_key_table = self.table.remove_entry(front.key().hash(), eq).unwrap();
303
+ if crate::hashbrown::util::unlikely(removed_key_table.is_none()) {
303
304
  unreachable!("popitem key not found in table");
304
305
  }
305
306
 
@@ -322,12 +322,8 @@ impl TTLPolicy {
322
322
  }
323
323
 
324
324
  let eq = |index: &usize| Ok::<_, pyo3::PyErr>((*index - self.front_offset) == 0);
325
- if std::hint::unlikely(
326
- self.table
327
- .remove_entry(handle.key().hash(), eq)
328
- .unwrap()
329
- .is_none(),
330
- ) {
325
+ let removed_key_table = self.table.remove_entry(handle.key().hash(), eq).unwrap();
326
+ if crate::hashbrown::util::unlikely(removed_key_table.is_none()) {
331
327
  unreachable!("popitem key not found in table");
332
328
  }
333
329
 
@@ -430,7 +426,8 @@ impl PolicyExt for TTLPolicy {
430
426
  let front = unsafe { front.unwrap_unchecked() };
431
427
 
432
428
  let eq = |index: &usize| Ok::<_, pyo3::PyErr>(*index - self.front_offset == 0);
433
- if std::hint::unlikely(self.table.remove_entry(front.key().hash(), eq)?.is_none()) {
429
+ let removed_key_table = self.table.remove_entry(front.key().hash(), eq).unwrap();
430
+ if crate::hashbrown::util::unlikely(removed_key_table.is_none()) {
434
431
  unreachable!("popitem key not found in table");
435
432
  }
436
433
 
@@ -632,7 +632,7 @@ async def test_async_recursive_cached_issue_54(
632
632
  def test_recursive_cached(random_cache_impl: type[cachebox.BaseCacheImpl]):
633
633
  obj = random_cache_impl(10)
634
634
 
635
- @cachebox.cached(obj)
635
+ @cachebox.cached(obj, lock=False)
636
636
  def factorial(n):
637
637
  if n < 0:
638
638
  raise ValueError
@@ -654,7 +654,7 @@ def test_recursive_threading_cached():
654
654
 
655
655
  obj = cachebox.LRUCache(10)
656
656
 
657
- @cachebox.cached(obj)
657
+ @cachebox.cached(obj, lock=False)
658
658
  def factorial(n):
659
659
  if n < 0:
660
660
  raise ValueError
@@ -680,7 +680,7 @@ def test_recursive_threading_cached():
680
680
  async def test_recursive_asyncio_cached():
681
681
  obj = cachebox.LRUCache(10)
682
682
 
683
- @cachebox.cached(obj)
683
+ @cachebox.cached(obj, lock=False)
684
684
  async def factorial(n) -> int:
685
685
  if n < 0:
686
686
  raise ValueError
@@ -742,3 +742,124 @@ async def test_async_handling_pending_errors():
742
742
  await calc(1, 2, 3, exception=True)
743
743
 
744
744
  assert await calc(1, 2, 3, exception=False) == 6
745
+
746
+
747
+ def test_misses_should_increment_without_lock():
748
+ calls = {"n": 0}
749
+
750
+ @cachebox.cached(cachebox.LRUCache(0), lock=False)
751
+ def f(x):
752
+ calls["n"] += 1
753
+ return x * 2
754
+
755
+ assert f(1) == 2 # miss
756
+ assert f(1) == 2 # hit
757
+ assert f(2) == 4 # miss
758
+
759
+ info = cachebox.get_cached_cache_info(f)
760
+ print(info)
761
+
762
+ assert info.misses == 2
763
+ assert info.hits == 1
764
+
765
+
766
+ @pytest.mark.asyncio
767
+ async def test_ignore_path_should_return_awaited_value():
768
+ @cachebox.cached(cachebox.LRUCache(0), lock=False)
769
+ async def f(x):
770
+ await asyncio.sleep(0)
771
+ return x * 2
772
+
773
+ result = await f(
774
+ 5,
775
+ cachebox__ignore=True, # type: ignore
776
+ )
777
+ assert not asyncio.iscoroutine(result)
778
+ assert result == 10
779
+
780
+
781
+ def test_locks_should_not_leak_on_exception():
782
+ @cachebox.cached(cachebox.LRUCache(0)) # lock پیش‌فرض True است
783
+ def f(x):
784
+ raise ValueError("boom")
785
+
786
+ for i in range(50):
787
+ with pytest.raises(ValueError):
788
+ f(i)
789
+
790
+ locks_cache = None
791
+ for cell in f.__closure__: # type: ignore
792
+ try:
793
+ val = cell.cell_contents
794
+ except ValueError:
795
+ continue
796
+
797
+ if isinstance(val, cachebox.Cache):
798
+ locks_cache = val
799
+ break
800
+
801
+ assert locks_cache is not None, "We couldn't find locks_cache"
802
+ print("locks length after 50 failing calls:", len(locks_cache))
803
+
804
+ assert len(locks_cache) == 0
805
+
806
+
807
+ @pytest.mark.asyncio
808
+ async def test_waiters_should_not_leak_on_cancel():
809
+ started = asyncio.Event()
810
+ release = asyncio.Event()
811
+
812
+ @cachebox.cached(cachebox.LRUCache(0))
813
+ async def f(x):
814
+ started.set()
815
+ await release.wait()
816
+ return x
817
+
818
+ first = asyncio.create_task(f(1))
819
+ await started.wait()
820
+
821
+ second = asyncio.create_task(f(1))
822
+ await asyncio.sleep(0.05)
823
+
824
+ second.cancel()
825
+ with pytest.raises(asyncio.CancelledError):
826
+ await second
827
+
828
+ release.set()
829
+ result = await first
830
+ assert result == 1
831
+
832
+ locks_cache = None
833
+ for cell in f.__closure__: # type: ignore
834
+ try:
835
+ val = cell.cell_contents
836
+ except ValueError:
837
+ continue
838
+ if isinstance(val, cachebox.Cache):
839
+ locks_cache = val
840
+ break
841
+ assert locks_cache is not None
842
+
843
+ print("locks length after cancel scenario:", len(locks_cache))
844
+ assert len(locks_cache) == 0
845
+
846
+
847
+ @pytest.mark.asyncio
848
+ async def test_cancelled_error_should_propagate_to_waiters_too():
849
+ gate = asyncio.Event()
850
+
851
+ @cachebox.cached(cachebox.LRUCache(0))
852
+ async def f(x):
853
+ await gate.wait()
854
+ raise asyncio.CancelledError()
855
+
856
+ t1 = asyncio.create_task(f(1))
857
+ t2 = asyncio.create_task(f(1))
858
+ await asyncio.sleep(0.01)
859
+ gate.set()
860
+
861
+ with pytest.raises(asyncio.CancelledError):
862
+ await t1
863
+
864
+ with pytest.raises(asyncio.CancelledError):
865
+ await t2
@@ -1,13 +0,0 @@
1
- use core::ptr::NonNull;
2
-
3
- #[cfg(test)]
4
- pub(crate) use std::alloc::AllocError;
5
- use std::alloc::Layout;
6
- pub(crate) use std::alloc::{Allocator, Global};
7
-
8
- pub(crate) fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<[u8]>, ()> {
9
- match alloc.allocate(layout) {
10
- Ok(ptr) => Ok(ptr),
11
- Err(_) => Err(()),
12
- }
13
- }
@@ -1,9 +0,0 @@
1
- pub(crate) use std::hint::likely;
2
- pub(crate) use std::hint::unlikely;
3
-
4
- // FIXME: use strict provenance functions once they are stable.
5
- // Implement it with a transmute for now.
6
- #[inline(always)]
7
- pub(crate) fn invalid_mut<T>(addr: usize) -> *mut T {
8
- unsafe { core::mem::transmute(addr) }
9
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes