cachetools 5.5.1__tar.gz → 5.5.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 (32) hide show
  1. {cachetools-5.5.1 → cachetools-5.5.2}/CHANGELOG.rst +10 -0
  2. {cachetools-5.5.1 → cachetools-5.5.2}/LICENSE +1 -1
  3. {cachetools-5.5.1/src/cachetools.egg-info → cachetools-5.5.2}/PKG-INFO +2 -2
  4. {cachetools-5.5.1 → cachetools-5.5.2}/README.rst +1 -1
  5. {cachetools-5.5.1 → cachetools-5.5.2}/docs/conf.py +1 -1
  6. {cachetools-5.5.1 → cachetools-5.5.2}/docs/index.rst +12 -6
  7. {cachetools-5.5.1 → cachetools-5.5.2}/src/cachetools/__init__.py +7 -128
  8. cachetools-5.5.2/src/cachetools/_decorators.py +152 -0
  9. {cachetools-5.5.1 → cachetools-5.5.2/src/cachetools.egg-info}/PKG-INFO +2 -2
  10. {cachetools-5.5.1 → cachetools-5.5.2}/src/cachetools.egg-info/SOURCES.txt +1 -0
  11. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_cached.py +24 -0
  12. {cachetools-5.5.1 → cachetools-5.5.2}/tox.ini +1 -1
  13. {cachetools-5.5.1 → cachetools-5.5.2}/MANIFEST.in +0 -0
  14. {cachetools-5.5.1 → cachetools-5.5.2}/pyproject.toml +0 -0
  15. {cachetools-5.5.1 → cachetools-5.5.2}/setup.cfg +0 -0
  16. {cachetools-5.5.1 → cachetools-5.5.2}/setup.py +0 -0
  17. {cachetools-5.5.1 → cachetools-5.5.2}/src/cachetools/func.py +0 -0
  18. {cachetools-5.5.1 → cachetools-5.5.2}/src/cachetools/keys.py +0 -0
  19. {cachetools-5.5.1 → cachetools-5.5.2}/src/cachetools.egg-info/dependency_links.txt +0 -0
  20. {cachetools-5.5.1 → cachetools-5.5.2}/src/cachetools.egg-info/top_level.txt +0 -0
  21. {cachetools-5.5.1 → cachetools-5.5.2}/tests/__init__.py +0 -0
  22. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_cache.py +0 -0
  23. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_cachedmethod.py +0 -0
  24. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_fifo.py +0 -0
  25. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_func.py +0 -0
  26. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_keys.py +0 -0
  27. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_lfu.py +0 -0
  28. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_lru.py +0 -0
  29. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_mru.py +0 -0
  30. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_rr.py +0 -0
  31. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_tlru.py +0 -0
  32. {cachetools-5.5.1 → cachetools-5.5.2}/tests/test_ttl.py +0 -0
@@ -1,3 +1,13 @@
1
+ v5.5.2 (2025-02-20)
2
+ ===================
3
+
4
+ - Reduce number of ``@cached`` lock/unlock operations.
5
+
6
+ - Improve documentation.
7
+
8
+ - Update CI environment.
9
+
10
+
1
11
  v5.5.1 (2025-01-21)
2
12
  ===================
3
13
 
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014-2024 Thomas Kemmer
3
+ Copyright (c) 2014-2025 Thomas Kemmer
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cachetools
3
- Version: 5.5.1
3
+ Version: 5.5.2
4
4
  Summary: Extensible memoizing collections and decorators
5
5
  Home-page: https://github.com/tkem/cachetools/
6
6
  Author: Thomas Kemmer
@@ -126,7 +126,7 @@ Related Projects
126
126
  License
127
127
  ------------------------------------------------------------------------
128
128
 
129
- Copyright (c) 2014-2024 Thomas Kemmer.
129
+ Copyright (c) 2014-2025 Thomas Kemmer.
130
130
 
131
131
  Licensed under the `MIT License`_.
132
132
 
@@ -100,7 +100,7 @@ Related Projects
100
100
  License
101
101
  ------------------------------------------------------------------------
102
102
 
103
- Copyright (c) 2014-2024 Thomas Kemmer.
103
+ Copyright (c) 2014-2025 Thomas Kemmer.
104
104
 
105
105
  Licensed under the `MIT License`_.
106
106
 
@@ -19,7 +19,7 @@ def get_version():
19
19
 
20
20
 
21
21
  project = "cachetools"
22
- copyright = "2014-2024 Thomas Kemmer"
22
+ copyright = "2014-2025 Thomas Kemmer"
23
23
  release, version = get_version()
24
24
 
25
25
  extensions = [
@@ -41,6 +41,12 @@ of the cache. When a cache is full, :meth:`Cache.__setitem__()` calls
41
41
  :meth:`self.popitem()` repeatedly until there is enough room for the
42
42
  item to be added.
43
43
 
44
+ .. note::
45
+
46
+ Please be aware that `maxsize` must be a positive number. If you
47
+ really want your cache to grow without bounds, use
48
+ :const:`math.inf` or something similar.
49
+
44
50
  In general, a cache's size is the total size of its item's values.
45
51
  Therefore, :class:`Cache` provides a :meth:`getsizeof` method, which
46
52
  returns the size of a given `value`. The default implementation of
@@ -50,12 +56,12 @@ making the cache's size equal to the number of its items, or
50
56
  named constructor parameter `getsizeof`, which may specify a function
51
57
  of one argument used to retrieve the size of an item's value.
52
58
 
53
- Note that the values of a :class:`Cache` are mutable by default, as
54
- are e.g. the values of a :class:`dict`. It is the user's
55
- responsibility to take care that cached values are not accidentally
56
- modified. This is especially important when using a custom
57
- `getsizeof` function, since the size of an item's value will only be
58
- computed when the item is inserted into the cache.
59
+ The values of a :class:`Cache` are mutable by default, as are e.g. the
60
+ values of a :class:`dict`. It is the user's responsibility to take
61
+ care that cached values are not accidentally modified. This is
62
+ especially important when using a custom `getsizeof` function, since
63
+ the size of an item's value will only be computed when the item is
64
+ inserted into the cache.
59
65
 
60
66
  .. note::
61
67
 
@@ -13,7 +13,7 @@ __all__ = (
13
13
  "cachedmethod",
14
14
  )
15
15
 
16
- __version__ = "5.5.1"
16
+ __version__ = "5.5.2"
17
17
 
18
18
  import collections
19
19
  import collections.abc
@@ -23,6 +23,7 @@ import random
23
23
  import time
24
24
 
25
25
  from . import keys
26
+ from ._decorators import _cached_wrapper
26
27
 
27
28
 
28
29
  class _DefaultSize:
@@ -643,150 +644,28 @@ def cached(cache, key=keys.hashkey, lock=None, info=False):
643
644
 
644
645
  def decorator(func):
645
646
  if info:
646
- hits = misses = 0
647
-
648
647
  if isinstance(cache, Cache):
649
648
 
650
- def getinfo():
651
- nonlocal hits, misses
649
+ def make_info(hits, misses):
652
650
  return _CacheInfo(hits, misses, cache.maxsize, cache.currsize)
653
651
 
654
652
  elif isinstance(cache, collections.abc.Mapping):
655
653
 
656
- def getinfo():
657
- nonlocal hits, misses
654
+ def make_info(hits, misses):
658
655
  return _CacheInfo(hits, misses, None, len(cache))
659
656
 
660
657
  else:
661
658
 
662
- def getinfo():
663
- nonlocal hits, misses
659
+ def make_info(hits, misses):
664
660
  return _CacheInfo(hits, misses, 0, 0)
665
661
 
666
- if cache is None:
667
-
668
- def wrapper(*args, **kwargs):
669
- nonlocal misses
670
- misses += 1
671
- return func(*args, **kwargs)
672
-
673
- def cache_clear():
674
- nonlocal hits, misses
675
- hits = misses = 0
676
-
677
- cache_info = getinfo
678
-
679
- elif lock is None:
680
-
681
- def wrapper(*args, **kwargs):
682
- nonlocal hits, misses
683
- k = key(*args, **kwargs)
684
- try:
685
- result = cache[k]
686
- hits += 1
687
- return result
688
- except KeyError:
689
- misses += 1
690
- v = func(*args, **kwargs)
691
- try:
692
- cache[k] = v
693
- except ValueError:
694
- pass # value too large
695
- return v
696
-
697
- def cache_clear():
698
- nonlocal hits, misses
699
- cache.clear()
700
- hits = misses = 0
701
-
702
- cache_info = getinfo
703
-
704
- else:
705
-
706
- def wrapper(*args, **kwargs):
707
- nonlocal hits, misses
708
- k = key(*args, **kwargs)
709
- try:
710
- with lock:
711
- result = cache[k]
712
- hits += 1
713
- return result
714
- except KeyError:
715
- with lock:
716
- misses += 1
717
- v = func(*args, **kwargs)
718
- # in case of a race, prefer the item already in the cache
719
- try:
720
- with lock:
721
- return cache.setdefault(k, v)
722
- except ValueError:
723
- return v # value too large
724
-
725
- def cache_clear():
726
- nonlocal hits, misses
727
- with lock:
728
- cache.clear()
729
- hits = misses = 0
730
-
731
- def cache_info():
732
- with lock:
733
- return getinfo()
734
-
662
+ wrapper = _cached_wrapper(func, cache, key, lock, make_info)
735
663
  else:
736
- if cache is None:
737
-
738
- def wrapper(*args, **kwargs):
739
- return func(*args, **kwargs)
740
-
741
- def cache_clear():
742
- pass
743
-
744
- elif lock is None:
745
-
746
- def wrapper(*args, **kwargs):
747
- k = key(*args, **kwargs)
748
- try:
749
- return cache[k]
750
- except KeyError:
751
- pass # key not found
752
- v = func(*args, **kwargs)
753
- try:
754
- cache[k] = v
755
- except ValueError:
756
- pass # value too large
757
- return v
758
-
759
- def cache_clear():
760
- cache.clear()
761
-
762
- else:
763
-
764
- def wrapper(*args, **kwargs):
765
- k = key(*args, **kwargs)
766
- try:
767
- with lock:
768
- return cache[k]
769
- except KeyError:
770
- pass # key not found
771
- v = func(*args, **kwargs)
772
- # in case of a race, prefer the item already in the cache
773
- try:
774
- with lock:
775
- return cache.setdefault(k, v)
776
- except ValueError:
777
- return v # value too large
778
-
779
- def cache_clear():
780
- with lock:
781
- cache.clear()
782
-
783
- cache_info = None
664
+ wrapper = _cached_wrapper(func, cache, key, lock, None)
784
665
 
785
666
  wrapper.cache = cache
786
667
  wrapper.cache_key = key
787
668
  wrapper.cache_lock = lock
788
- wrapper.cache_clear = cache_clear
789
- wrapper.cache_info = cache_info
790
669
 
791
670
  return functools.update_wrapper(wrapper, func)
792
671
 
@@ -0,0 +1,152 @@
1
+ """Extensible memoizing decorator helpers."""
2
+
3
+
4
+ def _cached_locked_info(func, cache, key, lock, info):
5
+ hits = misses = 0
6
+
7
+ def wrapper(*args, **kwargs):
8
+ nonlocal hits, misses
9
+ k = key(*args, **kwargs)
10
+ with lock:
11
+ try:
12
+ result = cache[k]
13
+ hits += 1
14
+ return result
15
+ except KeyError:
16
+ misses += 1
17
+ v = func(*args, **kwargs)
18
+ with lock:
19
+ try:
20
+ # in case of a race, prefer the item already in the cache
21
+ return cache.setdefault(k, v)
22
+ except ValueError:
23
+ return v # value too large
24
+
25
+ def cache_clear():
26
+ nonlocal hits, misses
27
+ with lock:
28
+ cache.clear()
29
+ hits = misses = 0
30
+
31
+ def cache_info():
32
+ with lock:
33
+ return info(hits, misses)
34
+
35
+ wrapper.cache_clear = cache_clear
36
+ wrapper.cache_info = cache_info
37
+ return wrapper
38
+
39
+
40
+ def _cached_unlocked_info(func, cache, key, info):
41
+ hits = misses = 0
42
+
43
+ def wrapper(*args, **kwargs):
44
+ nonlocal hits, misses
45
+ k = key(*args, **kwargs)
46
+ try:
47
+ result = cache[k]
48
+ hits += 1
49
+ return result
50
+ except KeyError:
51
+ misses += 1
52
+ v = func(*args, **kwargs)
53
+ try:
54
+ cache[k] = v
55
+ except ValueError:
56
+ pass # value too large
57
+ return v
58
+
59
+ def cache_clear():
60
+ nonlocal hits, misses
61
+ cache.clear()
62
+ hits = misses = 0
63
+
64
+ wrapper.cache_clear = cache_clear
65
+ wrapper.cache_info = lambda: info(hits, misses)
66
+ return wrapper
67
+
68
+
69
+ def _uncached_info(func, info):
70
+ misses = 0
71
+
72
+ def wrapper(*args, **kwargs):
73
+ nonlocal misses
74
+ misses += 1
75
+ return func(*args, **kwargs)
76
+
77
+ def cache_clear():
78
+ nonlocal misses
79
+ misses = 0
80
+
81
+ wrapper.cache_clear = cache_clear
82
+ wrapper.cache_info = lambda: info(0, misses)
83
+ return wrapper
84
+
85
+
86
+ def _cached_locked(func, cache, key, lock):
87
+ def wrapper(*args, **kwargs):
88
+ k = key(*args, **kwargs)
89
+ with lock:
90
+ try:
91
+ return cache[k]
92
+ except KeyError:
93
+ pass # key not found
94
+ v = func(*args, **kwargs)
95
+ with lock:
96
+ try:
97
+ # in case of a race, prefer the item already in the cache
98
+ return cache.setdefault(k, v)
99
+ except ValueError:
100
+ return v # value too large
101
+
102
+ def cache_clear():
103
+ with lock:
104
+ cache.clear()
105
+
106
+ wrapper.cache_clear = cache_clear
107
+ return wrapper
108
+
109
+
110
+ def _cached_unlocked(func, cache, key):
111
+ def wrapper(*args, **kwargs):
112
+ k = key(*args, **kwargs)
113
+ try:
114
+ return cache[k]
115
+ except KeyError:
116
+ pass # key not found
117
+ v = func(*args, **kwargs)
118
+ try:
119
+ cache[k] = v
120
+ except ValueError:
121
+ pass # value too large
122
+ return v
123
+
124
+ wrapper.cache_clear = lambda: cache.clear()
125
+ return wrapper
126
+
127
+
128
+ def _uncached(func):
129
+ def wrapper(*args, **kwargs):
130
+ return func(*args, **kwargs)
131
+
132
+ wrapper.cache_clear = lambda: None
133
+ return wrapper
134
+
135
+
136
+ def _cached_wrapper(func, cache, key, lock, info):
137
+ if info is not None:
138
+ if cache is None:
139
+ wrapper = _uncached_info(func, info)
140
+ elif lock is None:
141
+ wrapper = _cached_unlocked_info(func, cache, key, info)
142
+ else:
143
+ wrapper = _cached_locked_info(func, cache, key, lock, info)
144
+ else:
145
+ if cache is None:
146
+ wrapper = _uncached(func)
147
+ elif lock is None:
148
+ wrapper = _cached_unlocked(func, cache, key)
149
+ else:
150
+ wrapper = _cached_locked(func, cache, key, lock)
151
+ wrapper.cache_info = None
152
+ return wrapper
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cachetools
3
- Version: 5.5.1
3
+ Version: 5.5.2
4
4
  Summary: Extensible memoizing collections and decorators
5
5
  Home-page: https://github.com/tkem/cachetools/
6
6
  Author: Thomas Kemmer
@@ -126,7 +126,7 @@ Related Projects
126
126
  License
127
127
  ------------------------------------------------------------------------
128
128
 
129
- Copyright (c) 2014-2024 Thomas Kemmer.
129
+ Copyright (c) 2014-2025 Thomas Kemmer.
130
130
 
131
131
  Licensed under the `MIT License`_.
132
132
 
@@ -9,6 +9,7 @@ tox.ini
9
9
  docs/conf.py
10
10
  docs/index.rst
11
11
  src/cachetools/__init__.py
12
+ src/cachetools/_decorators.py
12
13
  src/cachetools/func.py
13
14
  src/cachetools/keys.py
14
15
  src/cachetools.egg-info/PKG-INFO
@@ -164,6 +164,30 @@ class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
164
164
  self.assertEqual(len(cache), 0)
165
165
  self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
166
166
 
167
+ def test_decorator_lock_info(self):
168
+ cache = self.cache(2)
169
+ lock = CountedLock()
170
+ wrapper = cachetools.cached(cache, lock=lock, info=True)(self.func)
171
+ self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
172
+ self.assertEqual(lock.count, 1)
173
+ self.assertEqual(wrapper(0), 0)
174
+ self.assertEqual(lock.count, 3)
175
+ self.assertEqual(wrapper.cache_info(), (0, 1, 2, 1))
176
+ self.assertEqual(lock.count, 4)
177
+ self.assertEqual(wrapper(1), 1)
178
+ self.assertEqual(lock.count, 6)
179
+ self.assertEqual(wrapper.cache_info(), (0, 2, 2, 2))
180
+ self.assertEqual(lock.count, 7)
181
+ self.assertEqual(wrapper(0), 0)
182
+ self.assertEqual(lock.count, 8)
183
+ self.assertEqual(wrapper.cache_info(), (1, 2, 2, 2))
184
+ self.assertEqual(lock.count, 9)
185
+ wrapper.cache_clear()
186
+ self.assertEqual(lock.count, 10)
187
+ self.assertEqual(len(cache), 0)
188
+ self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
189
+ self.assertEqual(lock.count, 11)
190
+
167
191
  def test_zero_size_cache_decorator(self):
168
192
  cache = self.cache(0)
169
193
  wrapper = cachetools.cached(cache)(self.func)
@@ -6,7 +6,7 @@ deps =
6
6
  pytest
7
7
  pytest-cov
8
8
  commands =
9
- py.test --basetemp={envtmpdir} --cov=cachetools {posargs}
9
+ py.test --basetemp={envtmpdir} --cov=cachetools --cov-report term-missing {posargs}
10
10
 
11
11
  [testenv:check-manifest]
12
12
  deps =
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