diff options
author | Jordan Cook <jordan.cook@pioneer.com> | 2021-08-09 22:37:51 -0500 |
---|---|---|
committer | Jordan Cook <jordan.cook.git@proton.me> | 2023-03-01 17:35:57 -0600 |
commit | 25e99f612fd4d4a4537037e33e5b8ab0f908d370 (patch) | |
tree | 4f453d9eb547a552a1c6c06200f9aa9e3ef805a6 | |
parent | 812301ab85a3b54387dcebd7d65407ace2589b9f (diff) | |
download | requests-cache-memcached.tar.gz |
WIP: Add a memcached backendmemcached
-rw-r--r-- | docker-compose.yml | 6 | ||||
-rw-r--r-- | pyproject.toml | 4 | ||||
-rw-r--r-- | requests_cache/backends/memcached.py | 83 |
3 files changed, 92 insertions, 1 deletions
diff --git a/docker-compose.yml b/docker-compose.yml index bae7782..f9f91ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,12 @@ services: AWS_SECRET_ACCESS_KEY: 'placeholder' working_dir: '/home/dynamodblocal' + memcached: + image: docker.io/bitnami/memcached + container_name: memcached + ports: + - '11211:11211' + mongo: image: mongo container_name: mongo diff --git a/pyproject.toml b/pyproject.toml index ed0b4de..32882f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ boto3 = {optional=true, version=">=1.15"} botocore = {optional=true, version=">=1.18"} pymongo = {optional=true, version=">=3"} redis = {optional=true, version=">=3"} +pymemcache = {optional=true, version="^3.5.0"} # Optional serialization dependencies bson = {optional=true, version=">=0.5"} @@ -68,6 +69,7 @@ sphinxext-opengraph = {optional=true, version=">=0.6"} [tool.poetry.extras] # Package extras for optional backend dependencies dynamodb = ["boto3", "botocore"] +memcached = ["pymemcache"] mongodb = ["pymongo"] redis = ["redis"] @@ -78,7 +80,7 @@ security = ["itsdangerous"] yaml = ["pyyaml"] # All optional packages combined, for demo/evaluation purposes -all = ["boto3", "botocore", "itsdangerous", "pymongo", "pyyaml", "redis", "ujson"] +all = ["boto3", "botocore", "itsdangerous", "pymemcache", "pymongo", "pyyaml", "redis", "ujson"] # Documentation docs = ["furo", "linkify-it-py", "myst-parser", "sphinx", "sphinx-autodoc-typehints", diff --git a/requests_cache/backends/memcached.py b/requests_cache/backends/memcached.py new file mode 100644 index 0000000..b44aa6d --- /dev/null +++ b/requests_cache/backends/memcached.py @@ -0,0 +1,83 @@ +# TODO: Currently only supports text-based serialization formats. +# pymemcache does not yet have support for binary or meta protocol. +# pylibmc does, but requires libmemcached binaries on the host machine, +# so it's not compatible with memcached running in a Docker container. +import re +from telnetlib import Telnet + +from pymemcache.client.base import Client + +from . import BaseCache, BaseStorage, get_valid_kwargs + +KEY_PATTERN = re.compile(rb'ITEM (\S*)') + + +class FileCache(BaseCache): + """Memcached backend""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.responses = MemcacheDict('responses', **kwargs) + self.redirects = MemcacheDict('redirects', connection=self.responses.connection, **kwargs) + + +class MemcacheDict(BaseStorage): + """A dictionary-like interface to memcached""" + + def __init__( + self, + key_prefix: str = None, + connection=None, + host='localhost', + timeout: float = 5, + **kwargs, + ): + super().__init__(**kwargs) + connection_kwargs = get_valid_kwargs(Client, kwargs) + self.connection = connection or Client( + host, + key_prefix=key_prefix, + encoding='utf-8', + timeout=timeout, + **connection_kwargs, + ) + + def __getitem__(self, key): + item = self.connection.get(key) + if not item: + raise KeyError + return item + + def __setitem__(self, key, value): + self.connection.set(key, value) + + def __delitem__(self, key): + if not self.connection.delete(key): + raise KeyError + + # TODO: Is there a cleaner way to do this? + def __iter__(self): + host, port = self.client.server + stats_client = Telnet(host, port) + stats_client.write(b'stats cachedump 1 10000\n') + output = stats_client.read_until(b'END') + return KEY_PATTERN.findall(output) + + def __len__(self): + return self.connection.stats().get(b'curr_items', 0) + + def clear(self): + self.connection.flush_all() + + +class MemcachePickleDict(MemcacheDict): + """Same as :class:`MemcacheDict`, but serializes values before saving""" + + def __setitem__(self, key, value): + serialized_value = self.serializer.dumps(value) + # if isinstance(serialized_value, bytes): + # serialized_value = '?' + super().__setitem__(key, serialized_value) + + def __getitem__(self, key): + return self.serializer.loads(super().__getitem__(key)) |