meta-memcache 2.0.0a1.dev6__tar.gz → 2.0.0a1.dev7__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 (46) hide show
  1. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/PKG-INFO +2 -1
  2. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/pyproject.toml +2 -1
  3. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/__init__.py +1 -0
  4. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/commands/high_level_commands.py +8 -8
  5. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/connection/memcache_socket.py +38 -27
  6. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/executors/default.py +3 -2
  7. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/extras/migrating_cache_client.py +5 -1
  8. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/extras/probabilistic_hot_cache.py +4 -3
  9. meta_memcache-2.0.0a1.dev7/src/meta_memcache/protocol.py +177 -0
  10. meta_memcache-2.0.0a1.dev6/src/meta_memcache/protocol.py +0 -318
  11. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/LICENSE +0 -0
  12. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/README.md +0 -0
  13. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/base/__init__.py +0 -0
  14. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/base/base_cache_client.py +0 -0
  15. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/base/base_serializer.py +0 -0
  16. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/cache_client.py +0 -0
  17. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/commands/__init__.py +0 -0
  18. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/commands/meta_commands.py +0 -0
  19. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/configuration.py +0 -0
  20. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/connection/__init__.py +0 -0
  21. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/connection/pool.py +0 -0
  22. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/connection/providers.py +0 -0
  23. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/errors.py +0 -0
  24. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/events/__init__.py +0 -0
  25. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/events/write_failure_event.py +0 -0
  26. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/executors/__init__.py +0 -0
  27. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/extras/__init__.py +0 -0
  28. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/extras/client_wrapper.py +0 -0
  29. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/interfaces/__init__.py +0 -0
  30. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/interfaces/cache_api.py +0 -0
  31. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/interfaces/commands.py +0 -0
  32. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/interfaces/executor.py +0 -0
  33. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/interfaces/high_level_commands.py +0 -0
  34. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/interfaces/meta_commands.py +0 -0
  35. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/interfaces/router.py +0 -0
  36. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/metrics/__init__.py +0 -0
  37. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/metrics/base.py +0 -0
  38. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/metrics/prometheus.py +0 -0
  39. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/py.typed +0 -0
  40. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/routers/__init__.py +0 -0
  41. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/routers/default.py +0 -0
  42. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/routers/ephemeral.py +0 -0
  43. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/routers/gutter.py +0 -0
  44. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/routers/helpers.py +0 -0
  45. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/serializer.py +0 -0
  46. {meta_memcache-2.0.0a1.dev6 → meta_memcache-2.0.0a1.dev7}/src/meta_memcache/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meta-memcache
3
- Version: 2.0.0a1.dev6
3
+ Version: 2.0.0a1.dev7
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
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.9
14
14
  Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Requires-Dist: marisa-trie (>=1.0.0,<2.0.0)
17
+ Requires-Dist: meta-memcache-socket (>=0.1.0,<0.2.0)
17
18
  Requires-Dist: uhashring (>=2.1,<3.0)
18
19
  Project-URL: Repository, https://github.com/RevenueCat/meta-memcache-py
19
20
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "meta-memcache"
3
- version = "2.0.0a1-dev6"
3
+ version = "2.0.0a1-dev7"
4
4
  description = "Modern, pure python, memcache client with support for new meta commands."
5
5
  license = "MIT"
6
6
  readme = "README.md"
@@ -14,6 +14,7 @@ packages = [{include = "meta_memcache", from="src"}]
14
14
  python = "^3.8"
15
15
  uhashring = "^2.1"
16
16
  marisa-trie = "^1.0.0"
17
+ meta-memcache-socket = "^0.1.0"
17
18
 
18
19
  [tool.poetry.group.extras.dependencies]
19
20
  prometheus-client = "^0.17.1"
@@ -40,6 +40,7 @@ from meta_memcache.protocol import (
40
40
  Miss,
41
41
  NotStored,
42
42
  ServerVersion,
43
+ ResponseFlags,
43
44
  SetMode,
44
45
  Success,
45
46
  TokenFlag,
@@ -287,23 +287,23 @@ class HighLevelCommandsMixin:
287
287
 
288
288
  if isinstance(result, Value):
289
289
  # It is a hit.
290
- if result.win:
290
+ if result.flags.win:
291
291
  # Win flag present, meaning we got the lease to
292
292
  # recache/cache the item. We need to mimic a miss.
293
- return None, result.cas_token
294
- if result.size == 0 and result.win is False:
293
+ return None, result.flags.cas_token
294
+ if result.size == 0 and result.flags.win is False:
295
295
  # The value is empty, this is a miss lease,
296
296
  # and we lost, so we must keep retrying and
297
- # wait for the winner to populate the value.
297
+ # wait for the.flags.winner to populate the value.
298
298
  if i < lease_policy.miss_retries:
299
299
  continue
300
300
  else:
301
301
  # We run out of retries, behave as a miss
302
- return None, result.cas_token
302
+ return None, result.flags.cas_token
303
303
  else:
304
304
  # There is data, either the is no lease or
305
305
  # we lost and should use the stale value.
306
- return result.value, result.cas_token
306
+ return result.value, result.flags.cas_token
307
307
  else:
308
308
  # With MISS_LEASE_TTL we should always get a value
309
309
  # because on miss a lease empty value is generated
@@ -378,7 +378,7 @@ class HighLevelCommandsMixin:
378
378
  if result is None:
379
379
  return None, None
380
380
  else:
381
- return result.value, result.cas_token
381
+ return result.value, result.flags.cas_token
382
382
 
383
383
  def _get(
384
384
  self: HighLevelCommandMixinWithMetaCommands,
@@ -414,7 +414,7 @@ class HighLevelCommandsMixin:
414
414
  ) -> Optional[Value]:
415
415
  if isinstance(result, Value):
416
416
  # It is a hit
417
- if result.win:
417
+ if result.flags.win:
418
418
  # Win flag present, meaning we got the lease to
419
419
  # recache the item. We need to mimic a miss, so
420
420
  # we set the value to None.
@@ -1,17 +1,21 @@
1
1
  import logging
2
2
  import socket
3
- from typing import Union
3
+ from typing import Optional, Tuple, Union
4
+
5
+ import meta_memcache_socket
4
6
 
5
7
  from meta_memcache.errors import MemcacheError
6
8
  from meta_memcache.protocol import (
7
9
  ENDL,
8
10
  ENDL_LEN,
11
+ EMPTY_RESPONSE_FLAGS,
9
12
  NOOP,
10
13
  Conflict,
11
14
  Miss,
12
15
  NotStored,
13
16
  ServerVersion,
14
17
  Success,
18
+ ResponseFlags,
15
19
  Value,
16
20
  get_store_success_response_header,
17
21
  )
@@ -119,7 +123,9 @@ class MemcacheSocket:
119
123
  self._pos = 0
120
124
  self._read = remaining_data
121
125
 
122
- def _get_single_header(self) -> memoryview:
126
+ def _get_single_header(
127
+ self,
128
+ ) -> Tuple[int, Optional[int], Optional[int], Optional[ResponseFlags]]:
123
129
  # Reset buffer for new data
124
130
  if self._read == self._pos:
125
131
  self._read = 0
@@ -127,22 +133,19 @@ class MemcacheSocket:
127
133
  elif self._pos > self._reset_buffer_size:
128
134
  self._reset_buffer()
129
135
 
130
- endl_pos = -1
131
136
  while True:
132
- if self._read - self._pos > ENDL_LEN:
133
- endl_pos = self._buf.find(ENDL, self._pos, self._read)
134
- if endl_pos >= 0:
135
- break
137
+ if self._read != self._pos:
138
+ # We have data in the buffer: find the header
139
+ if header_data := meta_memcache_socket.parse_header(
140
+ self._buf_view, self._pos, self._read
141
+ ):
142
+ self._pos = header_data[0]
143
+ return header_data
136
144
  # Missing data, but still space in buffer, so read more
137
145
  if self._recv_info_buffer() <= 0:
138
146
  break
139
147
 
140
- if endl_pos < 0:
141
- raise MemcacheError("Bad response. Socket might have closed unexpectedly")
142
-
143
- pos = self._pos
144
- self._pos = endl_pos + ENDL_LEN
145
- return self._buf_view[pos:endl_pos]
148
+ raise MemcacheError("Bad response. Socket might have closed unexpectedly")
146
149
 
147
150
  def sendall(self, data: bytes, with_noop: bool = False) -> None:
148
151
  if with_noop:
@@ -153,10 +156,12 @@ class MemcacheSocket:
153
156
  def _read_until_noop_header(self) -> None:
154
157
  while self._noop_expected > 0:
155
158
  header = self._get_single_header()
156
- if header[0:2] == b"MN":
159
+ if header[1] == meta_memcache_socket.RESPONSE_NOOP:
157
160
  self._noop_expected -= 1
158
161
 
159
- def _get_header(self) -> memoryview:
162
+ def _get_header(
163
+ self,
164
+ ) -> Tuple[int, Optional[int], Optional[int], Optional[ResponseFlags]]:
160
165
  try:
161
166
  if self._noop_expected > 0:
162
167
  self._read_until_noop_header()
@@ -169,30 +174,36 @@ class MemcacheSocket:
169
174
  def get_response(
170
175
  self,
171
176
  ) -> Union[Value, Success, NotStored, Conflict, Miss]:
172
- header = self._get_header().tobytes()
173
- response_code = header[0:2]
177
+ (_, response_code, size, flags) = self._get_header()
174
178
  result: Union[Value, Success, NotStored, Conflict, Miss]
175
179
  try:
176
- if response_code == b"VA":
180
+ if response_code == meta_memcache_socket.RESPONSE_VALUE:
181
+ if size is None:
182
+ raise MemcacheError("Bad value response. Missing size")
177
183
  # Value response
178
- result = Value.from_header(header)
179
- elif response_code == self._store_success_response_header:
184
+ result = Value(
185
+ size=size, flags=flags or EMPTY_RESPONSE_FLAGS, value=None
186
+ )
187
+ elif response_code == meta_memcache_socket.RESPONSE_SUCCESS:
180
188
  # Stored or no value, return Success
181
- result = Success.from_header(header)
182
- elif response_code == b"NS":
189
+ result = Success(flags=flags or EMPTY_RESPONSE_FLAGS)
190
+ elif response_code == meta_memcache_socket.RESPONSE_NOT_STORED:
183
191
  # Value response, parse size and flags
184
192
  result = NOT_STORED
185
- elif response_code == b"EX":
193
+ elif response_code == meta_memcache_socket.RESPONSE_CONFLICT:
186
194
  # Already exists, not changed, CAS conflict
187
195
  result = CONFLICT
188
- elif response_code == b"EN" or response_code == b"NF":
196
+ elif response_code == meta_memcache_socket.RESPONSE_MISS:
189
197
  # Not Found, Miss.
190
198
  result = MISS
191
199
  else:
192
- raise MemcacheError(f"Unknown response: {bytes(response_code)!r}")
200
+ raise MemcacheError(f"Unknown response: {response_code}")
193
201
  except Exception as e:
194
- _log.warning(f"Error parsing response header in {self}: {header!r}")
195
- raise MemcacheError(f"Error parsing response header {header!r}") from e
202
+ _log.warning(
203
+ f"Error parsing response header in {self}: "
204
+ f"Response: {response_code}, size {size}, flags: {flags}"
205
+ )
206
+ raise MemcacheError("Error parsing response header") from e
196
207
 
197
208
  return result
198
209
 
@@ -17,6 +17,7 @@ from meta_memcache.protocol import (
17
17
  MetaCommand,
18
18
  Miss,
19
19
  NotStored,
20
+ ResponseFlags,
20
21
  ServerVersion,
21
22
  Success,
22
23
  TokenFlag,
@@ -252,12 +253,12 @@ class DefaultExecutor:
252
253
  Read response on a connection
253
254
  """
254
255
  if flags and Flag.NOREPLY in flags:
255
- return Success()
256
+ return Success(flags=ResponseFlags())
256
257
  result = conn.get_response()
257
258
  if isinstance(result, Value):
258
259
  data = conn.get_value(result.size)
259
260
  if result.size > 0:
260
- encoding_id = result.client_flag or 0
261
+ encoding_id = result.flags.client_flag or 0
261
262
  try:
262
263
  result.value = self._serializer.unserialize(data, encoding_id)
263
264
  except Exception:
@@ -78,7 +78,11 @@ class MigratingCacheClient(HighLevelCommandsMixin):
78
78
  return current_mode
79
79
 
80
80
  def _get_value_ttl(self, value: Value) -> int:
81
- ttl = value.ttl if value.ttl is not None else self._default_read_backfill_ttl
81
+ ttl = (
82
+ value.flags.ttl
83
+ if value.flags.ttl is not None
84
+ else self._default_read_backfill_ttl
85
+ )
82
86
  if ttl < 0:
83
87
  # TTL for items marked to store forvered is returned as -1
84
88
  ttl = 0
@@ -124,10 +124,11 @@ class ProbabilisticHotCache(ClientWrapper):
124
124
  allowed: bool,
125
125
  ) -> None:
126
126
  if not is_hot:
127
- hit_after_write = value.fetched or 0
128
- last_read_age = value.last_access if value.last_access is not None else 9999
127
+ last_read_age = (
128
+ value.flags.last_access if value.flags.last_access is not None else 9999
129
+ )
129
130
  if (
130
- hit_after_write > 0
131
+ value.flags.fetched
131
132
  and last_read_age <= self._max_last_access_age_seconds
132
133
  ):
133
134
  # Is detected as hot
@@ -0,0 +1,177 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum, IntEnum
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from meta_memcache_socket import ResponseFlags
6
+
7
+ ENDL = b"\r\n"
8
+ NOOP: bytes = b"mn" + ENDL
9
+ ENDL_LEN = 2
10
+ SPACE: int = ord(" ")
11
+
12
+
13
+ @dataclass
14
+ class Key:
15
+ __slots__ = ("key", "routing_key", "is_unicode")
16
+ key: str
17
+ routing_key: Optional[str]
18
+ is_unicode: bool
19
+
20
+ def __init__(
21
+ self,
22
+ key: str,
23
+ routing_key: Optional[str] = None,
24
+ is_unicode: bool = False,
25
+ ) -> None:
26
+ self.key = key
27
+ self.routing_key = routing_key
28
+ self.is_unicode = is_unicode
29
+
30
+ def __hash__(self) -> int:
31
+ return hash((self.key, self.routing_key))
32
+
33
+
34
+ class MetaCommand(Enum):
35
+ META_GET = b"mg" # Meta Get
36
+ META_SET = b"ms" # Meta Set
37
+ META_DELETE = b"md" # Meta Delete
38
+ META_ARITHMETIC = b"ma" # Meta Arithmetic
39
+
40
+
41
+ class SetMode(Enum):
42
+ SET = b"S" # Default
43
+ ADD = b"E" # Add if item does NOT EXIST, else LRU bump and return NS
44
+ APPEND = b"A" # If item exists, append the new value to its data.
45
+ PREPEND = b"P" # If item exists, prepend the new value to its data.
46
+ REPLACE = b"R" # Set only if item already exists.
47
+
48
+
49
+ class Flag(Enum):
50
+ BINARY = b"b"
51
+ NOREPLY = b"q"
52
+ RETURN_CLIENT_FLAG = b"f"
53
+ RETURN_CAS_TOKEN = b"c"
54
+ RETURN_VALUE = b"v"
55
+ RETURN_TTL = b"t"
56
+ RETURN_SIZE = b"s"
57
+ RETURN_LAST_ACCESS = b"l"
58
+ RETURN_FETCHED = b"h"
59
+ RETURN_KEY = b"k"
60
+ NO_UPDATE_LRU = b"u"
61
+ MARK_STALE = b"I"
62
+
63
+
64
+ class IntFlag(Enum):
65
+ CACHE_TTL = b"T"
66
+ RECACHE_TTL = b"R"
67
+ MISS_LEASE_TTL = b"N"
68
+ SET_CLIENT_FLAG = b"F"
69
+ MA_INITIAL_VALUE = b"J"
70
+ MA_DELTA_VALUE = b"D"
71
+ CAS_TOKEN = b"C"
72
+
73
+
74
+ class TokenFlag(Enum):
75
+ OPAQUE = b"O"
76
+ # 'M' (mode switch):
77
+ # * Meta Arithmetic:
78
+ # - I or +: increment
79
+ # - D or -: decrement
80
+ # * Meta Set: See SetMode Enum above
81
+ # - E: "add" command. LRU bump and return NS if item exists. Else add.
82
+ # - A: "append" command. If item exists, append the new value to its data.
83
+ # - P: "prepend" command. If item exists, prepend the new value to its data.
84
+ # - R: "replace" command. Set only if item already exists.
85
+ # - S: "set" command. The default mode, added for completeness.
86
+ MODE = b"M"
87
+
88
+
89
+ # Store maps of byte values (int) to enum value
90
+ flag_values: Dict[int, Flag] = {f.value[0]: f for f in Flag}
91
+ int_flags_values: Dict[int, IntFlag] = {f.value[0]: f for f in IntFlag}
92
+ token_flags_values: Dict[int, TokenFlag] = {f.value[0]: f for f in TokenFlag}
93
+
94
+
95
+ @dataclass
96
+ class MemcacheResponse:
97
+ __slots__ = ()
98
+
99
+
100
+ @dataclass
101
+ class Miss(MemcacheResponse):
102
+ __slots__ = ()
103
+
104
+ pass
105
+
106
+
107
+ # Response flags
108
+ EMPTY_RESPONSE_FLAGS = ResponseFlags()
109
+
110
+
111
+ @dataclass
112
+ class Success(MemcacheResponse):
113
+ __slots__ = ("flags",)
114
+ flags: ResponseFlags
115
+
116
+ @classmethod
117
+ def default(cls) -> "Success":
118
+ return cls(flags=ResponseFlags())
119
+
120
+
121
+ @dataclass
122
+ class Value(Success):
123
+ __slots__ = ("flags", "size", "value")
124
+ size: int
125
+ value: Optional[Any]
126
+
127
+
128
+ @dataclass
129
+ class ValueContainer:
130
+ __slots__ = ("value",)
131
+ value: Any
132
+
133
+
134
+ MaybeValue = Optional[ValueContainer]
135
+ MaybeValues = Optional[List[ValueContainer]]
136
+
137
+
138
+ @dataclass
139
+ class NotStored(MemcacheResponse):
140
+ __slots__ = ()
141
+
142
+
143
+ @dataclass
144
+ class Conflict(MemcacheResponse):
145
+ __slots__ = ()
146
+
147
+
148
+ ReadResponse = Union[Miss, Value, Success]
149
+ WriteResponse = Union[Success, NotStored, Conflict, Miss]
150
+
151
+
152
+ Blob = Union[bytes, bytearray, memoryview]
153
+
154
+
155
+ class ServerVersion(IntEnum):
156
+ """
157
+ If more versions with breaking changes are
158
+ added, bump stable to the next int. Code
159
+ will be able to use > / < / = to code
160
+ the behavior of the different versions.
161
+ """
162
+
163
+ AWS_1_6_6 = 1
164
+ STABLE = 2
165
+
166
+
167
+ def get_store_success_response_header(version: ServerVersion) -> bytes:
168
+ if version == ServerVersion.AWS_1_6_6:
169
+ return b"OK"
170
+ return b"HD"
171
+
172
+
173
+ def encode_size(size: int, version: ServerVersion) -> bytes:
174
+ if version == ServerVersion.AWS_1_6_6:
175
+ return b"S" + str(size).encode("ascii")
176
+ else:
177
+ return str(size).encode("ascii")
@@ -1,318 +0,0 @@
1
- from dataclasses import dataclass
2
- from enum import Enum, IntEnum
3
- from typing import Any, Dict, List, Optional, Union
4
-
5
- ENDL = b"\r\n"
6
- NOOP: bytes = b"mn" + ENDL
7
- ENDL_LEN = 2
8
- SPACE: int = ord(" ")
9
-
10
-
11
- @dataclass
12
- class Key:
13
- __slots__ = ("key", "routing_key", "is_unicode")
14
- key: str
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))
30
-
31
-
32
- class MetaCommand(Enum):
33
- META_GET = b"mg" # Meta Get
34
- META_SET = b"ms" # Meta Set
35
- META_DELETE = b"md" # Meta Delete
36
- META_ARITHMETIC = b"ma" # Meta Arithmetic
37
-
38
-
39
- class SetMode(Enum):
40
- SET = b"S" # Default
41
- ADD = b"E" # Add if item does NOT EXIST, else LRU bump and return NS
42
- APPEND = b"A" # If item exists, append the new value to its data.
43
- PREPEND = b"P" # If item exists, prepend the new value to its data.
44
- REPLACE = b"R" # Set only if item already exists.
45
-
46
-
47
- class Flag(Enum):
48
- BINARY = b"b"
49
- NOREPLY = b"q"
50
- RETURN_CLIENT_FLAG = b"f"
51
- RETURN_CAS_TOKEN = b"c"
52
- RETURN_VALUE = b"v"
53
- RETURN_TTL = b"t"
54
- RETURN_SIZE = b"s"
55
- RETURN_LAST_ACCESS = b"l"
56
- RETURN_FETCHED = b"h"
57
- RETURN_KEY = b"k"
58
- NO_UPDATE_LRU = b"u"
59
- MARK_STALE = b"I"
60
-
61
-
62
- class IntFlag(Enum):
63
- CACHE_TTL = b"T"
64
- RECACHE_TTL = b"R"
65
- MISS_LEASE_TTL = b"N"
66
- SET_CLIENT_FLAG = b"F"
67
- MA_INITIAL_VALUE = b"J"
68
- MA_DELTA_VALUE = b"D"
69
- CAS_TOKEN = b"C"
70
-
71
-
72
- class TokenFlag(Enum):
73
- OPAQUE = b"O"
74
- # 'M' (mode switch):
75
- # * Meta Arithmetic:
76
- # - I or +: increment
77
- # - D or -: decrement
78
- # * Meta Set: See SetMode Enum above
79
- # - E: "add" command. LRU bump and return NS if item exists. Else add.
80
- # - A: "append" command. If item exists, append the new value to its data.
81
- # - P: "prepend" command. If item exists, prepend the new value to its data.
82
- # - R: "replace" command. Set only if item already exists.
83
- # - S: "set" command. The default mode, added for completeness.
84
- MODE = b"M"
85
-
86
-
87
- # Store maps of byte values (int) to enum value
88
- flag_values: Dict[int, Flag] = {f.value[0]: f for f in Flag}
89
- int_flags_values: Dict[int, IntFlag] = {f.value[0]: f for f in IntFlag}
90
- token_flags_values: Dict[int, TokenFlag] = {f.value[0]: f for f in TokenFlag}
91
-
92
-
93
- @dataclass
94
- class MemcacheResponse:
95
- __slots__ = ()
96
-
97
-
98
- @dataclass
99
- class Miss(MemcacheResponse):
100
- __slots__ = ()
101
-
102
- pass
103
-
104
-
105
- # Response flags
106
- TOKEN_FLAG_OPAQUE = ord("O")
107
- INT_FLAG_CAS_TOKEN = ord("c")
108
- INT_FLAG_FETCHED = ord("h")
109
- INT_FLAG_LAST_ACCESS = ord("l")
110
- INT_FLAG_TTL = ord("t")
111
- INT_FLAG_CLIENT_FLAG = ord("f")
112
- INT_FLAG_SIZE = ord("s")
113
- FLAG_WIN = ord("W")
114
- FLAG_LOST = ord("Z")
115
- FLAG_STALE = ord("X")
116
-
117
-
118
- # @dataclass(slots=True, init=False)
119
- @dataclass
120
- class Success(MemcacheResponse):
121
- __slots__ = (
122
- "cas_token",
123
- "fetched",
124
- "last_access",
125
- "ttl",
126
- "client_flag",
127
- "win",
128
- "stale",
129
- "real_size",
130
- "opaque",
131
- )
132
- cas_token: Optional[int]
133
- fetched: Optional[int]
134
- last_access: Optional[int]
135
- ttl: Optional[int]
136
- client_flag: Optional[int]
137
- win: Optional[bool]
138
- stale: bool
139
- real_size: Optional[int]
140
- opaque: Optional[bytes]
141
-
142
- def __init__(
143
- self,
144
- *,
145
- cas_token: Optional[int] = None,
146
- fetched: Optional[int] = None,
147
- last_access: Optional[int] = None,
148
- ttl: Optional[int] = None,
149
- client_flag: Optional[int] = None,
150
- win: Optional[bool] = None,
151
- stale: bool = False,
152
- real_size: Optional[int] = None,
153
- opaque: Optional[bytes] = None,
154
- ) -> None:
155
- self.cas_token = cas_token
156
- self.fetched = fetched
157
- self.last_access = last_access
158
- self.ttl = ttl
159
- self.client_flag = client_flag
160
- self.win = win
161
- self.stale = stale
162
- self.real_size = real_size
163
- self.opaque = opaque
164
-
165
- @classmethod
166
- def from_header(cls, header: "Blob") -> "Success":
167
- result = cls()
168
- result._set_flags(header)
169
- return result
170
-
171
- def _set_flags(self, header: bytes, pos: int = 3) -> None: # noqa: C901
172
- header_size = len(header)
173
- while pos < header_size:
174
- flag = header[pos]
175
- pos += 1
176
- if flag == SPACE:
177
- continue
178
- end = pos
179
- while end < header_size:
180
- if header[end] == SPACE:
181
- break
182
- end += 1
183
-
184
- if flag == INT_FLAG_CAS_TOKEN:
185
- self.cas_token = int(header[pos:end])
186
- elif flag == INT_FLAG_FETCHED:
187
- self.fetched = int(header[pos:end])
188
- elif flag == INT_FLAG_LAST_ACCESS:
189
- self.last_access = int(header[pos:end])
190
- elif flag == INT_FLAG_TTL:
191
- self.ttl = int(header[pos:end])
192
- elif flag == INT_FLAG_CLIENT_FLAG:
193
- self.client_flag = int(header[pos:end])
194
- elif flag == FLAG_WIN:
195
- self.win = True
196
- elif flag == FLAG_LOST:
197
- self.win = False
198
- elif flag == FLAG_STALE:
199
- self.stale = True
200
- elif flag == INT_FLAG_SIZE:
201
- self.real_size = int(header[pos:end])
202
- elif flag == TOKEN_FLAG_OPAQUE:
203
- self.opaque = header[pos:end]
204
- pos = end + 1
205
-
206
-
207
- # @dataclass(slots=True, init=False)
208
- @dataclass
209
- class Value(Success):
210
- __slots__ = (
211
- "cas_token",
212
- "fetched",
213
- "last_access",
214
- "ttl",
215
- "client_flag",
216
- "win",
217
- "stale",
218
- "real_size",
219
- "opaque",
220
- "size",
221
- "value",
222
- )
223
- size: int
224
- value: Optional[Any]
225
-
226
- def __init__(
227
- self,
228
- *,
229
- size: int,
230
- value: Optional[Any] = None,
231
- cas_token: Optional[int] = None,
232
- fetched: Optional[int] = None,
233
- last_access: Optional[int] = None,
234
- ttl: Optional[int] = None,
235
- client_flag: Optional[int] = None,
236
- win: Optional[bool] = None,
237
- stale: bool = False,
238
- real_size: Optional[int] = None,
239
- opaque: Optional[bytes] = None,
240
- ) -> None:
241
- self.size = size
242
- self.value = value
243
- self.cas_token = cas_token
244
- self.fetched = fetched
245
- self.last_access = last_access
246
- self.ttl = ttl
247
- self.client_flag = client_flag
248
- self.win = win
249
- self.stale = stale
250
- self.real_size = real_size
251
- self.opaque = opaque
252
-
253
- @classmethod
254
- def from_header(cls, header: "Blob") -> "Value":
255
- header_size = len(header)
256
- if header_size < 4 or header[2] != SPACE:
257
- raise ValueError(f"Invalid header {header!r}")
258
- end = 4
259
- while end < header_size:
260
- if header[end] == SPACE:
261
- break
262
- end += 1
263
- size = int(header[3:end])
264
- result = cls(size=size)
265
- result._set_flags(header, pos=end + 1)
266
- return result
267
-
268
-
269
- @dataclass
270
- class ValueContainer:
271
- __slots__ = ("value",)
272
- value: Any
273
-
274
-
275
- MaybeValue = Optional[ValueContainer]
276
- MaybeValues = Optional[List[ValueContainer]]
277
-
278
-
279
- @dataclass
280
- class NotStored(MemcacheResponse):
281
- __slots__ = ()
282
-
283
-
284
- @dataclass
285
- class Conflict(MemcacheResponse):
286
- __slots__ = ()
287
-
288
-
289
- ReadResponse = Union[Miss, Value, Success]
290
- WriteResponse = Union[Success, NotStored, Conflict, Miss]
291
-
292
-
293
- Blob = Union[bytes, bytearray, memoryview]
294
-
295
-
296
- class ServerVersion(IntEnum):
297
- """
298
- If more versions with breaking changes are
299
- added, bump stable to the next int. Code
300
- will be able to use > / < / = to code
301
- the behavior of the different versions.
302
- """
303
-
304
- AWS_1_6_6 = 1
305
- STABLE = 2
306
-
307
-
308
- def get_store_success_response_header(version: ServerVersion) -> bytes:
309
- if version == ServerVersion.AWS_1_6_6:
310
- return b"OK"
311
- return b"HD"
312
-
313
-
314
- def encode_size(size: int, version: ServerVersion) -> bytes:
315
- if version == ServerVersion.AWS_1_6_6:
316
- return b"S" + str(size).encode("ascii")
317
- else:
318
- return str(size).encode("ascii")