Memcache Client for Python

Experimental memcached client library for python. This project is in WIP status, please don’t use it in production environment.

Key features:

  • Based on memcached’s new meta commands;

  • Asyncio support;

  • Type hints.

Installation

$ pip install memcache

API document

Core client

class memcache.AsyncMemcache(addr: tuple[str, int] | list[tuple[str, int]] | None = None, *, pool_size: int | None = 23, pool_timeout: int | None = 1, load_func: ~collections.abc.Callable[[str | bytes, bytes, int], ~typing.Any] = <function load>, dump_func: ~collections.abc.Callable[[str | bytes, ~typing.Any], tuple[bytes, int]] = <function dump>, username: str | None = None, password: str | None = None)

Async Memcache client.

Parameters:
  • addr

    memcached server addresses to be connected.

    The address can be a two elements tuple, as (ip, port) format.

    The address can be None, thus the default server ("localhost", 11211) should be used.

    The address can be a list of tuple, like [("192.168.1.10", 11211), ("192.168.1.11", 11211)]. In this situation, the keys will be hashed to one of those servers by consistent hash algorithm.

  • pool_size – The connection pool size. This size will be used as the max number to keep the connections for future uses.

  • pool_timeout – If the there is no available connection in the pool, and the pool_size is reached, wait the specified time to get an available connection, or a asyncio.TimeoutError is raised.

  • load_func – Function to load the bytes content from memcached to python values.

  • dump_func – Function to dump the python values to bytes content to store in memcached.

  • username – Memcached ASCII protocol authentication username.

  • password – Memcached ASCII protocol authentication password.

async add(key: bytes | str, value: Any, *, expire: int | None = None) bool
async append(key: bytes | str, value: Any) bool
async cas(key: bytes | str, value: Any, cas_token: int, *, expire: int | None = None) None

Store a value using compare-and-swap operation.

Parameters:
  • key – The key to store

  • value – The value to store

  • cas_token – The CAS token from a previous gets operation

  • expire – Optional expiration time in seconds

Raises:

MemcacheError – If the CAS token doesn’t match or other error occurs

async close() None
async decr(key: bytes | str, value: int = 1) int
async delete(key: bytes | str) bool
async execute_meta_command(command: MetaCommand) MetaResult
async flush_all() None
async get(key: bytes | str) Any | None
async get_many(keys: list[bytes | str]) dict[str, Any]
async gets(key: bytes | str) tuple[Any, int] | None

Get a value and its CAS token from memcached.

Parameters:

key – The key to retrieve

Returns:

A tuple of (value, cas_token) or None if key doesn’t exist

async incr(key: bytes | str, value: int = 1) int
async prepend(key: bytes | str, value: Any) bool
async replace(key: bytes | str, value: Any, *, expire: int | None = None) bool
async set(key: bytes | str, value: Any, *, expire: int | None = None) None
async touch(key: bytes | str, expire: int) bool
class memcache.Memcache(addr: tuple[str, int] | list[tuple[str, int]] | None = None, *, pool_size: int | None = 23, pool_timeout: int | None = 1, load_func: ~collections.abc.Callable[[str | bytes, bytes, int], ~typing.Any] = <function load>, dump_func: ~collections.abc.Callable[[str | bytes, ~typing.Any], tuple[bytes, int]] = <function dump>, username: str | None = None, password: str | None = None)

Memcache client.

Parameters:
  • addr

    memcached server addresses to be connected.

    The address can be a two elements tuple, as (ip, port) format.

    The address can be None, thus the default server ("localhost", 11211) should be used.

    The address can be a list of tuple, like [("192.168.1.10", 11211), ("192.168.1.11", 11211)]. In this situation, the keys will be hashed to one of those servers by consistent hash algorithm.

  • pool_size – The connection pool size. This size will be used as the max number to keep the connections for future uses.

  • pool_timeout – If the there is no available connection in the pool, and the pool_size is reached, wait the specified time to get an available connection, or a queue.Empty is raised.

  • load_func – Function to load the bytes content from memcached to python values.

  • dump_func – Function to dump the python values to bytes content to store in memcached.

  • username – Memcached ASCII protocol authentication username.

  • password – Memcached ASCII protocol authentication password.

add(key: bytes | str, value: Any, *, expire: int | None = None) bool
append(key: bytes | str, value: Any) bool
cas(key: bytes | str, value: Any, cas_token: int, *, expire: int | None = None) None

Store a value using compare-and-swap operation.

Parameters:
  • key – The key to store

  • value – The value to store

  • cas_token – The CAS token from a previous gets operation

  • expire – Optional expiration time in seconds

Raises:

MemcacheError – If the CAS token doesn’t match or other error occurs

close() None
decr(key: bytes | str, value: int = 1) int
delete(key: bytes | str) bool
execute_meta_command(command: MetaCommand) MetaResult
flush_all() None
get(key: bytes | str) Any | None
get_many(keys: list[bytes | str]) dict[str, Any]
gets(key: bytes | str) tuple[Any, int] | None

Get a value and its CAS token from memcached.

Parameters:

key – The key to retrieve

Returns:

A tuple of (value, cas_token) or None if key doesn’t exist

incr(key: bytes | str, value: int = 1) int
prepend(key: bytes | str, value: Any) bool
replace(key: bytes | str, value: Any, *, expire: int | None = None) bool
set(key: bytes | str, value: Any, *, expire: int | None = None) None
touch(key: bytes | str, expire: int) bool
exception memcache.MemcacheError
class memcache.MetaCommand(cm: bytes, key: bytes | str, datalen: int | None = None, flags: list[bytes] | None = None, value: bytes | None = None)
cm: bytes
datalen: int | None
dump() bytes
dump_header() bytes
flags: list[bytes]
key: bytes
value: bytes | None
class memcache.MetaResult(rc: bytes, datalen: int | None, flags: list[bytes], value: bytes | None)
datalen: int | None
flags: list[bytes]
static load_header(line: bytes) MetaResult
rc: bytes
value: bytes | None

Meta client (experimental)

Note

MetaClient and AsyncMetaClient live under memcache.experiment and their API may change in any minor release. If you depend on them, pin the minor version in your dependency spec (e.g. memcache~=0.14.0).

MetaClient is a high-level client for memcached’s meta protocol. The core methods (get/set/delete/increment/batch) cover the full protocol surface, while convenience wrappers such as add, cas, invalidate and get_with_lease package common usage patterns with safer defaults. Every result has an explicit status.

from memcache.experiment import Get, GetStatus, Meta, MetaClient, Set

with MetaClient(("localhost", 11211)) as client:
    client.set("key", {"message": "value"}, ttl=60)

    result = client.get("key", meta=Meta.CAS | Meta.TTL | Meta.SIZE)
    if result.status is GetStatus.HIT:
        print(result.value, result.item.cas, result.item.ttl)

AsyncMetaClient has the same concepts and call shape; its methods and lease fulfill() are awaited.

For protocol experts, client.meta maps the wire commands one-to-one (mg/ms/md/ma/me) with one keyword argument per protocol flag. It works on raw bytes and returns lightly parsed responses without serialization or semantic mapping.

exception memcache.experiment.AmbiguousWriteError(result: Any | None = None)

A request was sent but its terminal response was not received.

class memcache.experiment.Arithmetic(key: 'Key', delta: 'int' = 1, decrement: 'bool' = False, initial: 'int | None' = None, initial_ttl: 'int | None' = None, ttl: 'int | None' = None, compare_cas: 'int | None' = None, version: 'int | None' = None, return_cas: 'bool' = False, return_ttl: 'bool' = False)
compare_cas: int | None = None
decrement: bool = False
delta: int = 1
initial: int | None = None
initial_ttl: int | None = None
key: str | bytes
return_cas: bool = False
return_ttl: bool = False
ttl: int | None = None
version: int | None = None
class memcache.experiment.ArithmeticResult(key: 'Key', status: 'MutationStatus', value: 'int | None' = None, item: 'ItemMeta' = ItemMeta(cas=None, ttl=None, size=None, last_access=None, hit_before=None), error: 'BaseException | None' = None)
error: BaseException | None = None
item: ItemMeta = ItemMeta(cas=None, ttl=None, size=None, last_access=None, hit_before=None)
key: str | bytes
status: MutationStatus
value: int | None = None
class memcache.experiment.AsyncMetaClient(addr: tuple[str, int] | list[tuple[str, int]] | None = None, *, pool_size: int | None = 23, pool_timeout: int | None = 1, timeout: float | None = 1.0, serializer: Serializer | None = None, username: str | None = None, password: str | None = None)

High-level meta protocol client with a batch-first executor.

async add(key: str | bytes, value: Any, *, ttl: int | None = None, version: int | None = None, return_cas: bool = False, timeout: float | None = None) MutationResult

Store only if the key does not exist; ALREADY_EXISTS otherwise.

async append(key: str | bytes, value: bytes, *, vivify_ttl: int | None = None, timeout: float | None = None) MutationResult

Append bytes to an existing value; serialization is skipped.

async batch(operations: Sequence[Get | Set | Delete | Arithmetic], *, timeout: float | None = None) BatchResult
async cas(key: str | bytes, value: Any, cas_token: int, *, ttl: int | None = None, version: int | None = None, return_cas: bool = False, timeout: float | None = None) MutationResult

Store only if the item’s CAS still matches; CAS_MISMATCH otherwise.

async close() None
async decrement(key: str | bytes, delta: int = 1, *, initial: int | None = None, initial_ttl: int | None = None, ttl: int | None = None, compare_cas: int | None = None, version: int | None = None, return_cas: bool = False, return_ttl: bool = False, timeout: float | None = None) ArithmeticResult

Decrement a counter; saturates at zero instead of underflowing.

async delete(key: str | bytes, *, compare_cas: int | None = None, timeout: float | None = None) MutationResult
async execute_meta_command(command: MetaCommand, *, timeout: float | None = None) MetaResult
async flush_all(delay: int = 0) None
async get(key: str | bytes, *, value: bool = True, meta: Meta = <Meta.NONE: 0>, touch: int | None = None, no_lru_bump: bool = False, unless_cas: int | None = None, lease_ttl: int | None = None, refresh_before: int | None = None, timeout: float | None = None) GetResult[Any]

Read a key; covers every mg capability.

With lease_ttl the result is a LeaseResult; use get_with_lease() for the statically typed variant.

async get_with_lease(key: str | bytes, *, lease_ttl: int, refresh_before: int | None = None, meta: Meta = <Meta.NONE: 0>, timeout: float | None = None) LeaseResult[Any]
async increment(key: str | bytes, delta: int = 1, *, initial: int | None = None, initial_ttl: int | None = None, ttl: int | None = None, compare_cas: int | None = None, version: int | None = None, return_cas: bool = False, return_ttl: bool = False, timeout: float | None = None) ArithmeticResult

Increment a counter; overflows wrap around (unsigned 64-bit).

async inspect(key: str | bytes, *, meta: Meta = <Meta.CAS|TTL|SIZE: 7>, no_lru_bump: bool = True, timeout: float | None = None) GetResult[Any]
async invalidate(key: str | bytes, *, stale_for: int | None = None, compare_cas: int | None = None, timeout: float | None = None) MutationResult
async prepend(key: str | bytes, value: bytes, *, vivify_ttl: int | None = None, timeout: float | None = None) MutationResult

Prepend bytes to an existing value; serialization is skipped.

async set(key: str | bytes, value: Any, *, ttl: int | None = None, mode: str = 'set', compare_cas: int | None = None, version: int | None = None, return_cas: bool = False, vivify_ttl: int | None = None, timeout: float | None = None) MutationResult

Store a key; covers every ms capability.

mode is one of set/add/replace/append/prepend; the concatenation modes take bytes only and skip serialization.

async touch(key: str | bytes, ttl: int, *, timeout: float | None = None) MutationResult
class memcache.experiment.BatchResult(results: Sequence[GetResult[Any] | MutationResult | ArithmeticResult])
class memcache.experiment.CompressedSerializer(serializer: Serializer, *, min_size: int = 1024, level: int | None = None)

Wraps another serializer, compressing large payloads with zstd.

zstd dominates the cache-hot-path tradeoff: better ratio than zlib at a fraction of the CPU, with decompression speed where a read-heavy cache spends its time (stdlib on 3.14+, backports.zstd before). Reads sniff the payload header instead of trusting a codec label, so zstd, zlib, gzip, bz2, and lzma/xz payloads are all readable regardless of which client or library version wrote them.

FLAG_COMPRESSED is set only when compression actually shrank the payload; incompressible values are stored as-is. FLAG_INT values are never compressed because the server-side arithmetic commands parse the stored bytes directly.

dump(key: str | bytes, value: Any) tuple[bytes, int]
load(key: str | bytes, value: bytes, flags: int) Any
class memcache.experiment.Delete(key: 'Key', compare_cas: 'int | None' = None, invalidate: 'bool' = False, stale_for: 'int | None' = None)
compare_cas: int | None = None
invalidate: bool = False
key: str | bytes
stale_for: int | None = None
class memcache.experiment.Get(key: 'Key', meta: 'Meta' = <Meta.NONE: 0>, touch: 'int | None' = None, no_lru_bump: 'bool' = False, unless_cas: 'int | None' = None, value: 'bool' = True, lease_ttl: 'int | None' = None, refresh_before: 'int | None' = None)
key: str | bytes
lease_ttl: int | None = None
meta: Meta = 0
no_lru_bump: bool = False
refresh_before: int | None = None
touch: int | None = None
unless_cas: int | None = None
value: bool = True
class memcache.experiment.GetResult(*, key: str | bytes, status: GetStatus, value: Any = <object object>, item: ItemMeta | None = None, value_state: ValueState = ValueState.MISSING, lease_state: LeaseState = LeaseState.NONE, error: BaseException | None = None)
property already_won: bool
property cas_token: int | None
property hit_before: bool | None
property is_stale: bool
property last_access: int | None
property size: int | None
property ttl: int | None
property value: T
value_or(default: T) T
property won_recache: bool
class memcache.experiment.GetStatus(*values)
AMBIGUOUS = 6
FAILED = 5
HIT = 1
MISS = 2
PENDING = 3
UNCHANGED = 4
class memcache.experiment.ItemMeta(cas: 'int | None' = None, ttl: 'int | None' = None, size: 'int | None' = None, last_access: 'int | None' = None, hit_before: 'bool | None' = None)
cas: int | None = None
hit_before: bool | None = None
last_access: int | None = None
size: int | None = None
ttl: int | None = None
class memcache.experiment.JsonSerializer

Falls back to JSON for arbitrary objects.

Cross-language but lossy: tuples come back as lists, dict keys are coerced to str, and bytes inside containers are rejected.

dump_object(key: str | bytes, value: Any) tuple[bytes, int]
load_object(key: str | bytes, value: bytes, flags: int) Any
class memcache.experiment.LeaseResult(*, fulfill: Callable[[...], Any], **kwargs: Any)
fulfill(value: T, *, ttl: int | None = None, version: int | None = None, return_cas: bool = False, timeout: float | None = None) Any
class memcache.experiment.LeaseState(*values)
BUSY = 3
GRANTED = 2
NONE = 1
class memcache.experiment.Meta(*values)
CAS = 1
HIT_BEFORE = 16
LAST_ACCESS = 8
NONE = 0
SIZE = 4
TTL = 2
class memcache.experiment.MetaClient(addr: tuple[str, int] | list[tuple[str, int]] | None = None, *, pool_size: int | None = 23, pool_timeout: int | None = 1, timeout: float | None = 1.0, serializer: Serializer | None = None, username: str | None = None, password: str | None = None)

Native synchronous meta protocol client.

add(key: str | bytes, value: Any, *, ttl: int | None = None, version: int | None = None, return_cas: bool = False, timeout: float | None = None) MutationResult

Store only if the key does not exist; ALREADY_EXISTS otherwise.

append(key: str | bytes, value: bytes, *, vivify_ttl: int | None = None, timeout: float | None = None) MutationResult

Append bytes to an existing value; serialization is skipped.

batch(operations: Sequence[Get | Set | Delete | Arithmetic], *, timeout: float | None = None) BatchResult
cas(key: str | bytes, value: Any, cas_token: int, *, ttl: int | None = None, version: int | None = None, return_cas: bool = False, timeout: float | None = None) MutationResult

Store only if the item’s CAS still matches; CAS_MISMATCH otherwise.

close() None
decrement(key: str | bytes, delta: int = 1, *, initial: int | None = None, initial_ttl: int | None = None, ttl: int | None = None, compare_cas: int | None = None, version: int | None = None, return_cas: bool = False, return_ttl: bool = False, timeout: float | None = None) ArithmeticResult

Decrement a counter; saturates at zero instead of underflowing.

delete(key: str | bytes, *, compare_cas: int | None = None, timeout: float | None = None) MutationResult
execute_meta_command(command: MetaCommand, *, timeout: float | None = None) MetaResult
flush_all(delay: int = 0) None
get(key: str | bytes, *, value: bool = True, meta: Meta = <Meta.NONE: 0>, touch: int | None = None, no_lru_bump: bool = False, unless_cas: int | None = None, lease_ttl: int | None = None, refresh_before: int | None = None, timeout: float | None = None) GetResult[Any]

Read a key; covers every mg capability.

With lease_ttl the result is a LeaseResult; use get_with_lease() for the statically typed variant.

get_with_lease(key: str | bytes, *, lease_ttl: int, refresh_before: int | None = None, meta: Meta = <Meta.NONE: 0>, timeout: float | None = None) LeaseResult[Any]
increment(key: str | bytes, delta: int = 1, *, initial: int | None = None, initial_ttl: int | None = None, ttl: int | None = None, compare_cas: int | None = None, version: int | None = None, return_cas: bool = False, return_ttl: bool = False, timeout: float | None = None) ArithmeticResult

Increment a counter; overflows wrap around (unsigned 64-bit).

inspect(key: str | bytes, *, meta: Meta = <Meta.CAS|TTL|SIZE: 7>, no_lru_bump: bool = True, timeout: float | None = None) GetResult[Any]
invalidate(key: str | bytes, *, stale_for: int | None = None, compare_cas: int | None = None, timeout: float | None = None) MutationResult
prepend(key: str | bytes, value: bytes, *, vivify_ttl: int | None = None, timeout: float | None = None) MutationResult

Prepend bytes to an existing value; serialization is skipped.

set(key: str | bytes, value: Any, *, ttl: int | None = None, mode: str = 'set', compare_cas: int | None = None, version: int | None = None, return_cas: bool = False, vivify_ttl: int | None = None, timeout: float | None = None) MutationResult

Store a key; covers every ms capability.

mode is one of set/add/replace/append/prepend; the concatenation modes take bytes only and skip serialization.

touch(key: str | bytes, ttl: int, *, timeout: float | None = None) MutationResult
class memcache.experiment.MetaCommandResult(rc: bytes, value: bytes | None = None, cas: int | None = None, ttl: int | None = None, client_flags: int | None = None, size: int | None = None, last_access: int | None = None, hit_before: bool | None = None, key: bytes | None = None, opaque: bytes | None = None, won: bool = False, busy: bool = False, stale: bool = False, flags: tuple[bytes, ...] = ())

A lightly parsed meta protocol response.

rc is the wire return code (HD, VA, EN, NS, EX, NF); value is the raw data block, if any. The remaining fields are response flags decoded to native types; a field is None (or False for markers) when the server did not send the flag.

busy: bool = False
cas: int | None = None
client_flags: int | None = None
flags: tuple[bytes, ...] = ()
hit_before: bool | None = None
key: bytes | None = None
last_access: int | None = None
property ok: bool
opaque: bytes | None = None
rc: bytes
size: int | None = None
stale: bool = False
ttl: int | None = None
value: bytes | None = None
won: bool = False
class memcache.experiment.MutationResult(key: 'Key', status: 'MutationStatus', cas: 'int | None' = None, error: 'BaseException | None' = None)
cas: int | None = None
error: BaseException | None = None
key: str | bytes
status: MutationStatus
class memcache.experiment.MutationStatus(*values)
ALREADY_EXISTS = 3
AMBIGUOUS = 6
CAS_MISMATCH = 4
FAILED = 5
NOT_FOUND = 2
STORED = 1
class memcache.experiment.PickleSerializer

Falls back to pickle for arbitrary objects.

pickle.loads executes code embedded in the payload; use this only when every writer to the cache is trusted.

dump_object(key: str | bytes, value: Any) tuple[bytes, int]
load_object(key: str | bytes, value: bytes, flags: int) Any
exception memcache.experiment.ProtocolError

The server returned a malformed or unsupported protocol response.

exception memcache.experiment.ResultValueError

Raised when a result does not carry a usable value.

exception memcache.experiment.SerializeError
class memcache.experiment.Serializer(*args, **kwargs)

Paired value codec; load must understand every flag dump emits.

dump(key: str | bytes, value: Any) tuple[bytes, int]
load(key: str | bytes, value: bytes, flags: int) Any
class memcache.experiment.Set(key: 'Key', value: 'Any', ttl: 'int | None' = None, mode: 'str' = 'set', compare_cas: 'int | None' = None, version: 'int | None' = None, return_cas: 'bool' = False, vivify_ttl: 'int | None' = None)
compare_cas: int | None = None
key: str | bytes
mode: str = 'set'
return_cas: bool = False
ttl: int | None = None
value: Any
version: int | None = None
vivify_ttl: int | None = None
class memcache.experiment.StrictSerializer

Stores bytes, int, and str only; never pickles and never unpickles.

dump_object(key: str | bytes, value: Any) tuple[bytes, int]
load_object(key: str | bytes, value: bytes, flags: int) Any
class memcache.experiment.ValueState(*values)
FRESH = 1
MISSING = 3
STALE = 2

License

Memcache is distributed by a MIT license.