django-constance/constance/base.py
Philipp Thumfart 4ac1e546c7
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
Added async support (#656)
* Added async logic

* Added tests and fixed async deadlock on aset

* Used abstract base class for backend to simplify code coverage

* Reordered try except block

* Added explicit thread safety

* Fixed linting error

* Worked on redis init block

* Fixed async test setup

* Added tests for redis instantiation

* Fixed linting errors
2026-03-04 16:37:37 -06:00

149 lines
4.1 KiB
Python

import asyncio
import warnings
from . import settings
from . import utils
class AsyncValueProxy:
def __init__(self, key, config, default):
self._key = key
self._config = config
self._default = default
self._value = None
self._fetched = False
def __await__(self):
return self._get_value().__await__()
async def _get_value(self):
if not self._fetched:
result = await self._config._backend.aget(self._key)
if result is None:
result = self._default
await self._config.aset(self._key, result)
self._value = result
self._fetched = True
return self._value
def _get_sync_value(self):
warnings.warn(
f"Synchronous access to Constance setting '{self._key}' inside an async loop. "
f"Use 'await config.{self._key}' instead.",
RuntimeWarning,
stacklevel=3,
)
return self._config._get_sync_value(self._key, self._default)
def __str__(self):
return str(self._get_sync_value())
def __repr__(self):
return repr(self._get_sync_value())
def __int__(self):
return int(self._get_sync_value())
def __float__(self):
return float(self._get_sync_value())
def __bool__(self):
return bool(self._get_sync_value())
def __eq__(self, other):
return self._get_sync_value() == other
def __ne__(self, other):
return self._get_sync_value() != other
def __lt__(self, other):
return self._get_sync_value() < other
def __le__(self, other):
return self._get_sync_value() <= other
def __gt__(self, other):
return self._get_sync_value() > other
def __ge__(self, other):
return self._get_sync_value() >= other
def __getitem__(self, key):
return self._get_sync_value()[key]
def __iter__(self):
return iter(self._get_sync_value())
def __len__(self):
return len(self._get_sync_value())
def __contains__(self, item):
return item in self._get_sync_value()
def __hash__(self):
return hash(self._get_sync_value())
def __add__(self, other):
return self._get_sync_value() + other
def __sub__(self, other):
return self._get_sync_value() - other
def __mul__(self, other):
return self._get_sync_value() * other
def __truediv__(self, other):
return self._get_sync_value() / other
class Config:
"""The global config wrapper that handles the backend."""
def __init__(self):
super().__setattr__("_backend", utils.import_module_attr(settings.BACKEND)())
def _get_sync_value(self, key, default):
result = self._backend.get(key)
if result is None:
result = default
setattr(self, key, default)
return result
def __getattr__(self, key):
if key == "_backend":
return super().__getattribute__(key)
try:
if len(settings.CONFIG[key]) not in (2, 3):
raise AttributeError(key)
default = settings.CONFIG[key][0]
except KeyError as e:
raise AttributeError(key) from e
try:
asyncio.get_running_loop()
except RuntimeError:
return self._get_sync_value(key, default)
return AsyncValueProxy(key, self, default)
def __setattr__(self, key, value):
if key == "_backend":
super().__setattr__(key, value)
return
if key not in settings.CONFIG:
raise AttributeError(key)
self._backend.set(key, value)
return
async def aset(self, key, value):
if key not in settings.CONFIG:
raise AttributeError(key)
await self._backend.aset(key, value)
async def amget(self, keys):
backend_values = await self._backend.amget(keys)
# Merge with defaults like utils.get_values_for_keys
default_initial = {name: settings.CONFIG[name][0] for name in keys if name in settings.CONFIG}
return dict(default_initial, **backend_values)
def __dir__(self):
return settings.CONFIG.keys()