diff --git a/.bumpversion.cfg b/.bumpversion.cfg index c9734f1..420100e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.0 +current_version = 0.3.0 commit = False tag = False diff --git a/microcosm_caching/factories.py b/microcosm_caching/factories.py index e8c98ff..53c822d 100644 --- a/microcosm_caching/factories.py +++ b/microcosm_caching/factories.py @@ -1,15 +1,14 @@ from microcosm.api import defaults, typed -from microcosm.config.types import boolean +from microcosm.config.types import boolean, comma_separated_list from microcosm_caching.memcached import MemcachedCache @defaults( enabled=typed(boolean, default_value=False), - host="localhost", - port=typed(int, default_value=11211), - connect_timeout=3, - read_timeout=2, + servers=typed(comma_separated_list, default_value="localhost:11211"), + connect_timeout=typed(int, default_value=3), + read_timeout=typed(int, default_value=2), ) def configure_resource_cache(graph): """ @@ -21,12 +20,22 @@ def configure_resource_cache(graph): return None kwargs = dict( - host=graph.config.resource_cache.host, - port=graph.config.resource_cache.port, + servers=parse_server_config(graph.config.resource_cache.servers), connect_timeout=graph.config.resource_cache.connect_timeout, read_timeout=graph.config.resource_cache.read_timeout, ) + if graph.metadata.testing: kwargs.update(dict(testing=True)) return MemcachedCache(**kwargs) + + +def parse_server_config(servers): + # NB: Assume input of the form: ["host:port","host:port"] + parsed_servers = [] + for server in servers: + host, port = server.split(":") + parsed_servers.append((host, int(port))) + + return parsed_servers diff --git a/microcosm_caching/memcached.py b/microcosm_caching/memcached.py index 04e885c..0479163 100644 --- a/microcosm_caching/memcached.py +++ b/microcosm_caching/memcached.py @@ -4,8 +4,9 @@ """ from enum import IntEnum, unique from json import dumps, loads +from typing import Optional, Tuple -from pymemcache.client.base import Client +from pymemcache.client.hash import HashClient from pymemcache.test.utils import MockMemcacheClient from microcosm_caching.base import CacheBase @@ -33,9 +34,9 @@ def json_serializer(key, value): """ if isinstance(value, str) or isinstance(value, bytes): - return value, SerializationFlag.STRING + return value, SerializationFlag.STRING.value - return dumps(value), SerializationFlag.JSON + return dumps(value), SerializationFlag.JSON.value def json_deserializer(key, value, flags): @@ -65,8 +66,7 @@ class MemcachedCache(CacheBase): """ def __init__( self, - host="localhost", - port=11211, + servers: Optional[Tuple[str, int]] = None, connect_timeout=None, read_timeout=None, serializer=json_serializer, @@ -74,22 +74,30 @@ def __init__( testing=False, ): client_kwargs = dict( - server=(host, port), connect_timeout=connect_timeout, timeout=read_timeout, - serializer=json_serializer, - deserializer=json_deserializer, + serializer=serializer, + deserializer=deserializer, ) + if testing: - self.client = MockMemcacheClient(**client_kwargs) + self.client = MockMemcacheClient( + server=None, + **client_kwargs, + ) else: - self.client = Client(**client_kwargs) + self.client = HashClient( + servers=servers, + **client_kwargs, + ) - def get(self, key): + def get(self, key: str): return self.client.get(key) - def set(self, key, value, ttl=None): + def set(self, key: str, value, ttl=None): if ttl is None: # pymemcache interprets 0 as no expiration ttl = 0 + # NB: If input is malformed, this will not raise errors. + # set `noreply` to False for further debugging return self.client.set(key, value, expire=ttl) diff --git a/microcosm_caching/tests/test_factories.py b/microcosm_caching/tests/test_factories.py index b4a0d15..34304d2 100644 --- a/microcosm_caching/tests/test_factories.py +++ b/microcosm_caching/tests/test_factories.py @@ -6,6 +6,8 @@ from microcosm.api import create_object_graph, load_from_dict from parameterized import parameterized +from microcosm_caching.factories import parse_server_config + class TestResourceCacheFactory: @@ -29,9 +31,17 @@ def setup(self): )), ]) def test_set_and_get_value_when_resource_cache_is_enabled(self, key, value): - self.graph.resource_cache.set("key", value) + self.graph.resource_cache.set(key, value) assert_that( - self.graph.resource_cache.get("key"), + self.graph.resource_cache.get(key), is_(equal_to(value)), ) + + def test_parse_server_config(self): + assert_that( + parse_server_config(["server1:11211", "server2:22122"]), + is_( + [("server1", 11211), ("server2", 22122)], + ), + ) diff --git a/microcosm_caching/tests/test_serializers.py b/microcosm_caching/tests/test_serializers.py new file mode 100644 index 0000000..87198b7 --- /dev/null +++ b/microcosm_caching/tests/test_serializers.py @@ -0,0 +1,22 @@ +from json import dumps + +from hamcrest import assert_that, is_ +from parameterized import parameterized + +from microcosm_caching.memcached import SerializationFlag, json_deserializer, json_serializer + + +@parameterized([ + ("key", "string-value", ("string-value", SerializationFlag.STRING.value)), + ("key", dict(foo="bar"), (dumps(dict(foo="bar")), SerializationFlag.JSON.value)), +]) +def test_serializer(key, value, result): + assert_that(json_serializer(key, value), is_(result)) + + +@parameterized([ + ("key", "string-value", SerializationFlag.STRING.value, "string-value"), + ("key", dumps(dict(foo="bar")), SerializationFlag.JSON.value, dict(foo="bar")), +]) +def test_deserializer(key, value, flag, expected_value): + assert_that(json_deserializer(key, value, flag), is_(expected_value)) diff --git a/setup.py b/setup.py index c5f4e31..79c7267 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ project = "microcosm-caching" -version = "0.2.0" +version = "0.3.0" setup( name=project,