diff --git a/constance/backends/memory.py b/constance/backends/memory.py new file mode 100644 index 0000000..960766a --- /dev/null +++ b/constance/backends/memory.py @@ -0,0 +1,38 @@ +from threading import Lock + +from . import Backend +from .. import signals, config + + +class MemoryBackend(Backend): + """ + Simple in-memory backend that should be mostly used for testing purposes + """ + _storage = {} + _lock = Lock() + + def __init__(self): + super().__init__() + + def get(self, key): + with self._lock: + return self._storage.get(key) + + def mget(self, keys): + if not keys: + return + result = [] + with self._lock: + for key in keys: + value = self._storage.get(key) + if value is not None: + result.append((key, value)) + return result + + def set(self, key, value): + with self._lock: + old_value = self._storage.get(key) + self._storage[key] = value + signals.config_updated.send( + sender=config, key=key, old_value=old_value, new_value=value + ) diff --git a/docs/backends.rst b/docs/backends.rst index 61120c4..8b95315 100644 --- a/docs/backends.rst +++ b/docs/backends.rst @@ -144,3 +144,15 @@ configured cache backend to enable this feature, e.g. "default":: setting to ``None``. .. _django-picklefield: http://pypi.python.org/pypi/django-picklefield/ + +Memory +------ + +The configuration values are stored in a memory and do not persist between process +restarts. In order to use this backend you must set the ``CONSTANCE_BACKEND`` +Django setting to:: + + CONSTANCE_BACKEND = 'constance.backends.memory.MemoryBackend' + +The main purpose of this one is to be used mostly for testing/developing means, +so make sure you intentionally use it on production environments. diff --git a/docs/testing.rst b/docs/testing.rst index fb1ebac..9508a46 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -41,7 +41,7 @@ method level and also as a Pytest usage -~~~~~ +~~~~~~~~~~~~ Django-constance provides pytest plugin that adds marker :class:`@pytest.mark.override_config()`. It handles config override for @@ -108,3 +108,16 @@ Any scope, auto-used fixture alternative can also be implemented like this with override_config(API_URL="/awesome/url/"): yield + +Memory backend +~~~~~~~~~~~~~~ + +If you don't want to rely on any external services such as Redis or database when +running your unittests you can select :class:`MemoryBackend` for a test Django settings file + +.. code-block:: python + + CONSTANCE_BACKEND = 'constance.backends.memory.MemoryBackend' + +It will provide simple thread-safe backend which will reset to default values after each +test run. diff --git a/test_threads.py b/test_threads.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/backends/__init__.py b/tests/backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_database.py b/tests/backends/test_database.py similarity index 100% rename from tests/test_database.py rename to tests/backends/test_database.py diff --git a/tests/backends/test_memory.py b/tests/backends/test_memory.py new file mode 100644 index 0000000..e8069e9 --- /dev/null +++ b/tests/backends/test_memory.py @@ -0,0 +1,18 @@ +from django.test import TestCase + +from constance import settings + +from tests.storage import StorageTestsMixin + + +class TestMemory(StorageTestsMixin, TestCase): + + def setUp(self): + self.old_backend = settings.BACKEND + settings.BACKEND = 'constance.backends.memory.MemoryBackend' + super().setUp() + self.config._backend._storage = {} + + def tearDown(self): + self.config._backend._storage = {} + settings.BACKEND = self.old_backend diff --git a/tests/test_redis.py b/tests/backends/test_redis.py similarity index 100% rename from tests/test_redis.py rename to tests/backends/test_redis.py diff --git a/tests/redis_mockup.py b/tests/redis_mockup.py index 5b0778b..3b1743e 100644 --- a/tests/redis_mockup.py +++ b/tests/redis_mockup.py @@ -3,9 +3,4 @@ class Connection(dict): self[key] = value def mget(self, keys): - values = [] - for key in keys: - value = self.get(key, None) - if value is not None: - values.append(value) - return values + return [self.get(key) for key in keys] diff --git a/tests/storage.py b/tests/storage.py index 2c3e5f6..8994be7 100644 --- a/tests/storage.py +++ b/tests/storage.py @@ -1,6 +1,7 @@ from datetime import datetime, date, time, timedelta from decimal import Decimal +from constance import settings from constance.base import Config @@ -77,3 +78,18 @@ class StorageTestsMixin: self.assertEqual(self.config.DATE_VALUE, date(2001, 12, 20)) self.assertEqual(self.config.TIME_VALUE, time(1, 59, 0)) self.assertEqual(self.config.TIMEDELTA_VALUE, timedelta(days=1, hours=2, minutes=3)) + + def test_backend_retrieves_multiple_values(self): + # Check corner cases such as falsy values + self.config.INT_VALUE = 0 + self.config.BOOL_VALUE = False + self.config.STRING_VALUE = '' + + values = dict(self.config._backend.mget(settings.CONFIG)) + self.assertEqual(values['INT_VALUE'], 0) + self.assertEqual(values['BOOL_VALUE'], False) + self.assertEqual(values['STRING_VALUE'], '') + + def test_backend_does_not_return_none_values(self): + result = dict(self.config._backend.mget(settings.CONFIG)) + self.assertEqual(result, {})