From 8ef5fbca4e90668c8ae1f9f60f4d5d43816a593c Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Thu, 1 May 2025 11:31:29 +0200 Subject: [PATCH] [fix] cache.ExpireCache: definition of a context name for the key The definition of a context name belongs in the abstract base class (was previously only in the concrete implementation for the SQLite adapter). Suggested-by: @dalf https://github.com/searxng/searxng/pull/4650#discussion_r2069873853 Signed-off-by: Markus Heiser --- searx/cache.py | 59 +++++++++++++++++++++++-------------- searx/enginelib/__init__.py | 4 +-- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/searx/cache.py b/searx/cache.py index dc388207d..ea6c17be9 100644 --- a/searx/cache.py +++ b/searx/cache.py @@ -83,12 +83,12 @@ class ExpireCacheStats: """Dataclass wich provides information on the status of the cache.""" cached_items: dict[str, list[tuple[str, typing.Any, int]]] - """Values in the cache mapped by table name. + """Values in the cache mapped by context name. .. code: python { - "table name": [ + "context name": [ ("foo key": "foo value", ), ("bar key": "bar value", ), # ... @@ -98,22 +98,22 @@ class ExpireCacheStats: """ def report(self): - c_tables = 0 + c_ctx = 0 c_kv = 0 lines = [] - for table_name, kv_list in self.cached_items.items(): - c_tables += 1 + for ctx_name, kv_list in self.cached_items.items(): + c_ctx += 1 if not kv_list: - lines.append(f"[{table_name:20s}] empty") + lines.append(f"[{ctx_name:20s}] empty") continue for key, value, expire in kv_list: valid_until = datetime.datetime.fromtimestamp(expire).strftime("%Y-%m-%d %H:%M:%S") c_kv += 1 - lines.append(f"[{table_name:20s}] {valid_until} {key:12}" f" --> ({type(value).__name__}) {value} ") + lines.append(f"[{ctx_name:20s}] {valid_until} {key:12}" f" --> ({type(value).__name__}) {value} ") - lines.append(f"number of tables: {c_tables}") + lines.append(f"Number of contexts: {c_ctx}") lines.append(f"number of key/value pairs: {c_kv}") return "\n".join(lines) @@ -127,15 +127,27 @@ class ExpireCache(abc.ABC): hash_token = "hash_token" @abc.abstractmethod - def set(self, key: str, value: typing.Any, expire: int | None) -> bool: + def set(self, key: str, value: typing.Any, expire: int | None, ctx: str | None = None) -> bool: """Set *key* to *value*. To set a timeout on key use argument ``expire`` (in sec.). If expire is unset the default is taken from :py:obj:`ExpireCacheCfg.MAXHOLD_TIME`. After the timeout has expired, the key will automatically be deleted. + + The ``ctx`` argument specifies the context of the ``key``. A key is + only unique in its context. + + The concrete implementations of this abstraction determine how the + context is mapped in the connected database. In SQL databases, for + example, the context is a DB table or in a Key/Value DB it could be + a prefix for the key. + + If the context is not specified (the default is ``None``) then a + default context should be used, e.g. a default table for SQL databases + or a default prefix in a Key/Value DB. """ @abc.abstractmethod - def get(self, key: str, default=None) -> typing.Any: + def get(self, key: str, default=None, ctx: str | None = None) -> typing.Any: """Return *value* of *key*. If key is unset, ``None`` is returned.""" @abc.abstractmethod @@ -171,7 +183,7 @@ class ExpireCache(abc.ABC): @staticmethod def normalize_name(name: str) -> str: """Returns a normalized name that can be used as a file name or as a SQL - table name.""" + table name (is used, for example, to normalize the context name).""" _valid = "-_." + string.ascii_letters + string.digits return "".join([c for c in name if c in _valid]) @@ -320,14 +332,15 @@ class ExpireCacheSQLite(sqlitedb.SQLiteAppl, ExpireCache): # implement ABC methods of ExpireCache - def set(self, key: str, value: typing.Any, expire: int | None, table: str | None = None) -> bool: - """Set key/value in ``table``. If expire is unset the default is taken - from :py:obj:`ExpireCacheCfg.MAXHOLD_TIME`. If ``table`` argument is - ``None`` (the default), a table name is generated from the - :py:obj:`ExpireCacheCfg.name`. If DB ``table`` does not exists, it will be - created (on demand) by :py:obj:`self.create_table + def set(self, key: str, value: typing.Any, expire: int | None, ctx: str | None = None) -> bool: + """Set key/value in DB table given by argument ``ctx``. If expire is + unset the default is taken from :py:obj:`ExpireCacheCfg.MAXHOLD_TIME`. + If ``ctx`` argument is ``None`` (the default), a table name is + generated from the :py:obj:`ExpireCacheCfg.name`. If DB table does not + exists, it will be created (on demand) by :py:obj:`self.create_table `. """ + table = ctx self.maintenance() value = self.serialize(value=value) @@ -360,12 +373,14 @@ class ExpireCacheSQLite(sqlitedb.SQLiteAppl, ExpireCache): return True - def get(self, key: str, default=None, table: str | None = None) -> typing.Any: - """Get value of ``key`` from ``table``. If ``table`` argument is - ``None`` (the default), a table name is generated from the - :py:obj:`ExpireCacheCfg.name`. If ``key`` not exists (in table), the - ``default`` value is returned. + def get(self, key: str, default=None, ctx: str | None = None) -> typing.Any: + """Get value of ``key`` from table given by argument ``ctx``. If + ``ctx`` argument is ``None`` (the default), a table name is generated + from the :py:obj:`ExpireCacheCfg.name`. If ``key`` not exists (in + table), the ``default`` value is returned. + """ + table = ctx self.maintenance() if not table: diff --git a/searx/enginelib/__init__.py b/searx/enginelib/__init__.py index 7449578fa..0435b14e1 100644 --- a/searx/enginelib/__init__.py +++ b/searx/enginelib/__init__.py @@ -161,11 +161,11 @@ class EngineCache: key=key, value=value, expire=expire or self.expire, - table=self.table_name, + ctx=self.table_name, ) def get(self, key: str, default=None) -> Any: - return ENGINES_CACHE.get(key, default=default, table=self.table_name) + return ENGINES_CACHE.get(key, default=default, ctx=self.table_name) def secret_hash(self, name: str | bytes) -> str: return ENGINES_CACHE.secret_hash(name=name)