details:   https://code.tryton.org/tryton/commit/950ce335bfce
branch:    default
user:      Cédric Krier <[email protected]>
date:      Mon Nov 03 23:08:31 2025 +0100
description:
        Store only immutable structure or make a shallow copy in MemoryCache

        Closes #14347
diffstat:

 trytond/CHANGELOG                   |   1 +
 trytond/doc/ref/cache.rst           |   5 +++++
 trytond/trytond/cache.py            |  18 +++++++++++++++---
 trytond/trytond/ir/action.py        |   6 +++++-
 trytond/trytond/ir/model.py         |   2 ++
 trytond/trytond/ir/ui/view.py       |   2 +-
 trytond/trytond/tests/test_cache.py |   2 +-
 7 files changed, 30 insertions(+), 6 deletions(-)

diffs (132 lines):

diff -r 5395d22d4a07 -r 950ce335bfce trytond/CHANGELOG
--- a/trytond/CHANGELOG Tue Oct 21 11:00:36 2025 +0200
+++ b/trytond/CHANGELOG Mon Nov 03 23:08:31 2025 +0100
@@ -1,3 +1,4 @@
+* Store only immutable structure in MemoryCache
 * Replace ``clean_days`` with ``log_size`` in the cron configuration
 * Enforce access check in export_data (issue14366)
 * Include the traceback only in RPC responses in development mode (issue14354)
diff -r 5395d22d4a07 -r 950ce335bfce trytond/doc/ref/cache.rst
--- a/trytond/doc/ref/cache.rst Tue Oct 21 11:00:36 2025 +0200
+++ b/trytond/doc/ref/cache.rst Mon Nov 03 23:08:31 2025 +0100
@@ -90,3 +90,8 @@
     By default Tryton uses a MemoryCache, but this behaviour can be overridden
     by setting a fully qualified name of an alternative class defined in the
     ``class`` of the :ref:`config-cache` section.
+
+.. note::
+
+   The MemoryCache convert the stored values into immutable structure and make
+   a copy for other structures.
diff -r 5395d22d4a07 -r 950ce335bfce trytond/trytond/cache.py
--- a/trytond/trytond/cache.py  Tue Oct 21 11:00:36 2025 +0200
+++ b/trytond/trytond/cache.py  Mon Nov 03 23:08:31 2025 +0100
@@ -1,12 +1,13 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
+import copy
 import datetime as dt
 import json
 import logging
 import selectors
 import threading
 from collections import OrderedDict, defaultdict
-from copy import deepcopy
+from types import MappingProxyType
 from uuid import uuid4
 from weakref import WeakKeyDictionary
 
@@ -54,6 +55,17 @@
         return o
 
 
+def immutable(o):
+    if isinstance(o, dict):
+        return MappingProxyType({k: immutable(v) for k, v in o.items()})
+    elif isinstance(o, list):
+        return tuple(immutable(v) for v in o)
+    elif isinstance(o, set):
+        return frozenset(immutable(v) for v in o)
+    else:
+        return copy.copy(o)
+
+
 def _get_modules(cursor):
     ir_module = Table('ir_module')
     cursor.execute(*ir_module.select(
@@ -199,7 +211,7 @@
                 return default
             cache.move_to_end(key)
             self.hit += 1
-            return deepcopy(result)
+            return result
         except (KeyError, TypeError):
             self.miss += 1
             return default
@@ -212,7 +224,7 @@
         else:
             expire = None
         try:
-            cache[key] = (expire, deepcopy(value))
+            cache[key] = (expire, immutable(value))
         except TypeError:
             pass
         return value
diff -r 5395d22d4a07 -r 950ce335bfce trytond/trytond/ir/action.py
--- a/trytond/trytond/ir/action.py      Tue Oct 21 11:00:36 2025 +0200
+++ b/trytond/trytond/ir/action.py      Mon Nov 03 23:08:31 2025 +0100
@@ -1,5 +1,6 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
+import copy
 import os
 from collections import defaultdict
 from functools import partial
@@ -731,7 +732,10 @@
             cls._template_cache.clear()
 
     def get_template_cached(self):
-        return self._template_cache.get(self.id)
+        template = self._template_cache.get(self.id)
+        if template is not None:
+            template = copy.copy(template)
+        return template
 
     def set_template_cached(self, template):
         self._template_cache.set(self.id, template)
diff -r 5395d22d4a07 -r 950ce335bfce trytond/trytond/ir/model.py
--- a/trytond/trytond/ir/model.py       Tue Oct 21 11:00:36 2025 +0200
+++ b/trytond/trytond/ir/model.py       Mon Nov 03 23:08:31 2025 +0100
@@ -186,6 +186,8 @@
                     if issubclass(pool_get(m), classes))
             items = list(items)
             cls._get_names_cache.set(key, items)
+        else:
+            items = list(items)
         return items
 
     @classmethod
diff -r 5395d22d4a07 -r 950ce335bfce trytond/trytond/ir/ui/view.py
--- a/trytond/trytond/ir/ui/view.py     Tue Oct 21 11:00:36 2025 +0200
+++ b/trytond/trytond/ir/ui/view.py     Mon Nov 03 23:08:31 2025 +0100
@@ -294,7 +294,7 @@
         key = (self.id, model)
         result = self._view_get_cache.get(key)
         if result:
-            return result
+            return result.copy()
         if self.inherit:
             if self.inherit.model == model:
                 return self.inherit.view_get(model=model)
diff -r 5395d22d4a07 -r 950ce335bfce trytond/trytond/tests/test_cache.py
--- a/trytond/trytond/tests/test_cache.py       Tue Oct 21 11:00:36 2025 +0200
+++ b/trytond/trytond/tests/test_cache.py       Mon Nov 03 23:08:31 2025 +0100
@@ -133,7 +133,7 @@
         cache.set('foo', value)
         value.remove('bar')
 
-        self.assertEqual(cache.get('foo'), ['bar'])
+        self.assertEqual(cache.get('foo'), ('bar',))
 
     @with_transaction()
     def test_memory_cache_drop(self):

Reply via email to