cachebox 6.1.2__tar.gz → 6.2.1__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.1.2 → cachebox-6.2.1}/.github/workflows/CI.yml +4 -6
  2. {cachebox-6.1.2 → cachebox-6.2.1}/.github/workflows/python-test.yml +12 -9
  3. {cachebox-6.1.2 → cachebox-6.2.1}/Cargo.lock +8 -1
  4. {cachebox-6.1.2 → cachebox-6.2.1}/Cargo.toml +2 -2
  5. {cachebox-6.1.2 → cachebox-6.2.1}/PKG-INFO +2 -2
  6. {cachebox-6.1.2 → cachebox-6.2.1}/README.md +1 -1
  7. {cachebox-6.1.2 → cachebox-6.2.1}/cachebox/_core.pyi +209 -23
  8. {cachebox-6.1.2 → cachebox-6.2.1}/cachebox/_wrappers.py +40 -13
  9. {cachebox-6.1.2 → cachebox-6.2.1}/docs/docs/api/impls.md +8 -0
  10. {cachebox-6.1.2 → cachebox-6.2.1}/requirements-dev.txt +1 -0
  11. cachebox-6.2.1/src/hashbrown/alloc.rs +21 -0
  12. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/raw.rs +4 -5
  13. cachebox-6.2.1/src/hashbrown/util.rs +23 -0
  14. {cachebox-6.1.2 → cachebox-6.2.1}/src/internal/lazyheap.rs +1 -1
  15. {cachebox-6.1.2 → cachebox-6.2.1}/src/internal/linked_list.rs +1 -1
  16. {cachebox-6.1.2 → cachebox-6.2.1}/src/internal/onceinit.rs +1 -1
  17. {cachebox-6.1.2 → cachebox-6.2.1}/src/internal/utils.rs +1 -1
  18. {cachebox-6.1.2 → cachebox-6.2.1}/src/lib.rs +4 -8
  19. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/fifopolicy.rs +2 -1
  20. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/ttlpolicy.rs +4 -7
  21. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/wrapped.rs +12 -1
  22. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/cache.rs +46 -4
  23. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/fifocache.rs +46 -4
  24. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/lfucache.rs +47 -4
  25. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/lrucache.rs +46 -4
  26. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/rrcache.rs +46 -4
  27. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/ttlcache.rs +47 -4
  28. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/vttlcache.rs +38 -2
  29. {cachebox-6.1.2 → cachebox-6.2.1}/tests/mixins.py +61 -8
  30. {cachebox-6.1.2 → cachebox-6.2.1}/tests/test_impls.py +25 -6
  31. {cachebox-6.1.2 → cachebox-6.2.1}/tests/test_utils.py +3 -3
  32. cachebox-6.1.2/src/hashbrown/alloc.rs +0 -13
  33. cachebox-6.1.2/src/hashbrown/util.rs +0 -9
  34. {cachebox-6.1.2 → cachebox-6.2.1}/.github/workflows/docs.yml +0 -0
  35. {cachebox-6.1.2 → cachebox-6.2.1}/.gitignore +0 -0
  36. {cachebox-6.1.2 → cachebox-6.2.1}/LICENSE +0 -0
  37. {cachebox-6.1.2 → cachebox-6.2.1}/LICENSE-THIRD-PARTY +0 -0
  38. {cachebox-6.1.2 → cachebox-6.2.1}/build.rs +0 -0
  39. {cachebox-6.1.2 → cachebox-6.2.1}/cachebox/__init__.py +0 -0
  40. {cachebox-6.1.2 → cachebox-6.2.1}/cachebox/_cachebox.py +0 -0
  41. {cachebox-6.1.2 → cachebox-6.2.1}/cachebox/utils.py +0 -0
  42. {cachebox-6.1.2 → cachebox-6.2.1}/docs/docs/api/index.md +0 -0
  43. {cachebox-6.1.2 → cachebox-6.2.1}/docs/docs/api/utils.md +0 -0
  44. {cachebox-6.1.2 → cachebox-6.2.1}/docs/docs/getting-started.md +0 -0
  45. {cachebox-6.1.2 → cachebox-6.2.1}/docs/docs/index.md +0 -0
  46. {cachebox-6.1.2 → cachebox-6.2.1}/docs/docs/installation.md +0 -0
  47. {cachebox-6.1.2 → cachebox-6.2.1}/docs/docs/migration.md +0 -0
  48. {cachebox-6.1.2 → cachebox-6.2.1}/docs/docs/tips.md +0 -0
  49. {cachebox-6.1.2 → cachebox-6.2.1}/docs/mkdocs.yml +0 -0
  50. {cachebox-6.1.2 → cachebox-6.2.1}/pyproject.toml +0 -0
  51. {cachebox-6.1.2 → cachebox-6.2.1}/rustfmt.toml +0 -0
  52. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/control/bitmask.rs +0 -0
  53. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/control/group/generic.rs +0 -0
  54. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/control/group/lsx.rs +0 -0
  55. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/control/group/mod.rs +0 -0
  56. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/control/group/neon.rs +0 -0
  57. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/control/group/sse2.rs +0 -0
  58. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/control/mod.rs +0 -0
  59. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/control/tag.rs +0 -0
  60. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/mod.rs +0 -0
  61. {cachebox-6.1.2 → cachebox-6.2.1}/src/hashbrown/scopeguard.rs +0 -0
  62. {cachebox-6.1.2 → cachebox-6.2.1}/src/internal/alias.rs +0 -0
  63. {cachebox-6.1.2 → cachebox-6.2.1}/src/internal/mod.rs +0 -0
  64. {cachebox-6.1.2 → cachebox-6.2.1}/src/internal/pickle.rs +0 -0
  65. {cachebox-6.1.2 → cachebox-6.2.1}/src/macro_rules.rs +0 -0
  66. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/common.rs +0 -0
  67. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/lfupolicy.rs +0 -0
  68. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/lrupolicy.rs +0 -0
  69. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/mod.rs +0 -0
  70. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/nopolicy.rs +0 -0
  71. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/rrpolicy.rs +0 -0
  72. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/traits.rs +0 -0
  73. {cachebox-6.1.2 → cachebox-6.2.1}/src/policies/vttlpolicy.rs +0 -0
  74. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/base.rs +0 -0
  75. {cachebox-6.1.2 → cachebox-6.2.1}/src/pyclasses/mod.rs +0 -0
  76. {cachebox-6.1.2 → cachebox-6.2.1}/src/typeref.rs +0 -0
  77. {cachebox-6.1.2 → cachebox-6.2.1}/tests/__init__.py +0 -0
  78. {cachebox-6.1.2 → cachebox-6.2.1}/tests/conftest.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.1.2"
40
+ version = "6.2.1"
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.1.2"
3
+ version = "6.2.1"
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.1.2
3
+ Version: 6.2.1
4
4
  Classifier: Programming Language :: Python :: Implementation :: CPython
5
5
  Classifier: Programming Language :: Python :: Implementation :: PyPy
6
6
  Classifier: Programming Language :: Python :: 3
@@ -161,7 +161,7 @@ assert cache.get("key") == "value"
161
161
  ```
162
162
 
163
163
  ## Learn more
164
- Read the documentation for full information and learn more: [**Documentation**](https://awolverp.github.com/cachebox)
164
+ Read the documentation for full information and learn more: [**Documentation**](https://awolverp.github.io/cachebox/)
165
165
 
166
166
  ## License
167
167
  This repository is licensed under the [MIT License](LICENSE)
@@ -130,7 +130,7 @@ assert cache.get("key") == "value"
130
130
  ```
131
131
 
132
132
  ## Learn more
133
- Read the documentation for full information and learn more: [**Documentation**](https://awolverp.github.com/cachebox)
133
+ Read the documentation for full information and learn more: [**Documentation**](https://awolverp.github.io/cachebox/)
134
134
 
135
135
  ## License
136
136
  This repository is licensed under the [MIT License](LICENSE)
@@ -157,7 +157,9 @@ class BaseCacheImpl(typing.Generic[KT, VT]):
157
157
  *args: typing.Any,
158
158
  **kwargs: typing.Any,
159
159
  ) -> None: ...
160
- def get(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]: ...
160
+ def get(
161
+ self, key: KT, default: typing.Optional[DT] = None
162
+ ) -> typing.Union[VT, DT]: ...
161
163
  def __getitem__(self, key: KT) -> VT: ...
162
164
  def setdefault(
163
165
  self,
@@ -166,6 +168,13 @@ class BaseCacheImpl(typing.Generic[KT, VT]):
166
168
  *args: typing.Any,
167
169
  **kwargs: typing.Any,
168
170
  ) -> typing.Optional[VT | DT]: ...
171
+ def setdefault_with(
172
+ self,
173
+ key: KT,
174
+ factory: typing.Callable[[], DT],
175
+ *args: typing.Any,
176
+ **kwargs: typing.Any,
177
+ ) -> VT | DT: ...
169
178
  def pop(self, key: KT, default: DT = ...) -> typing.Union[VT, DT]:
170
179
  """
171
180
  Removes the specified key and returns the corresponding value.
@@ -352,15 +361,40 @@ class Cache(BaseCacheImpl[KT, VT]):
352
361
  default: typing.Optional[DT] = None,
353
362
  ) -> typing.Optional[VT | DT]:
354
363
  """
355
- Inserts ``key`` with ``default`` as its value if the key is absent.
364
+ Get `key`s value, or atomatically insert `default` and return it.
365
+ If `key` exists, its current value is returned and `default` is ignored.
366
+ Otherwise `default` is inserted for `key` and returned.
356
367
 
357
368
  Args:
358
369
  key: The key to look up or insert.
359
370
  default: The value to insert if ``key`` is not in the cache.
360
371
  Defaults to ``None``.
361
372
 
362
- Returns:
363
- The existing value if ``key`` is present, otherwise ``default``.
373
+ Note:
374
+ Use `setdefault_with`, if computing the value is expensive or has side
375
+ effectes.
376
+ """
377
+ ...
378
+
379
+ def setdefault_with(
380
+ self,
381
+ key: KT,
382
+ factory: typing.Callable[[], DT],
383
+ ) -> VT | DT:
384
+ """
385
+ Get `key`s value, or atomatically create and insert one via `factory`.
386
+ If `key` exists, its current value is returned and `factory` is not called.
387
+ Otherwise `factory` is called exactly once under an internal lock, its
388
+ result is inserted and returned.
389
+
390
+ Args:
391
+ key: The key to look up or insert.
392
+ factory: The factory to call and get default value from if ``key`` is not in the cache.
393
+
394
+ Warning:
395
+ `factory` must not call back into this cache (deadlock risk) or block
396
+ for long. If `factory` raises, nothing is inserted and the exception
397
+ propagates.
364
398
  """
365
399
  ...
366
400
 
@@ -508,15 +542,40 @@ class FIFOCache(BaseCacheImpl[KT, VT]):
508
542
  default: typing.Optional[DT] = None,
509
543
  ) -> typing.Optional[VT | DT]:
510
544
  """
511
- Inserts ``key`` with ``default`` as its value if the key is absent.
545
+ Get `key`s value, or atomatically insert `default` and return it.
546
+ If `key` exists, its current value is returned and `default` is ignored.
547
+ Otherwise `default` is inserted for `key` and returned.
512
548
 
513
549
  Args:
514
550
  key: The key to look up or insert.
515
551
  default: The value to insert if ``key`` is not in the cache.
516
552
  Defaults to ``None``.
517
553
 
518
- Returns:
519
- The existing value if ``key`` is present, otherwise ``default``.
554
+ Note:
555
+ Use `setdefault_with`, if computing the value is expensive or has side
556
+ effectes.
557
+ """
558
+ ...
559
+
560
+ def setdefault_with(
561
+ self,
562
+ key: KT,
563
+ factory: typing.Callable[[], DT],
564
+ ) -> VT | DT:
565
+ """
566
+ Get `key`s value, or atomatically create and insert one via `factory`.
567
+ If `key` exists, its current value is returned and `factory` is not called.
568
+ Otherwise `factory` is called exactly once under an internal lock, its
569
+ result is inserted and returned.
570
+
571
+ Args:
572
+ key: The key to look up or insert.
573
+ factory: The factory to call and get default value from if ``key`` is not in the cache.
574
+
575
+ Warning:
576
+ `factory` must not call back into this cache (deadlock risk) or block
577
+ for long. If `factory` raises, nothing is inserted and the exception
578
+ propagates.
520
579
  """
521
580
  ...
522
581
 
@@ -697,15 +756,40 @@ class RRCache(BaseCacheImpl[KT, VT]):
697
756
  default: typing.Optional[DT] = None,
698
757
  ) -> typing.Optional[VT | DT]:
699
758
  """
700
- Inserts ``key`` with ``default`` as its value if the key is absent.
759
+ Get `key`s value, or atomatically insert `default` and return it.
760
+ If `key` exists, its current value is returned and `default` is ignored.
761
+ Otherwise `default` is inserted for `key` and returned.
701
762
 
702
763
  Args:
703
764
  key: The key to look up or insert.
704
765
  default: The value to insert if ``key`` is not in the cache.
705
766
  Defaults to ``None``.
706
767
 
707
- Returns:
708
- The existing value if ``key`` is present, otherwise ``default``.
768
+ Note:
769
+ Use `setdefault_with`, if computing the value is expensive or has side
770
+ effectes.
771
+ """
772
+ ...
773
+
774
+ def setdefault_with(
775
+ self,
776
+ key: KT,
777
+ factory: typing.Callable[[], DT],
778
+ ) -> VT | DT:
779
+ """
780
+ Get `key`s value, or atomatically create and insert one via `factory`.
781
+ If `key` exists, its current value is returned and `factory` is not called.
782
+ Otherwise `factory` is called exactly once under an internal lock, its
783
+ result is inserted and returned.
784
+
785
+ Args:
786
+ key: The key to look up or insert.
787
+ factory: The factory to call and get default value from if ``key`` is not in the cache.
788
+
789
+ Warning:
790
+ `factory` must not call back into this cache (deadlock risk) or block
791
+ for long. If `factory` raises, nothing is inserted and the exception
792
+ propagates.
709
793
  """
710
794
  ...
711
795
 
@@ -879,15 +963,40 @@ class LRUCache(BaseCacheImpl[KT, VT]):
879
963
  default: typing.Optional[DT] = None,
880
964
  ) -> typing.Optional[VT | DT]:
881
965
  """
882
- Inserts ``key`` with ``default`` as its value if the key is absent.
966
+ Get `key`s value, or atomatically insert `default` and return it.
967
+ If `key` exists, its current value is returned and `default` is ignored.
968
+ Otherwise `default` is inserted for `key` and returned.
883
969
 
884
970
  Args:
885
971
  key: The key to look up or insert.
886
972
  default: The value to insert if ``key`` is not in the cache.
887
973
  Defaults to ``None``.
888
974
 
889
- Returns:
890
- The existing value if ``key`` is present, otherwise ``default``.
975
+ Note:
976
+ Use `setdefault_with`, if computing the value is expensive or has side
977
+ effectes.
978
+ """
979
+ ...
980
+
981
+ def setdefault_with(
982
+ self,
983
+ key: KT,
984
+ factory: typing.Callable[[], DT],
985
+ ) -> VT | DT:
986
+ """
987
+ Get `key`s value, or atomatically create and insert one via `factory`.
988
+ If `key` exists, its current value is returned and `factory` is not called.
989
+ Otherwise `factory` is called exactly once under an internal lock, its
990
+ result is inserted and returned.
991
+
992
+ Args:
993
+ key: The key to look up or insert.
994
+ factory: The factory to call and get default value from if ``key`` is not in the cache.
995
+
996
+ Warning:
997
+ `factory` must not call back into this cache (deadlock risk) or block
998
+ for long. If `factory` raises, nothing is inserted and the exception
999
+ propagates.
891
1000
  """
892
1001
  ...
893
1002
 
@@ -1102,15 +1211,40 @@ class LFUCache(BaseCacheImpl[KT, VT]):
1102
1211
  default: typing.Optional[DT] = None,
1103
1212
  ) -> typing.Optional[VT | DT]:
1104
1213
  """
1105
- Inserts ``key`` with ``default`` as its value if the key is absent.
1214
+ Get `key`s value, or atomatically insert `default` and return it.
1215
+ If `key` exists, its current value is returned and `default` is ignored.
1216
+ Otherwise `default` is inserted for `key` and returned.
1106
1217
 
1107
1218
  Args:
1108
1219
  key: The key to look up or insert.
1109
1220
  default: The value to insert if ``key`` is not in the cache.
1110
1221
  Defaults to ``None``.
1111
1222
 
1112
- Returns:
1113
- The existing value if ``key`` is present, otherwise ``default``.
1223
+ Note:
1224
+ Use `setdefault_with`, if computing the value is expensive or has side
1225
+ effectes.
1226
+ """
1227
+ ...
1228
+
1229
+ def setdefault_with(
1230
+ self,
1231
+ key: KT,
1232
+ factory: typing.Callable[[], DT],
1233
+ ) -> VT | DT:
1234
+ """
1235
+ Get `key`s value, or atomatically create and insert one via `factory`.
1236
+ If `key` exists, its current value is returned and `factory` is not called.
1237
+ Otherwise `factory` is called exactly once under an internal lock, its
1238
+ result is inserted and returned.
1239
+
1240
+ Args:
1241
+ key: The key to look up or insert.
1242
+ factory: The factory to call and get default value from if ``key`` is not in the cache.
1243
+
1244
+ Warning:
1245
+ `factory` must not call back into this cache (deadlock risk) or block
1246
+ for long. If `factory` raises, nothing is inserted and the exception
1247
+ propagates.
1114
1248
  """
1115
1249
  ...
1116
1250
 
@@ -1284,15 +1418,40 @@ class TTLCache(BaseCacheImpl[KT, VT]):
1284
1418
  default: typing.Optional[DT] = None,
1285
1419
  ) -> typing.Optional[VT | DT]:
1286
1420
  """
1287
- Inserts ``key`` with ``default`` as its value if the key is absent.
1421
+ Get `key`s value, or atomatically insert `default` and return it.
1422
+ If `key` exists, its current value is returned and `default` is ignored.
1423
+ Otherwise `default` is inserted for `key` and returned.
1288
1424
 
1289
1425
  Args:
1290
1426
  key: The key to look up or insert.
1291
1427
  default: The value to insert if ``key`` is not in the cache.
1292
1428
  Defaults to ``None``.
1293
1429
 
1294
- Returns:
1295
- The existing value if ``key`` is present, otherwise ``default``.
1430
+ Note:
1431
+ Use `setdefault_with`, if computing the value is expensive or has side
1432
+ effectes.
1433
+ """
1434
+ ...
1435
+
1436
+ def setdefault_with(
1437
+ self,
1438
+ key: KT,
1439
+ factory: typing.Callable[[], DT],
1440
+ ) -> VT | DT:
1441
+ """
1442
+ Get `key`s value, or atomatically create and insert one via `factory`.
1443
+ If `key` exists, its current value is returned and `factory` is not called.
1444
+ Otherwise `factory` is called exactly once under an internal lock, its
1445
+ result is inserted and returned.
1446
+
1447
+ Args:
1448
+ key: The key to look up or insert.
1449
+ factory: The factory to call and get default value from if ``key`` is not in the cache.
1450
+
1451
+ Warning:
1452
+ `factory` must not call back into this cache (deadlock risk) or block
1453
+ for long. If `factory` raises, nothing is inserted and the exception
1454
+ propagates.
1296
1455
  """
1297
1456
  ...
1298
1457
 
@@ -1524,16 +1683,43 @@ class VTTLCache(BaseCacheImpl[KT, VT]):
1524
1683
  ttl: float | timedelta | datetime | None = None,
1525
1684
  ) -> typing.Optional[VT | DT]:
1526
1685
  """
1527
- Inserts ``key`` with ``default`` as its value if the key is absent.
1686
+ Get `key`s value, or atomatically insert `default` and return it.
1687
+ If `key` exists, its current value is returned and `default` is ignored.
1688
+ Otherwise `default` is inserted for `key` and returned.
1689
+
1690
+ Note:
1691
+ Use `setdefault_with`, if computing the value is expensive or has side
1692
+ effectes.
1528
1693
 
1529
1694
  Args:
1530
1695
  key: The key to look up or insert.
1531
1696
  default: The value to insert if ``key`` is not in the cache.
1532
1697
  Defaults to ``None``.
1533
- ttl: An optional time-to-live duration for items.
1698
+ ttl: An optional time-to-live duration for item.
1699
+ """
1700
+ ...
1534
1701
 
1535
- Returns:
1536
- The existing value if ``key`` is present, otherwise ``default``.
1702
+ def setdefault_with(
1703
+ self,
1704
+ key: KT,
1705
+ factory: typing.Callable[[], DT],
1706
+ ttl: float | timedelta | datetime | None = None,
1707
+ ) -> VT | DT:
1708
+ """
1709
+ Get `key`s value, or atomatically create and insert one via `factory`.
1710
+ If `key` exists, its current value is returned and `factory` is not called.
1711
+ Otherwise `factory` is called exactly once under an internal lock, its
1712
+ result is inserted and returned.
1713
+
1714
+ Args:
1715
+ key: The key to look up or insert.
1716
+ factory: The factory to call and get default value from if ``key`` is not in the cache.
1717
+ ttl: An optional time-to-live duration for item.
1718
+
1719
+ Warning:
1720
+ `factory` must not call back into this cache (deadlock risk) or block
1721
+ for long. If `factory` raises, nothing is inserted and the exception
1722
+ propagates.
1537
1723
  """
1538
1724
  ...
1539
1725
 
@@ -3,7 +3,7 @@ import typing
3
3
  from collections import namedtuple
4
4
  from contextlib import AbstractAsyncContextManager, AbstractContextManager
5
5
 
6
- from cachebox import BaseCacheImpl
6
+ from cachebox._core import BaseCacheImpl, Cache
7
7
 
8
8
  _PostProcess: typing.TypeAlias = typing.Callable[[typing.Any], typing.Any]
9
9
  _Callback: typing.TypeAlias = typing.Callable[[int, typing.Any, typing.Any], typing.Any]
@@ -71,7 +71,9 @@ def _cached_wrapper_without_lock(
71
71
 
72
72
  # Per-instance caches receive `self` as args[0]; exclude it from the ke
73
73
  _make_key = (
74
- (lambda a, k: key_maker(*a[1:], **k)) if cache_is_fn else (lambda a, k: key_maker(*a, **k))
74
+ (lambda a, k: key_maker(*a[1:], **k))
75
+ if cache_is_fn
76
+ else (lambda a, k: key_maker(*a, **k))
75
77
  )
76
78
 
77
79
  hits = 0
@@ -143,7 +145,9 @@ def _async_cached_wrapper_without_lock(
143
145
 
144
146
  # Per-instance caches receive `self` as args[0]; exclude it from the ke
145
147
  _make_key = (
146
- (lambda a, k: key_maker(*a[1:], **k)) if cache_is_fn else (lambda a, k: key_maker(*a, **k))
148
+ (lambda a, k: key_maker(*a[1:], **k))
149
+ if cache_is_fn
150
+ else (lambda a, k: key_maker(*a, **k))
147
151
  )
148
152
 
149
153
  hits = 0
@@ -205,13 +209,15 @@ def _cached_wrapper(
205
209
 
206
210
  # Per-instance caches receive `self` as args[0]; exclude it from the key
207
211
  _make_key = (
208
- (lambda a, k: key_maker(*a[1:], **k)) if cache_is_fn else (lambda a, k: key_maker(*a, **k))
212
+ (lambda a, k: key_maker(*a[1:], **k))
213
+ if cache_is_fn
214
+ else (lambda a, k: key_maker(*a, **k))
209
215
  )
210
216
 
211
217
  hits = 0
212
218
  misses = 0
213
219
 
214
- locks: dict[typing.Hashable, _Lock] = {}
220
+ locks: Cache[typing.Hashable, _Lock] = Cache(0)
215
221
  pending_errors: dict[typing.Hashable, BaseException] = {}
216
222
 
217
223
  def _wrapped(*args, **kwds):
@@ -237,9 +243,7 @@ def _cached_wrapper(
237
243
  except KeyError:
238
244
  pass
239
245
 
240
- lock = locks.get(key)
241
- if lock is None:
242
- locks[key] = lock = _Lock(lock_type())
246
+ lock = locks.setdefault_with(key, lambda: _Lock(lock_type()))
243
247
 
244
248
  # Acquire the per-key lock so that only one task computes the value
245
249
  # while the rest wait.
@@ -307,12 +311,37 @@ def _async_cached_wrapper(
307
311
  ):
308
312
  cache_is_fn = callable(cache)
309
313
  _make_key = (
310
- (lambda a, k: key_maker(*a[1:], **k)) if cache_is_fn else (lambda a, k: key_maker(*a, **k))
314
+ (lambda a, k: key_maker(*a[1:], **k))
315
+ if cache_is_fn
316
+ else (lambda a, k: key_maker(*a, **k))
311
317
  )
312
318
 
313
319
  hits = 0
314
320
  misses = 0
315
- locks: dict[typing.Hashable, _AsyncLock] = {}
321
+
322
+ # See _cached_wrapper
323
+ 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
+
316
345
  pending_errors: dict[typing.Hashable, BaseException] = {}
317
346
 
318
347
  async def _wrapped(*args, **kwds):
@@ -335,9 +364,7 @@ def _async_cached_wrapper(
335
364
  except KeyError:
336
365
  pass
337
366
 
338
- lock = locks.get(key)
339
- if lock is None:
340
- locks[key] = lock = _AsyncLock(lock_type())
367
+ lock = locks.setdefault_with(key, lambda: _AsyncLock(lock_type()))
341
368
 
342
369
  async with lock:
343
370
  err = pending_errors.get(key)