meta-memcache 2.3.0__tar.gz → 2.5.0__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 (53) hide show
  1. {meta_memcache-2.3.0/src/meta_memcache.egg-info → meta_memcache-2.5.0}/PKG-INFO +12 -1
  2. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/README.md +11 -0
  3. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/pyproject.toml +6 -1
  4. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/__init__.py +1 -1
  5. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/commands/high_level_commands.py +82 -23
  6. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/extras/probabilistic_hot_cache.py +56 -9
  7. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/interfaces/high_level_commands.py +7 -0
  8. {meta_memcache-2.3.0 → meta_memcache-2.5.0/src/meta_memcache.egg-info}/PKG-INFO +12 -1
  9. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/LICENSE +0 -0
  10. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/setup.cfg +0 -0
  11. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/base/__init__.py +0 -0
  12. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/base/base_cache_client.py +0 -0
  13. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/base/base_serializer.py +0 -0
  14. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/cache_client.py +0 -0
  15. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/commands/__init__.py +0 -0
  16. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/commands/meta_commands.py +0 -0
  17. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/compression/__init__.py +0 -0
  18. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/compression/zstd_manager.py +0 -0
  19. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/configuration.py +0 -0
  20. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/connection/__init__.py +0 -0
  21. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/connection/memcache_socket.py +0 -0
  22. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/connection/pool.py +0 -0
  23. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/connection/providers.py +0 -0
  24. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/errors.py +0 -0
  25. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/events/__init__.py +0 -0
  26. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/events/write_failure_event.py +0 -0
  27. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/executors/__init__.py +0 -0
  28. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/executors/default.py +0 -0
  29. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/extras/__init__.py +0 -0
  30. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/extras/client_wrapper.py +0 -0
  31. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/extras/migrating_cache_client.py +0 -0
  32. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/interfaces/__init__.py +0 -0
  33. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/interfaces/cache_api.py +0 -0
  34. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/interfaces/commands.py +0 -0
  35. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/interfaces/executor.py +0 -0
  36. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/interfaces/meta_commands.py +0 -0
  37. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/interfaces/router.py +0 -0
  38. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/metrics/__init__.py +0 -0
  39. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/metrics/base.py +0 -0
  40. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/metrics/prometheus.py +0 -0
  41. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/protocol.py +0 -0
  42. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/py.typed +0 -0
  43. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/routers/__init__.py +0 -0
  44. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/routers/default.py +0 -0
  45. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/routers/ephemeral.py +0 -0
  46. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/routers/gutter.py +0 -0
  47. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/routers/helpers.py +0 -0
  48. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/serializer.py +0 -0
  49. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache/settings.py +0 -0
  50. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache.egg-info/SOURCES.txt +0 -0
  51. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache.egg-info/dependency_links.txt +0 -0
  52. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache.egg-info/requires.txt +0 -0
  53. {meta_memcache-2.3.0 → meta_memcache-2.5.0}/src/meta_memcache.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-memcache
3
- Version: 2.3.0
3
+ Version: 2.5.0
4
4
  Summary: Modern, pure python, memcache client with support for new meta commands.
5
5
  Author-email: Guillermo Perez <bisho@revenuecat.com>
6
6
  License: MIT License
@@ -434,6 +434,17 @@ Invalidation...
434
434
  Get multiple keys at once
435
435
  """
436
436
 
437
+ def multi_get_cas(
438
+ self,
439
+ keys: List[Union[Key, str]],
440
+ touch_ttl: Optional[int] = None,
441
+ recache_policy: Optional[RecachePolicy] = None,
442
+ ) -> Dict[Key, Tuple[Optional[Any], Optional[int]]]:
443
+ """
444
+ Same as multi_get(), but also return the CAS tokens so
445
+ they can be used during writes and detect races
446
+ """
447
+
437
448
  def get_cas(
438
449
  self,
439
450
  key: Union[Key, str],
@@ -416,6 +416,17 @@ Invalidation...
416
416
  Get multiple keys at once
417
417
  """
418
418
 
419
+ def multi_get_cas(
420
+ self,
421
+ keys: List[Union[Key, str]],
422
+ touch_ttl: Optional[int] = None,
423
+ recache_policy: Optional[RecachePolicy] = None,
424
+ ) -> Dict[Key, Tuple[Optional[Any], Optional[int]]]:
425
+ """
426
+ Same as multi_get(), but also return the CAS tokens so
427
+ they can be used during writes and detect races
428
+ """
429
+
419
430
  def get_cas(
420
431
  self,
421
432
  key: Union[Key, str],
@@ -8,7 +8,7 @@ dependencies = [
8
8
  authors = [{ name = "Guillermo Perez", email = "bisho@revenuecat.com" }]
9
9
  license = { text = "MIT License" }
10
10
  name = "meta-memcache"
11
- version = "2.3.0"
11
+ version = "2.5.0"
12
12
  description = "Modern, pure python, memcache client with support for new meta commands."
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.10"
@@ -41,6 +41,11 @@ show_missing = true
41
41
  [tool.ruff]
42
42
  line-length = 88
43
43
 
44
+ [tool.ruff.lint]
45
+ # Ruff 0.16 expanded the default rule set from 59 to 413 rules. Pin the selection
46
+ # to the pre-0.16 default so version bumps don't silently add new rules.
47
+ select = ["E4", "E7", "E9", "F"]
48
+
44
49
  [tool.ruff.lint.per-file-ignores]
45
50
  "tests/*.py" = ["E731"]
46
51
  "src/meta_memcache/__init__.py" = ["F401"]
@@ -1,4 +1,4 @@
1
- __version__ = "2.3.0"
1
+ __version__ = "2.5.0"
2
2
 
3
3
  from meta_memcache.cache_client import CacheClient
4
4
  from meta_memcache.configuration import (
@@ -18,6 +18,7 @@ from meta_memcache.interfaces.high_level_commands import HighLevelCommandsProtoc
18
18
  from meta_memcache.interfaces.meta_commands import MetaCommandsProtocol
19
19
  from meta_memcache.interfaces.router import FailureHandling
20
20
  from meta_memcache.protocol import (
21
+ MA_MODE_DEC,
21
22
  Key,
22
23
  Miss,
23
24
  ReadResponse,
@@ -25,7 +26,6 @@ from meta_memcache.protocol import (
25
26
  SetMode,
26
27
  Success,
27
28
  Value,
28
- MA_MODE_DEC,
29
29
  )
30
30
 
31
31
  T = TypeVar("T")
@@ -59,6 +59,15 @@ class HighLevelCommandMixinWithMetaCommands(
59
59
  return_cas_token: bool = False,
60
60
  ) -> Optional[Value]: ... # pragma: no cover
61
61
 
62
+ def _get_or_lease(
63
+ self,
64
+ key: Union[Key, str],
65
+ lease_policy: LeasePolicy,
66
+ touch_ttl: Optional[int] = None,
67
+ recache_policy: Optional[RecachePolicy] = None,
68
+ lease_wait_fn: Optional[Callable[[float], None]] = None,
69
+ ) -> Optional[Value]: ... # pragma: no cover
70
+
62
71
  def _process_get_result(
63
72
  self, key: Union[Key, str], result: ReadResponse
64
73
  ) -> Optional[Value]: ... # pragma: no cover
@@ -249,14 +258,14 @@ class HighLevelCommandsMixin:
249
258
  ``lease_wait_fn`` is invoked with the number of seconds to wait
250
259
  between lease retries. Defaults to ``time.sleep``.
251
260
  """
252
- value, _ = self.get_or_lease_cas(
261
+ result = self._get_or_lease(
253
262
  key=key,
254
263
  lease_policy=lease_policy,
255
264
  touch_ttl=touch_ttl,
256
265
  recache_policy=recache_policy,
257
266
  lease_wait_fn=lease_wait_fn,
258
267
  )
259
- return value
268
+ return result.value if result is not None else None
260
269
 
261
270
  def get_or_lease_cas(
262
271
  self: HighLevelCommandMixinWithMetaCommands,
@@ -270,6 +279,35 @@ class HighLevelCommandsMixin:
270
279
  Same as get_or_lease(), but also return the CAS token so
271
280
  it can be used during writes and detect races
272
281
  """
282
+ result = self._get_or_lease(
283
+ key=key,
284
+ lease_policy=lease_policy,
285
+ touch_ttl=touch_ttl,
286
+ recache_policy=recache_policy,
287
+ lease_wait_fn=lease_wait_fn,
288
+ )
289
+ if result is None:
290
+ return None, None
291
+ return result.value, result.flags.cas_token
292
+
293
+ def _get_or_lease(
294
+ self: HighLevelCommandMixinWithMetaCommands,
295
+ key: Union[Key, str],
296
+ lease_policy: LeasePolicy,
297
+ touch_ttl: Optional[int] = None,
298
+ recache_policy: Optional[RecachePolicy] = None,
299
+ lease_wait_fn: Optional[Callable[[float], None]] = None,
300
+ ) -> Optional[Value]:
301
+ """
302
+ Internal version of get_or_lease() that returns the raw Value, so
303
+ wrappers can inspect the response flags, i.e. to decide whether the
304
+ value is worth promoting into a local cache, or read the CAS token.
305
+
306
+ `Value.value` is None when the caller must behave as if this was a
307
+ miss: either it won the lease, or it lost and ran out of retries.
308
+ The Value itself is still returned in both cases, since the lease
309
+ winner needs its CAS token to write the value back.
310
+ """
273
311
  if lease_policy.miss_retries <= 0:
274
312
  raise ValueError(
275
313
  "Wrong lease_policy: miss_retries needs to be greater than 0"
@@ -295,30 +333,25 @@ class HighLevelCommandsMixin:
295
333
  return_cas_token=True,
296
334
  )
297
335
 
298
- if isinstance(result, Value):
299
- # It is a hit.
300
- if result.flags.win:
301
- # Win flag present, meaning we got the lease to
302
- # recache/cache the item. We need to mimic a miss.
303
- return None, result.flags.cas_token
304
- if result.size == 0 and result.flags.win is False:
305
- # The value is empty, this is a miss lease,
306
- # and we lost, so we must keep retrying and
307
- # wait for the.flags.winner to populate the value.
308
- if i < lease_policy.miss_retries:
309
- continue
310
- else:
311
- # We run out of retries, behave as a miss
312
- return None, result.flags.cas_token
313
- else:
314
- # There is data, either the is no lease or
315
- # we lost and should use the stale value.
316
- return result.value, result.flags.cas_token
317
- else:
336
+ if not isinstance(result, Value):
318
337
  # With MISS_LEASE_TTL we should always get a value
319
338
  # because on miss a lease empty value is generated
320
339
  raise MemcacheError(f"Unexpected response: {result} for key {key}")
321
340
 
341
+ if (
342
+ result.size == 0
343
+ and result.flags.win is False
344
+ and i < lease_policy.miss_retries
345
+ ):
346
+ # This is a miss lease and we lost, so we must keep retrying
347
+ # and wait for the winner to populate the value.
348
+ continue
349
+
350
+ # Either there is data, or we got a placeholder: we won the lease,
351
+ # or we lost and ran out of retries. _process_get_result() already
352
+ # cleared the value of the placeholders, so both behave as a miss.
353
+ return result
354
+
322
355
  def get(
323
356
  self: HighLevelCommandMixinWithMetaCommands,
324
357
  key: Union[Key, str],
@@ -352,6 +385,27 @@ class HighLevelCommandsMixin:
352
385
  )
353
386
  return {k: v.value if v is not None else None for k, v in results.items()}
354
387
 
388
+ def multi_get_cas(
389
+ self: HighLevelCommandMixinWithMetaCommands,
390
+ keys: Iterable[Union[Key, str]],
391
+ touch_ttl: Optional[int] = None,
392
+ recache_policy: Optional[RecachePolicy] = None,
393
+ ) -> Dict[Key, Tuple[Optional[Any], Optional[int]]]:
394
+ """
395
+ Same as multi_get(), but also return the CAS tokens so
396
+ they can be used during writes and detect races
397
+ """
398
+ results = self._multi_get(
399
+ keys=keys,
400
+ touch_ttl=touch_ttl,
401
+ recache_policy=recache_policy,
402
+ return_cas_token=True,
403
+ )
404
+ return {
405
+ k: (v.value, v.flags.cas_token) if v is not None else (None, None)
406
+ for k, v in results.items()
407
+ }
408
+
355
409
  def _multi_get(
356
410
  self: HighLevelCommandMixinWithMetaCommands,
357
411
  keys: Iterable[Union[Key, str]],
@@ -423,6 +477,11 @@ class HighLevelCommandsMixin:
423
477
  # recache the item. We need to mimic a miss, so
424
478
  # we set the value to None.
425
479
  result.value = None
480
+ elif result.size == 0 and result.flags.win is False:
481
+ # An empty value with a lose flag is the placeholder the
482
+ # server plants on a lease miss, while the winner
483
+ # repopulates it. There is no value to return either.
484
+ result.value = None
426
485
  return result
427
486
  elif isinstance(result, Miss):
428
487
  return None
@@ -1,13 +1,13 @@
1
+ import pickle
1
2
  import random
2
3
  import threading
3
4
  import time
4
5
  from dataclasses import dataclass
5
- from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
6
- import pickle
6
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
7
7
 
8
8
  from marisa_trie import Trie # type: ignore
9
9
 
10
- from meta_memcache.configuration import RecachePolicy
10
+ from meta_memcache.configuration import LeasePolicy, RecachePolicy
11
11
  from meta_memcache.extras.client_wrapper import ClientWrapper
12
12
  from meta_memcache.interfaces.cache_api import CacheApi
13
13
  from meta_memcache.metrics.base import BaseMetricsCollector, MetricDefinition
@@ -191,6 +191,39 @@ class ProbabilisticHotCache(ClientWrapper):
191
191
  key: Union[Key, str],
192
192
  touch_ttl: Optional[int] = None,
193
193
  recache_policy: Optional[RecachePolicy] = None,
194
+ ) -> Optional[Any]:
195
+ return self._hot_cache_get(
196
+ key=key,
197
+ touch_ttl=touch_ttl,
198
+ recache_policy=recache_policy,
199
+ )
200
+
201
+ def get_or_lease(
202
+ self,
203
+ key: Union[Key, str],
204
+ lease_policy: LeasePolicy,
205
+ touch_ttl: Optional[int] = None,
206
+ recache_policy: Optional[RecachePolicy] = None,
207
+ lease_wait_fn: Optional[Callable[[float], None]] = None,
208
+ ) -> Optional[Any]:
209
+ # A hot cache hit needs no lease: the value is already local and there
210
+ # is nothing to repopulate. Only the requests that fall through to the
211
+ # server take part in the lease.
212
+ return self._hot_cache_get(
213
+ key=key,
214
+ touch_ttl=touch_ttl,
215
+ recache_policy=recache_policy,
216
+ lease_policy=lease_policy,
217
+ lease_wait_fn=lease_wait_fn,
218
+ )
219
+
220
+ def _hot_cache_get(
221
+ self,
222
+ key: Union[Key, str],
223
+ touch_ttl: Optional[int] = None,
224
+ recache_policy: Optional[RecachePolicy] = None,
225
+ lease_policy: Optional[LeasePolicy] = None,
226
+ lease_wait_fn: Optional[Callable[[float], None]] = None,
194
227
  ) -> Optional[Any]:
195
228
  key = key if isinstance(key, Key) else Key(key)
196
229
  if self._allowed_prefixes and not self._allowed_prefixes.prefixes(key.key):
@@ -203,12 +236,26 @@ class ProbabilisticHotCache(ClientWrapper):
203
236
  if found:
204
237
  return value
205
238
 
206
- result = self._get(
207
- key=key,
208
- touch_ttl=touch_ttl,
209
- recache_policy=recache_policy,
210
- return_cas_token=False,
211
- )
239
+ if lease_policy is not None:
240
+ result = self._get_or_lease(
241
+ key=key,
242
+ lease_policy=lease_policy,
243
+ touch_ttl=touch_ttl,
244
+ recache_policy=recache_policy,
245
+ lease_wait_fn=lease_wait_fn,
246
+ )
247
+ if result and result.value is None:
248
+ # Lease placeholder: we hold the lease, or we lost and ran out
249
+ # of retries. Behaves as a miss, and nothing worth promoting.
250
+ result = None
251
+ else:
252
+ result = self._get(
253
+ key=key,
254
+ touch_ttl=touch_ttl,
255
+ recache_policy=recache_policy,
256
+ return_cas_token=False,
257
+ )
258
+
212
259
  if result is None:
213
260
  allowed and self._metrics and self._metrics.metric_inc("candidate_misses")
214
261
  is_hot and self._clear_hot_cache_if_necessary(key)
@@ -120,6 +120,13 @@ class HighLevelCommandsProtocol(Protocol):
120
120
  recache_policy: Optional[RecachePolicy] = None,
121
121
  ) -> Dict[Key, Optional[Any]]: ... # pragma: no cover
122
122
 
123
+ def multi_get_cas(
124
+ self,
125
+ keys: Iterable[Union[Key, str]],
126
+ touch_ttl: Optional[int] = None,
127
+ recache_policy: Optional[RecachePolicy] = None,
128
+ ) -> Dict[Key, Tuple[Optional[Any], Optional[int]]]: ... # pragma: no cover
129
+
123
130
  def _multi_get(
124
131
  self,
125
132
  keys: Iterable[Union[Key, str]],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-memcache
3
- Version: 2.3.0
3
+ Version: 2.5.0
4
4
  Summary: Modern, pure python, memcache client with support for new meta commands.
5
5
  Author-email: Guillermo Perez <bisho@revenuecat.com>
6
6
  License: MIT License
@@ -434,6 +434,17 @@ Invalidation...
434
434
  Get multiple keys at once
435
435
  """
436
436
 
437
+ def multi_get_cas(
438
+ self,
439
+ keys: List[Union[Key, str]],
440
+ touch_ttl: Optional[int] = None,
441
+ recache_policy: Optional[RecachePolicy] = None,
442
+ ) -> Dict[Key, Tuple[Optional[Any], Optional[int]]]:
443
+ """
444
+ Same as multi_get(), but also return the CAS tokens so
445
+ they can be used during writes and detect races
446
+ """
447
+
437
448
  def get_cas(
438
449
  self,
439
450
  key: Union[Key, str],
File without changes
File without changes