meta-memcache 1.1.2.dev2__tar.gz → 1.1.2.dev5__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 (45) hide show
  1. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/PKG-INFO +1 -1
  2. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/pyproject.toml +1 -1
  3. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/commands/high_level_commands.py +40 -28
  4. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/connection/memcache_socket.py +6 -3
  5. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/connection/pool.py +14 -9
  6. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/connection/providers.py +20 -0
  7. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/executors/default.py +18 -3
  8. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/protocol.py +30 -9
  9. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/LICENSE +0 -0
  10. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/README.md +0 -0
  11. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/__init__.py +0 -0
  12. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/base/__init__.py +0 -0
  13. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/base/base_cache_client.py +0 -0
  14. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/base/base_serializer.py +0 -0
  15. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/cache_client.py +0 -0
  16. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/commands/__init__.py +0 -0
  17. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/commands/meta_commands.py +0 -0
  18. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/configuration.py +0 -0
  19. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/connection/__init__.py +0 -0
  20. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/errors.py +0 -0
  21. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/events/__init__.py +0 -0
  22. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/events/write_failure_event.py +0 -0
  23. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/executors/__init__.py +0 -0
  24. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/extras/__init__.py +0 -0
  25. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/extras/client_wrapper.py +0 -0
  26. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/extras/migrating_cache_client.py +0 -0
  27. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/extras/probabilistic_hot_cache.py +0 -0
  28. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/interfaces/__init__.py +0 -0
  29. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/interfaces/cache_api.py +0 -0
  30. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/interfaces/commands.py +0 -0
  31. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/interfaces/executor.py +0 -0
  32. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/interfaces/high_level_commands.py +0 -0
  33. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/interfaces/meta_commands.py +0 -0
  34. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/interfaces/router.py +0 -0
  35. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/metrics/__init__.py +0 -0
  36. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/metrics/base.py +0 -0
  37. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/metrics/prometheus.py +0 -0
  38. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/py.typed +0 -0
  39. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/routers/__init__.py +0 -0
  40. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/routers/default.py +0 -0
  41. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/routers/ephemeral.py +0 -0
  42. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/routers/gutter.py +0 -0
  43. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/routers/helpers.py +0 -0
  44. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/serializer.py +0 -0
  45. {meta_memcache-1.1.2.dev2 → meta_memcache-1.1.2.dev5}/src/meta_memcache/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meta-memcache
3
- Version: 1.1.2.dev2
3
+ Version: 1.1.2.dev5
4
4
  Summary: Modern, pure python, memcache client with support for new meta commands.
5
5
  Home-page: https://github.com/RevenueCat/meta-memcache-py
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "meta-memcache"
3
- version = "1.1.2-dev2"
3
+ version = "1.1.2-dev5"
4
4
  description = "Modern, pure python, memcache client with support for new meta commands."
5
5
  license = "MIT"
6
6
  readme = "README.md"
@@ -32,6 +32,22 @@ from meta_memcache.protocol import (
32
32
  T = TypeVar("T")
33
33
  _REFILL_FAILURE_HANDLING = FailureHandling(track_write_failures=False)
34
34
 
35
+ DEFAULT_FLAGS: Set[Flag] = {
36
+ Flag.RETURN_VALUE,
37
+ Flag.RETURN_TTL,
38
+ Flag.RETURN_CLIENT_FLAG,
39
+ Flag.RETURN_LAST_ACCESS,
40
+ Flag.RETURN_FETCHED,
41
+ }
42
+ DEFAULT_CAS_FLAGS: Set[Flag] = {
43
+ Flag.RETURN_VALUE,
44
+ Flag.RETURN_TTL,
45
+ Flag.RETURN_CLIENT_FLAG,
46
+ Flag.RETURN_LAST_ACCESS,
47
+ Flag.RETURN_FETCHED,
48
+ Flag.RETURN_CAS_TOKEN,
49
+ }
50
+
35
51
 
36
52
  class HighLevelCommandMixinWithMetaCommands(
37
53
  HighLevelCommandsProtocol, MetaCommandsProtocol, Protocol
@@ -328,20 +344,18 @@ class HighLevelCommandsMixin:
328
344
  recache_policy: Optional[RecachePolicy] = None,
329
345
  return_cas_token: bool = False,
330
346
  ) -> Dict[Key, Optional[Value]]:
331
- flags = {
332
- Flag.RETURN_VALUE,
333
- Flag.RETURN_TTL,
334
- Flag.RETURN_CLIENT_FLAG,
335
- Flag.RETURN_LAST_ACCESS,
336
- Flag.RETURN_FETCHED,
337
- }
338
347
  if return_cas_token:
339
- flags.add(Flag.RETURN_CAS_TOKEN)
340
- int_flags = {}
341
- if recache_policy:
342
- int_flags[IntFlag.RECACHE_TTL] = recache_policy.ttl
343
- if touch_ttl is not None and touch_ttl >= 0:
344
- int_flags[IntFlag.CACHE_TTL] = touch_ttl
348
+ flags = DEFAULT_CAS_FLAGS.copy()
349
+ else:
350
+ flags = DEFAULT_FLAGS.copy()
351
+ if recache_policy is None and touch_ttl is None:
352
+ int_flags = None
353
+ else:
354
+ int_flags = {}
355
+ if recache_policy:
356
+ int_flags[IntFlag.RECACHE_TTL] = recache_policy.ttl
357
+ if touch_ttl is not None and touch_ttl >= 0:
358
+ int_flags[IntFlag.CACHE_TTL] = touch_ttl
345
359
 
346
360
  results = self.meta_multiget(
347
361
  keys=[key if isinstance(key, Key) else Key(key) for key in keys],
@@ -377,22 +391,20 @@ class HighLevelCommandsMixin:
377
391
  return_cas_token: bool = False,
378
392
  ) -> Optional[Value]:
379
393
  key = key if isinstance(key, Key) else Key(key)
380
- flags = {
381
- Flag.RETURN_VALUE,
382
- Flag.RETURN_TTL,
383
- Flag.RETURN_CLIENT_FLAG,
384
- Flag.RETURN_LAST_ACCESS,
385
- Flag.RETURN_FETCHED,
386
- }
387
394
  if return_cas_token:
388
- flags.add(Flag.RETURN_CAS_TOKEN)
389
- int_flags = {}
390
- if lease_policy:
391
- int_flags[IntFlag.MISS_LEASE_TTL] = lease_policy.ttl
392
- if recache_policy:
393
- int_flags[IntFlag.RECACHE_TTL] = recache_policy.ttl
394
- if touch_ttl is not None and touch_ttl >= 0:
395
- int_flags[IntFlag.CACHE_TTL] = touch_ttl
395
+ flags = DEFAULT_CAS_FLAGS.copy()
396
+ else:
397
+ flags = DEFAULT_FLAGS.copy()
398
+ if lease_policy is None and recache_policy is None and touch_ttl is None:
399
+ int_flags = None
400
+ else:
401
+ int_flags = {}
402
+ if lease_policy:
403
+ int_flags[IntFlag.MISS_LEASE_TTL] = lease_policy.ttl
404
+ if recache_policy:
405
+ int_flags[IntFlag.RECACHE_TTL] = recache_policy.ttl
406
+ if touch_ttl is not None and touch_ttl >= 0:
407
+ int_flags[IntFlag.CACHE_TTL] = touch_ttl
396
408
 
397
409
  result = self.meta_get(key, flags=flags, int_flags=int_flags)
398
410
  return self._process_get_result(key, result)
@@ -21,6 +21,9 @@ from meta_memcache.protocol import (
21
21
  )
22
22
 
23
23
  _log: logging.Logger = logging.getLogger(__name__)
24
+ NOT_STORED = NotStored()
25
+ MISS = Miss()
26
+ CONFLICT = Conflict()
24
27
 
25
28
 
26
29
  class MemcacheSocket:
@@ -224,15 +227,15 @@ class MemcacheSocket:
224
227
  self._add_flags(result, chunks)
225
228
  elif response_code == b"NS":
226
229
  # Value response, parse size and flags
227
- result = NotStored()
230
+ result = NOT_STORED
228
231
  assert len(chunks) == 0 # noqa: S101
229
232
  elif response_code == b"EX":
230
233
  # Already exists, not changed, CAS conflict
231
- result = Conflict()
234
+ result = CONFLICT
232
235
  assert len(chunks) == 0 # noqa: S101
233
236
  elif response_code == b"EN" or response_code == b"NF":
234
237
  # Not Found, Miss.
235
- result = Miss()
238
+ result = MISS
236
239
  assert len(chunks) == 0 # noqa: S101
237
240
  else:
238
241
  raise MemcacheError(f"Unknown response: {bytes(response_code)!r}")
@@ -115,24 +115,29 @@ class ConnectionPool:
115
115
 
116
116
  @contextmanager
117
117
  def get_connection(self) -> Generator[MemcacheSocket, None, None]:
118
- try:
119
- conn = self._pool.popleft()
120
- except IndexError:
121
- conn = None
122
-
123
- if conn is None:
124
- conn = self._create_connection()
125
-
118
+ conn = self.pop_connection()
126
119
  try:
127
120
  yield conn
128
121
  except Exception as e:
122
+ self.release_connection(conn, error=True)
123
+ raise MemcacheServerError(self.server, "Memcache error") from e
124
+ else:
125
+ self.release_connection(conn, error=False)
126
+
127
+ def pop_connection(self) -> MemcacheSocket:
128
+ try:
129
+ return self._pool.popleft()
130
+ except IndexError:
131
+ return self._create_connection()
132
+
133
+ def release_connection(self, conn: MemcacheSocket, error: bool) -> None:
134
+ if error:
129
135
  # Errors, assume connection is in bad state
130
136
  _log.warning(
131
137
  "Error during cache conn context (discarding connection)",
132
138
  exc_info=True,
133
139
  )
134
140
  self._discard_connection(conn, error=True)
135
- raise MemcacheServerError(self.server, "Memcache error") from e
136
141
  else:
137
142
  if len(self._pool) < self._max_pool_size:
138
143
  # If there is a race, the deque might end with more than
@@ -1,4 +1,5 @@
1
1
  from typing import Dict, List, Protocol
2
+ import zlib
2
3
 
3
4
  from uhashring import HashRing # type: ignore
4
5
 
@@ -49,3 +50,22 @@ class HashRingConnectionPoolProvider:
49
50
  return {
50
51
  server: pool.get_counters() for server, pool in self._server_pool.items()
51
52
  }
53
+
54
+
55
+ class NonConsistentHashPoolProvider:
56
+ def __init__(self, server_pool: Dict[ServerAddress, ConnectionPool]) -> None:
57
+ self._server_pool = server_pool
58
+ self._server_count = len(server_pool)
59
+ self._servers: List[ServerAddress] = [x for x in server_pool.keys()]
60
+
61
+ def get_pool(self, key: Key) -> ConnectionPool:
62
+ routing_key = key.routing_key or key.key
63
+ server: ServerAddress = self._servers[
64
+ zlib.crc32(routing_key.encode()) % self._server_count
65
+ ]
66
+ return self._server_pool[server]
67
+
68
+ def get_counters(self) -> Dict[ServerAddress, PoolCounters]:
69
+ return {
70
+ server: pool.get_counters() for server, pool in self._server_pool.items()
71
+ }
@@ -115,7 +115,9 @@ class DefaultExecutor:
115
115
  else self._prepare_serialized_value_and_int_flags(value, int_flags)
116
116
  )
117
117
  try:
118
- with pool.get_connection() as conn:
118
+ conn = pool.pop_connection()
119
+ error = False
120
+ try:
119
121
  self._conn_send_cmd(
120
122
  conn,
121
123
  command=command,
@@ -126,6 +128,11 @@ class DefaultExecutor:
126
128
  token_flags=token_flags,
127
129
  )
128
130
  return self._conn_recv_response(conn, flags=flags)
131
+ except Exception as e:
132
+ error = True
133
+ raise MemcacheServerError(pool.server, "Memcache error") from e
134
+ finally:
135
+ pool.release_connection(conn, error=error)
129
136
  except MemcacheServerError:
130
137
  if track_write_failures and self._is_a_write_failure(command, int_flags):
131
138
  self.on_write_failure(key)
@@ -141,7 +148,7 @@ class DefaultExecutor:
141
148
  else:
142
149
  return NotStored()
143
150
 
144
- def exec_multi_on_pool(
151
+ def exec_multi_on_pool( # noqa: C901
145
152
  self,
146
153
  pool: ConnectionPool,
147
154
  command: MetaCommand,
@@ -154,7 +161,10 @@ class DefaultExecutor:
154
161
  ) -> Dict[Key, MemcacheResponse]:
155
162
  results: Dict[Key, MemcacheResponse] = {}
156
163
  try:
157
- with pool.get_connection() as conn:
164
+ conn = pool.pop_connection()
165
+ error = False
166
+ try:
167
+ # with pool.get_connection() as conn:
158
168
  for key, value in key_values:
159
169
  cmd_value, int_flags = (
160
170
  (None, int_flags)
@@ -175,6 +185,11 @@ class DefaultExecutor:
175
185
  )
176
186
  for key, _ in key_values:
177
187
  results[key] = self._conn_recv_response(conn, flags=flags)
188
+ except Exception as e:
189
+ error = True
190
+ raise MemcacheServerError(pool.server, "Memcache error") from e
191
+ finally:
192
+ pool.release_connection(conn, error=error)
178
193
  except MemcacheServerError:
179
194
  if track_write_failures and self._is_a_write_failure(command, int_flags):
180
195
  for key, _ in key_values:
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass
2
2
  from enum import Enum, IntEnum
3
- from typing import Any, Dict, List, NamedTuple, Optional, Set, Union
3
+ from typing import Any, Dict, List, Optional, Set, Union
4
4
 
5
5
  ENDL = b"\r\n"
6
6
  NOOP: bytes = b"mn" + ENDL
@@ -8,10 +8,25 @@ ENDL_LEN = 2
8
8
  SPACE: int = ord(" ")
9
9
 
10
10
 
11
- class Key(NamedTuple):
11
+ @dataclass
12
+ class Key:
13
+ __slots__ = ("key", "routing_key", "is_unicode")
12
14
  key: str
13
- routing_key: Optional[str] = None
14
- is_unicode: bool = False
15
+ routing_key: Optional[str]
16
+ is_unicode: bool
17
+
18
+ def __init__(
19
+ self,
20
+ key: str,
21
+ routing_key: Optional[str] = None,
22
+ is_unicode: bool = False,
23
+ ) -> None:
24
+ self.key = key
25
+ self.routing_key = routing_key
26
+ self.is_unicode = is_unicode
27
+
28
+ def __hash__(self) -> int:
29
+ return hash((self.key, self.routing_key))
15
30
 
16
31
 
17
32
  class MetaCommand(Enum):
@@ -84,16 +99,19 @@ int_flags_values: Dict[int, IntFlag] = {f.value[0]: f for f in IntFlag}
84
99
  token_flags_values: Dict[int, TokenFlag] = {f.value[0]: f for f in TokenFlag}
85
100
 
86
101
 
102
+ @dataclass
87
103
  class MemcacheResponse:
88
- pass
104
+ __slots__ = ()
89
105
 
90
106
 
107
+ @dataclass
91
108
  class Miss(MemcacheResponse):
92
- pass
109
+ __slots__ = ()
93
110
 
94
111
 
95
112
  @dataclass
96
113
  class Success(MemcacheResponse):
114
+ __slots__ = ("flags", "int_flags", "token_flags")
97
115
  flags: Set[Flag]
98
116
  int_flags: Dict[IntFlag, int]
99
117
  token_flags: Dict[TokenFlag, bytes]
@@ -111,6 +129,7 @@ class Success(MemcacheResponse):
111
129
 
112
130
  @dataclass
113
131
  class Value(Success):
132
+ __slots__ = ("flags", "int_flags", "token_flags", "size", "value")
114
133
  size: int
115
134
  value: Optional[Any]
116
135
 
@@ -127,7 +146,9 @@ class Value(Success):
127
146
  self.value = value
128
147
 
129
148
 
130
- class ValueContainer(NamedTuple):
149
+ @dataclass
150
+ class ValueContainer:
151
+ __slots__ = ("value",)
131
152
  value: Any
132
153
 
133
154
 
@@ -137,12 +158,12 @@ MaybeValues = Optional[List[ValueContainer]]
137
158
 
138
159
  @dataclass
139
160
  class NotStored(MemcacheResponse):
140
- pass
161
+ __slots__ = ()
141
162
 
142
163
 
143
164
  @dataclass
144
165
  class Conflict(MemcacheResponse):
145
- pass
166
+ __slots__ = ()
146
167
 
147
168
 
148
169
  ReadResponse = Union[Miss, Value, Success]