Fixed latent none return bug (#658)
Some checks failed
Docs / docs (push) Has been cancelled
Test / ruff-format (push) Has been cancelled
Test / ruff-lint (push) Has been cancelled
Test / build (3.10) (push) Has been cancelled
Test / build (3.11) (push) Has been cancelled
Test / build (3.12) (push) Has been cancelled
Test / build (3.13) (push) Has been cancelled
Test / build (3.14) (push) Has been cancelled
Test / build (3.8) (push) Has been cancelled
Test / build (3.9) (push) Has been cancelled

This commit is contained in:
Philipp Thumfart 2026-03-11 15:58:20 +01:00 committed by GitHub
parent 4ac1e546c7
commit 7e75db3ebc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 28 additions and 31 deletions

View file

@ -24,16 +24,18 @@ class Backend(ABC):
@abstractmethod
def mget(self, keys):
"""
Get the keys from the backend store and return a list of the values.
Return an empty list if not found.
Get the keys from the backend store and return a dict mapping
each found key to its value. Return an empty dict if no keys
are provided or none are found.
"""
...
@abstractmethod
async def amget(self, keys):
"""
Get the keys from the backend store and return a list of the values.
Return an empty list if not found.
Get the keys from the backend store and return a dict mapping
each found key to its value. Return an empty dict if no keys
are provided or none are found.
"""
...

View file

@ -54,20 +54,22 @@ class DatabaseBackend(Backend):
if self._cache.get(full_cachekey):
return
autofill_values = {full_cachekey: 1}
for key, value in self.mget(settings.CONFIG):
for key, value in self.mget(settings.CONFIG).items():
autofill_values[self.add_prefix(key)] = value
self._cache.set_many(autofill_values, timeout=self._autofill_timeout)
def mget(self, keys):
result = {}
if not keys:
return
return result
keys = {self.add_prefix(key): key for key in keys}
try:
stored = self._model._default_manager.filter(key__in=keys)
for const in stored:
yield keys[const.key], loads(const.value)
result[keys[const.key]] = loads(const.value)
except (OperationalError, ProgrammingError):
pass
return result
def get(self, key):
key = self.add_prefix(key)

View file

@ -25,14 +25,9 @@ class MemoryBackend(Backend):
def mget(self, keys):
if not keys:
return None
result = []
return {}
with self._lock:
for key in keys:
value = self._storage.get(key)
if value is not None:
result.append((key, value))
return result
return {key: self._storage[key] for key in keys if key in self._storage}
async def amget(self, keys):
if not keys:

View file

@ -73,11 +73,9 @@ class RedisBackend(Backend):
def mget(self, keys):
if not keys:
return
return {}
prefixed_keys = [self.add_prefix(key) for key in keys]
for key, value in zip(keys, self._rd.mget(prefixed_keys)):
if value:
yield key, loads(value)
return {key: loads(value) for key, value in zip(keys, self._rd.mget(prefixed_keys)) if value}
async def amget(self, keys):
if not keys:
@ -177,11 +175,13 @@ class CachingRedisBackend(RedisBackend):
def mget(self, keys):
if not keys:
return
return {}
result = {}
for key in keys:
value = self.get(key)
if value is not None:
yield key, value
result[key] = value
return result
async def amget(self, keys):
if not keys:

View file

@ -19,7 +19,7 @@ def get_values():
# First load a mapping between config name and default value
default_initial = ((name, options[0]) for name, options in settings.CONFIG.items())
# Then update the mapping with actually values from the backend
return dict(default_initial, **dict(config._backend.mget(settings.CONFIG)))
return dict(default_initial, **config._backend.mget(settings.CONFIG))
async def aget_values():
@ -52,7 +52,7 @@ def get_values_for_keys(keys):
raise AttributeError(f'"{", ".join(missing_keys)}" keys not found in configuration.')
# Merge default values and backend values, prioritizing backend values
return dict(default_initial, **dict(config._backend.mget(keys)))
return dict(default_initial, **config._backend.mget(keys))
async def aget_values_for_keys(keys):

View file

@ -19,7 +19,7 @@ class TestMemory(StorageTestsMixin, TestCase):
def test_mget_empty_keys(self):
result = self.config._backend.mget([])
self.assertIsNone(result)
self.assertEqual(result, {})
class TestMemoryAsync(TransactionTestCase):

View file

@ -24,18 +24,16 @@ class TestRedis(StorageTestsMixin, TestCase):
settings.BACKEND = self.old_backend
def test_mget_empty_keys(self):
# Test that mget returns None for empty keys
result = list(self.config._backend.mget([]) or [])
self.assertEqual(result, [])
result = self.config._backend.mget([])
self.assertEqual(result, {})
class TestCachingRedis(TestRedis):
_BACKEND = "constance.backends.redisd.CachingRedisBackend"
def test_mget_empty_keys(self):
# Test that mget returns None for empty keys
result = list(self.config._backend.mget([]) or [])
self.assertEqual(result, [])
result = self.config._backend.mget([])
self.assertEqual(result, {})
class TestRedisAsync(TransactionTestCase):

View file

@ -98,11 +98,11 @@ class StorageTestsMixin:
self.config.BOOL_VALUE = False
self.config.STRING_VALUE = ""
values = dict(self.config._backend.mget(settings.CONFIG))
values = 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))
result = self.config._backend.mget(settings.CONFIG)
self.assertEqual(result, {})