summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Cook <jordan.cook@pioneer.com>2021-08-09 22:37:51 -0500
committerJordan Cook <jordan.cook.git@proton.me>2023-03-01 17:35:57 -0600
commit25e99f612fd4d4a4537037e33e5b8ab0f908d370 (patch)
tree4f453d9eb547a552a1c6c06200f9aa9e3ef805a6
parent812301ab85a3b54387dcebd7d65407ace2589b9f (diff)
downloadrequests-cache-memcached.tar.gz
WIP: Add a memcached backendmemcached
-rw-r--r--docker-compose.yml6
-rw-r--r--pyproject.toml4
-rw-r--r--requests_cache/backends/memcached.py83
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))