Skip to content

Commit

Permalink
Add ZstdSerializer, change Key to include "domain"
Browse files Browse the repository at this point in the history
  • Loading branch information
bisho committed Mar 5, 2024
1 parent a0f1bb3 commit d056746
Show file tree
Hide file tree
Showing 21 changed files with 746 additions and 192 deletions.
4 changes: 2 additions & 2 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[flake8]
ignore=E203,W503
ignore=E203,W503,E704,E701
max-line-length = 88
select = B,B9,BLK,C,E,F,I,S,W
max-complexity = 10
application-import-names = meta_memcache,tests
import-order-style = cryptography
per-file-ignores =
__init__.py:F401
tests/*:S101,S403
tests/*:S101,S403
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,22 @@ will be gone or present, since they are stored in the same server). Note this is
also risky, if you place all keys of a user in the same server, and the server
goes down, the user life will be miserable.

### Custom domains:
You can add a domain to keys. This domain can be used for custom per-domain
metrics like hit ratios or to control serialization of the values.
```python:
Key("key:1:2", domain="example")
```
For example the ZstdSerializer allows to configure different dictionaries by
domain, so you can compress more efficiently data of different domains.

### Unicode keys:
Unicode keys are supported, the keys will be hashed according to Meta commands
[binary encoded keys](https://github.com/memcached/memcached/wiki/MetaCommands#binary-encoded-keys)
specification.

To use this, mark the key as unicode:
```python:
Key("🍺", unicode=True)
Key("🍺")
```

### Large keys:
Expand Down
234 changes: 184 additions & 50 deletions poetry.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ packages = [{include = "meta_memcache", from="src"}]
python = "^3.8"
uhashring = "^2.1"
marisa-trie = "^1.0.0"
meta-memcache-socket = "0.1.1"
meta-memcache-socket = "0.1.3"
zstandard = "^0.22.0"

[tool.poetry.group.extras.dependencies]
prometheus-client = "^0.17.1"
Expand All @@ -27,7 +28,7 @@ testpaths = [

[tool.isort]
profile = "black"
known_third_party = ["uhashring", "pytest", "pytest_mock", "marisa-trie"]
known_third_party = ["uhashring", "pytest", "pytest_mock", "marisa-trie", "zstandard"]

[tool.coverage.paths]
source = ["src", "*/site-packages"]
Expand Down
9 changes: 4 additions & 5 deletions src/meta_memcache/base/base_serializer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from typing import Any, NamedTuple

from meta_memcache.protocol import Blob
from meta_memcache.protocol import Blob, Key


class EncodedValue(NamedTuple):
Expand All @@ -13,10 +13,9 @@ class BaseSerializer(ABC):
@abstractmethod
def serialize(
self,
key: Key,
value: Any,
) -> EncodedValue:
...
) -> EncodedValue: ...

@abstractmethod
def unserialize(self, data: Blob, encoding_id: int) -> Any:
...
def unserialize(self, data: Blob, encoding_id: int) -> Any: ...
12 changes: 4 additions & 8 deletions src/meta_memcache/commands/high_level_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,19 @@ def _get(
lease_policy: Optional[LeasePolicy] = None,
recache_policy: Optional[RecachePolicy] = None,
return_cas_token: bool = False,
) -> Optional[Value]:
... # pragma: no cover
) -> Optional[Value]: ... # pragma: no cover

def _process_get_result(
self, key: Union[Key, str], result: ReadResponse
) -> Optional[Value]:
... # pragma: no cover
) -> Optional[Value]: ... # pragma: no cover

def _get_typed_value(
self,
key: Union[Key, str],
value: Any,
cls: Type[T],
error_on_type_mismatch: bool = False,
) -> Optional[T]:
... # pragma: no cover
) -> Optional[T]: ... # pragma: no cover

def _get_delta_flags(
self,
Expand All @@ -80,8 +77,7 @@ def _get_delta_flags(
no_reply: bool = False,
cas_token: Optional[int] = None,
return_value: bool = False,
) -> RequestFlags:
... # pragma: no cover
) -> RequestFlags: ... # pragma: no cover


class HighLevelCommandsMixin:
Expand Down
6 changes: 2 additions & 4 deletions src/meta_memcache/connection/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@


class ConnectionPoolProvider(Protocol):
def get_pool(self, key: Key) -> ConnectionPool:
... # pragma: no cover
def get_pool(self, key: Key) -> ConnectionPool: ... # pragma: no cover

def get_counters(self) -> Dict[ServerAddress, PoolCounters]:
... # pragma: no cover
def get_counters(self) -> Dict[ServerAddress, PoolCounters]: ... # pragma: no cover


class HostConnectionPoolProvider:
Expand Down
3 changes: 1 addition & 2 deletions src/meta_memcache/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ def __init__(self, server: str, message: str) -> None:
super().__init__(message)


class ServerMarkedDownError(MemcacheServerError):
...
class ServerMarkedDownError(MemcacheServerError): ...
7 changes: 4 additions & 3 deletions src/meta_memcache/executors/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ def _build_cmd(

def _prepare_serialized_value_and_flags(
self,
key: Key,
value: ValueContainer,
flags: Optional[RequestFlags],
) -> Tuple[Optional[bytes], RequestFlags]:
encoded_value = self._serializer.serialize(value.value)
encoded_value = self._serializer.serialize(key, value.value)
flags = flags if flags is not None else RequestFlags()
flags.client_flag = encoded_value.encoding_id
return encoded_value.data, flags
Expand Down Expand Up @@ -106,7 +107,7 @@ def exec_on_pool(
cmd_value, flags = (
(None, flags)
if value is None
else self._prepare_serialized_value_and_flags(value, flags)
else self._prepare_serialized_value_and_flags(key, value, flags)
)
try:
conn = pool.pop_connection()
Expand Down Expand Up @@ -159,7 +160,7 @@ def exec_multi_on_pool( # noqa: C901
cmd_value, flags = (
(None, flags)
if value is None
else self._prepare_serialized_value_and_flags(value, flags)
else self._prepare_serialized_value_and_flags(key, value, flags)
)

self._conn_send_cmd(
Expand Down
11 changes: 5 additions & 6 deletions src/meta_memcache/interfaces/cache_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@

class CacheApi(CommandsProtocol, Protocol):
@property
def on_write_failure(self) -> WriteFailureEvent:
... # pragma: no cover
def on_write_failure(self) -> WriteFailureEvent: ... # pragma: no cover

@on_write_failure.setter
def on_write_failure(self, value: WriteFailureEvent) -> None:
... # pragma: no cover
def on_write_failure(
self, value: WriteFailureEvent
) -> None: ... # pragma: no cover

def get_counters(self) -> Dict[ServerAddress, PoolCounters]:
... # pragma: no cover
def get_counters(self) -> Dict[ServerAddress, PoolCounters]: ... # pragma: no cover
8 changes: 4 additions & 4 deletions src/meta_memcache/interfaces/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ def exec_multi_on_pool(
... # pragma: no cover

@property
def on_write_failure(self) -> WriteFailureEvent:
... # pragma: no cover
def on_write_failure(self) -> WriteFailureEvent: ... # pragma: no cover

@on_write_failure.setter
def on_write_failure(self, value: WriteFailureEvent) -> None:
... # pragma: no cover
def on_write_failure(
self, value: WriteFailureEvent
) -> None: ... # pragma: no cover
45 changes: 15 additions & 30 deletions src/meta_memcache/interfaces/high_level_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ def set(
cas_token: Optional[int] = None,
stale_policy: Optional[StalePolicy] = None,
set_mode: SetMode = SetMode.SET,
) -> bool:
... # pragma: no cover
) -> bool: ... # pragma: no cover

def refill(
self,
Expand Down Expand Up @@ -76,59 +75,52 @@ def touch(
key: Union[Key, str],
ttl: int,
no_reply: bool = False,
) -> bool:
... # pragma: no cover
) -> bool: ... # pragma: no cover

def get_or_lease(
self,
key: Union[Key, str],
lease_policy: LeasePolicy,
touch_ttl: Optional[int] = None,
recache_policy: Optional[RecachePolicy] = None,
) -> Optional[Any]:
... # pragma: no cover
) -> Optional[Any]: ... # pragma: no cover

def get_or_lease_cas(
self,
key: Union[Key, str],
lease_policy: LeasePolicy,
touch_ttl: Optional[int] = None,
recache_policy: Optional[RecachePolicy] = None,
) -> Tuple[Optional[Any], Optional[int]]:
... # pragma: no cover
) -> Tuple[Optional[Any], Optional[int]]: ... # pragma: no cover

def get(
self,
key: Union[Key, str],
touch_ttl: Optional[int] = None,
recache_policy: Optional[RecachePolicy] = None,
) -> Optional[Any]:
... # pragma: no cover
) -> Optional[Any]: ... # pragma: no cover

def multi_get(
self,
keys: Iterable[Union[Key, str]],
touch_ttl: Optional[int] = None,
recache_policy: Optional[RecachePolicy] = None,
) -> Dict[Key, Optional[Any]]:
... # pragma: no cover
) -> Dict[Key, Optional[Any]]: ... # pragma: no cover

def _multi_get(
self,
keys: Iterable[Union[Key, str]],
touch_ttl: Optional[int] = None,
recache_policy: Optional[RecachePolicy] = None,
return_cas_token: bool = False,
) -> Dict[Key, Optional[Value]]:
... # pragma: no cover
) -> Dict[Key, Optional[Value]]: ... # pragma: no cover

def get_cas(
self,
key: Union[Key, str],
touch_ttl: Optional[int] = None,
recache_policy: Optional[RecachePolicy] = None,
) -> Tuple[Optional[Any], Optional[int]]:
... # pragma: no cover
) -> Tuple[Optional[Any], Optional[int]]: ... # pragma: no cover

def _get(
self,
Expand All @@ -137,8 +129,7 @@ def _get(
lease_policy: Optional[LeasePolicy] = None,
recache_policy: Optional[RecachePolicy] = None,
return_cas_token: bool = False,
) -> Optional[Value]:
... # pragma: no cover
) -> Optional[Value]: ... # pragma: no cover

def get_typed(
self,
Expand All @@ -147,8 +138,7 @@ def get_typed(
touch_ttl: Optional[int] = None,
recache_policy: Optional[RecachePolicy] = None,
error_on_type_mismatch: bool = False,
) -> Optional[T]:
... # pragma: no cover
) -> Optional[T]: ... # pragma: no cover

def get_cas_typed(
self,
Expand All @@ -157,8 +147,7 @@ def get_cas_typed(
touch_ttl: Optional[int] = None,
recache_policy: Optional[RecachePolicy] = None,
error_on_type_mismatch: bool = False,
) -> Tuple[Optional[T], Optional[int]]:
... # pragma: no cover
) -> Tuple[Optional[T], Optional[int]]: ... # pragma: no cover

def delta(
self,
Expand All @@ -167,8 +156,7 @@ def delta(
refresh_ttl: Optional[int] = None,
no_reply: bool = False,
cas_token: Optional[int] = None,
) -> bool:
... # pragma: no cover
) -> bool: ... # pragma: no cover

def delta_initialize(
self,
Expand All @@ -179,17 +167,15 @@ def delta_initialize(
refresh_ttl: Optional[int] = None,
no_reply: bool = False,
cas_token: Optional[int] = None,
) -> bool:
... # pragma: no cover
) -> bool: ... # pragma: no cover

def delta_and_get(
self,
key: Union[Key, str],
delta: int,
refresh_ttl: Optional[int] = None,
cas_token: Optional[int] = None,
) -> Optional[int]:
... # pragma: no cover
) -> Optional[int]: ... # pragma: no cover

def delta_initialize_and_get(
self,
Expand All @@ -199,5 +185,4 @@ def delta_initialize_and_get(
initial_ttl: int,
refresh_ttl: Optional[int] = None,
cas_token: Optional[int] = None,
) -> Optional[int]:
... # pragma: no cover
) -> Optional[int]: ... # pragma: no cover
15 changes: 5 additions & 10 deletions src/meta_memcache/interfaces/meta_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@ def meta_multiget(
keys: List[Key],
flags: Optional[RequestFlags] = None,
failure_handling: FailureHandling = DEFAULT_FAILURE_HANDLING,
) -> Dict[Key, ReadResponse]:
... # pragma: no cover
) -> Dict[Key, ReadResponse]: ... # pragma: no cover

def meta_get(
self,
key: Key,
flags: Optional[RequestFlags] = None,
failure_handling: FailureHandling = DEFAULT_FAILURE_HANDLING,
) -> ReadResponse:
... # pragma: no cover
) -> ReadResponse: ... # pragma: no cover

def meta_set(
self,
Expand All @@ -33,21 +31,18 @@ def meta_set(
ttl: int,
flags: Optional[RequestFlags] = None,
failure_handling: FailureHandling = DEFAULT_FAILURE_HANDLING,
) -> WriteResponse:
... # pragma: no cover
) -> WriteResponse: ... # pragma: no cover

def meta_delete(
self,
key: Key,
flags: Optional[RequestFlags] = None,
failure_handling: FailureHandling = DEFAULT_FAILURE_HANDLING,
) -> WriteResponse:
... # pragma: no cover
) -> WriteResponse: ... # pragma: no cover

def meta_arithmetic(
self,
key: Key,
flags: Optional[RequestFlags] = None,
failure_handling: FailureHandling = DEFAULT_FAILURE_HANDLING,
) -> WriteResponse:
... # pragma: no cover
) -> WriteResponse: ... # pragma: no cover
Loading

0 comments on commit d056746

Please sign in to comment.