From aefd12642ab04f0f379f7158a93eb2c71744fb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 19 Dec 2025 09:39:32 +0100 Subject: [PATCH] Use always the immutable value from the cache This prevents issues like #14433 from being missed. --- modules/account_payment_braintree/account.py | 3 +-- modules/account_payment_stripe/payment.py | 6 ++--- modules/attendance/attendance.py | 2 +- modules/company/res.py | 7 ++--- modules/party/address.py | 6 ++--- modules/product_price_list_cache/product.py | 3 ++- .../tests/test_module.py | 2 +- modules/project/work.py | 6 ++--- modules/stock/location.py | 2 +- .../carrier.py | 6 ++--- modules/timesheet_cost/company.py | 3 ++- modules/web_shop_shopify/web.py | 3 ++- trytond/doc/ref/cache.rst | 2 +- trytond/trytond/cache.py | 3 ++- trytond/trytond/ir/action.py | 18 +++++-------- trytond/trytond/ir/configuration.py | 3 +-- trytond/trytond/ir/lang.py | 11 ++++---- trytond/trytond/ir/message.py | 4 +-- trytond/trytond/ir/model.py | 27 +++++++------------ trytond/trytond/ir/rule.py | 3 +-- trytond/trytond/ir/translation.py | 3 +-- trytond/trytond/ir/ui/view.py | 5 ++-- trytond/trytond/model/dictschema.py | 3 +-- trytond/trytond/model/digits.py | 3 +-- trytond/trytond/model/modelsql.py | 2 +- trytond/trytond/model/modelstorage.py | 2 +- trytond/trytond/model/modelview.py | 10 +++---- trytond/trytond/report/report.py | 2 +- trytond/trytond/res/user.py | 3 +-- 29 files changed, 63 insertions(+), 90 deletions(-) diff --git a/modules/account_payment_braintree/account.py b/modules/account_payment_braintree/account.py index 5a019f6b0b..ec82619402 100644 --- a/modules/account_payment_braintree/account.py +++ b/modules/account_payment_braintree/account.py @@ -1170,8 +1170,7 @@ class PaymentBraintreeCustomer( for payment_method in customer.payment_methods: name = self._payment_method_name(payment_method) methods.append((payment_method.token, name)) - self._payment_methods_cache.set(self.id, methods) - return methods + return self._payment_methods_cache.set(self.id, methods) def _payment_method_name(cls, payment_method): name = payment_method.token diff --git a/modules/account_payment_stripe/payment.py b/modules/account_payment_stripe/payment.py index b6d3df0648..6c8124baa3 100644 --- a/modules/account_payment_stripe/payment.py +++ b/modules/account_payment_stripe/payment.py @@ -1712,8 +1712,7 @@ class Customer(CheckoutMixin, DeactivableMixin, ModelSQL, ModelView): else: continue sources.append((source.id, name)) - self._sources_cache.set(self.id, sources) - return sources + return self._sources_cache.set(self.id, sources) @classmethod def _source_name(cls, source): @@ -1779,8 +1778,7 @@ class Customer(CheckoutMixin, DeactivableMixin, ModelSQL, ModelView): for payment_method in payment_methods: name = self._payment_method_name(payment_method) methods.append((payment_method.id, name)) - self._payment_methods_cache.set(self.id, methods) - return methods + return self._payment_methods_cache.set(self.id, methods) @classmethod def _payment_method_name(cls, payment_method): diff --git a/modules/attendance/attendance.py b/modules/attendance/attendance.py index 20977fce15..3313136a83 100644 --- a/modules/attendance/attendance.py +++ b/modules/attendance/attendance.py @@ -210,7 +210,7 @@ class Period(Workflow, ModelSQL, ModelView): result = record.ends_at else: result = None - cls._last_period_cache.set(key, result) + result = cls._last_period_cache.set(key, result) return result @classmethod diff --git a/modules/company/res.py b/modules/company/res.py index 3cb4e90cb0..2d556a8cbb 100644 --- a/modules/company/res.py +++ b/modules/company/res.py @@ -174,9 +174,7 @@ class User(metaclass=PoolMeta): companies = [c.id for c in user.companies] else: companies = [] - companies = tuple(companies) - cls._get_companies_cache.set(user_id, companies) - return companies + return cls._get_companies_cache.set(user_id, companies) @classmethod def get_employees(cls): @@ -196,8 +194,7 @@ class User(metaclass=PoolMeta): else: employees = [] employees = tuple(employees) - cls._get_employees_cache.set(user_id, employees) - return employees + return cls._get_employees_cache.set(user_id, employees) @classmethod def read(cls, ids, fields_names): diff --git a/modules/party/address.py b/modules/party/address.py index d559403ccf..0aec562568 100644 --- a/modules/party/address.py +++ b/modules/party/address.py @@ -625,8 +625,7 @@ ${COUNTRY}""" else: format_ = getattr(cls, f'default_{field}', lambda: '')() - cls._get_format_cache.set(key, format_) - return format_ + return cls._get_format_cache.set(key, format_) class SubdivisionType(DeactivableMixin, ModelSQL, ModelView): @@ -670,8 +669,7 @@ class SubdivisionType(DeactivableMixin, ModelSQL, ModelView): else: types = [] - cls._get_types_cache.set(key, types) - return types + return cls._get_types_cache.set(key, types) @classmethod def on_modification(cls, mode, types, field_names=None): diff --git a/modules/product_price_list_cache/product.py b/modules/product_price_list_cache/product.py index 7ed9ac3425..149cfb921f 100644 --- a/modules/product_price_list_cache/product.py +++ b/modules/product_price_list_cache/product.py @@ -119,7 +119,8 @@ class PriceListCache(ModelSQL): unit_prices = self._unit_prices_cache.get(self.raw_unit_prices) if unit_prices is None: unit_prices = loads(self.raw_unit_prices) - self._unit_prices_cache.set(self.raw_unit_prices, unit_prices) + unit_prices = self._unit_prices_cache.set( + self.raw_unit_prices, unit_prices) return unit_prices @classmethod diff --git a/modules/product_price_list_cache/tests/test_module.py b/modules/product_price_list_cache/tests/test_module.py index c7ed5c816d..683c65ed6c 100644 --- a/modules/product_price_list_cache/tests/test_module.py +++ b/modules/product_price_list_cache/tests/test_module.py @@ -73,7 +73,7 @@ class ProductPriceListCacheTestCase(ModuleTestCase): self.assertEqual(cache.product, product) self.assertEqual(cache.uom, kilogram) self.assertEqual( - cache.unit_prices, [[0, Decimal(10)], [10, Decimal(9)]]) + cache.unit_prices, ((0, Decimal(10)), (10, Decimal(9)))) self.assertEqual(cache.pattern, None) # Test cached compute diff --git a/modules/project/work.py b/modules/project/work.py index 77ffc8734b..d518195fc5 100644 --- a/modules/project/work.py +++ b/modules/project/work.py @@ -64,8 +64,7 @@ class WorkStatus(DeactivableMixin, sequence_ordered(), ModelSQL, ModelView): status = records[0].id else: status = None - cls._get_default_status_cache.set(type, status) - return status + return cls._get_default_status_cache.set(type, status) @classmethod def on_modification(cls, mode, records, field_names=None): @@ -100,8 +99,7 @@ class WorkStatus(DeactivableMixin, sequence_ordered(), ModelSQL, ModelView): if domains: domains.append( (gettext('project.msg_domain_all'), '[]', False)) - cls._get_window_domains_cache.set(key, domains) - return domains + return cls._get_window_domains_cache.set(key, domains) class Work( diff --git a/modules/stock/location.py b/modules/stock/location.py index 2a3e716d04..99cf8a9638 100644 --- a/modules/stock/location.py +++ b/modules/stock/location.py @@ -369,7 +369,7 @@ class Location(DeactivableMixin, tree(), ModelSQL, ModelView): warehouse = warehouses[0].id else: warehouse = None - cls._default_warehouse_cache.set(None, warehouse) + warehouse = cls._default_warehouse_cache.set(None, warehouse) return warehouse @property diff --git a/modules/stock_package_shipping_sendcloud/carrier.py b/modules/stock_package_shipping_sendcloud/carrier.py index db19a5f0a6..2965e035cf 100644 --- a/modules/stock_package_shipping_sendcloud/carrier.py +++ b/modules/stock_package_shipping_sendcloud/carrier.py @@ -94,8 +94,7 @@ class CredentialSendcloud(sequence_ordered(), ModelSQL, ModelView, MatchMixin): auth=self.auth, timeout=timeout, headers=HEADERS) response.raise_for_status() addresses = response.json()['sender_addresses'] - self._addresses_sender_cache.set(self.id, addresses) - return addresses + return self._addresses_sender_cache.set(self.id, addresses) def get_sender_address(self, shipment_or_warehouse, pattern=None): pattern = pattern.copy() if pattern is not None else {} @@ -131,8 +130,7 @@ class CredentialSendcloud(sequence_ordered(), ModelSQL, ModelView, MatchMixin): auth=self.auth, timeout=timeout, headers=HEADERS) response.raise_for_status() methods = response.json()['shipping_methods'] - self._shiping_methods_cache.set(key, methods) - return methods + return self._shiping_methods_cache.set(key, methods) def get_shipping_method(self, shipment, package=None): pattern = self._get_shipping_method_pattern(shipment, package=package) diff --git a/modules/timesheet_cost/company.py b/modules/timesheet_cost/company.py index 1a8e70b02f..afff1f9c72 100644 --- a/modules/timesheet_cost/company.py +++ b/modules/timesheet_cost/company.py @@ -46,7 +46,8 @@ class Employee(metaclass=PoolMeta): for cost_price in cost_prices: employee_costs.append( (cost_price.date, cost_price.cost_price)) - self._cost_prices_cache.set(self.id, employee_costs) + employee_costs = self._cost_prices_cache.set( + self.id, employee_costs) return employee_costs def compute_cost_price(self, date=None): diff --git a/modules/web_shop_shopify/web.py b/modules/web_shop_shopify/web.py index 56a67e8f23..d626f797ff 100644 --- a/modules/web_shop_shopify/web.py +++ b/modules/web_shop_shopify/web.py @@ -1027,7 +1027,8 @@ class Shop_Warehouse(ModelView, metaclass=PoolMeta): QUERY_LOCATIONS, {}, 'locations'): locations.append( (str(gid2id(location['id'])), location['name'])) - self._shopify_locations_cache.set(self.shop.id, locations) + locations = self._shopify_locations_cache.set( + self.shop.id, locations) except GraphQLException: pass return locations diff --git a/trytond/doc/ref/cache.rst b/trytond/doc/ref/cache.rst index 33a1677ab4..9b8e0ad2be 100644 --- a/trytond/doc/ref/cache.rst +++ b/trytond/doc/ref/cache.rst @@ -54,7 +54,7 @@ Cache .. method:: Cache.set(key, value) - Set the ``value`` of the ``key`` in the cache. + Set and return the ``value`` of the ``key`` in the cache. .. method:: Cache.clear() diff --git a/trytond/trytond/cache.py b/trytond/trytond/cache.py index c7e8d6edf6..978a7b6592 100644 --- a/trytond/trytond/cache.py +++ b/trytond/trytond/cache.py @@ -224,7 +224,8 @@ class MemoryCache(BaseCache): else: expire = None try: - cache[key] = (expire, immutable(value)) + value = immutable(value) + cache[key] = (expire, value) except TypeError: pass return value diff --git a/trytond/trytond/ir/action.py b/trytond/trytond/ir/action.py index bee506d998..9135045802 100644 --- a/trytond/trytond/ir/action.py +++ b/trytond/trytond/ir/action.py @@ -336,8 +336,7 @@ class ActionKeyword(ModelSQL, ModelView): value['name'] = ( parent.rec_name + ' / ' + value['name']) keywords.sort(key=itemgetter('name')) - cls._get_keyword_cache.set(key, keywords) - return keywords + return cls._get_keyword_cache.set(key, keywords) class ActionMixin(ModelSQL): @@ -421,7 +420,7 @@ class ActionMixin(ModelSQL): key = (name, action_id) groups = Action._groups_cache.get(key) if groups is not None: - return set(groups) + return groups domain = [ (cls._action_name, '=', name), @@ -430,8 +429,7 @@ class ActionMixin(ModelSQL): domain.append(('id', '=', action_id)) actions = cls.search(domain) groups = {g.id for a in actions for g in a.groups} - Action._groups_cache.set(key, list(groups)) - return groups + return Action._groups_cache.set(key, groups) @classmethod def on_modification(cls, mode, records, field_names=None): @@ -749,7 +747,7 @@ class ActionReport( return template def set_template_cached(self, template): - self._template_cache.set(self.id, template) + return self._template_cache.set(self.id, template) @classmethod def validate_fields(cls, reports, field_names): @@ -1096,7 +1094,7 @@ class ActionWizard( key = (name, action_id) models = cls._get_models_cache.get(key) if models is not None: - return set(models) + return models domain = [ (cls._action_name, '=', name), @@ -1105,8 +1103,7 @@ class ActionWizard( domain.append(('id', '=', action_id)) actions = cls.search(domain) models = {a.model for a in actions if a.model} - cls._get_models_cache.set(key, models) - return models + return cls._get_models_cache.set(key, models) @classmethod def get_name(cls, name, model): @@ -1129,8 +1126,7 @@ class ActionWizard( if actions: action, = actions wiz_name = action.name - cls._get_name_cache.set(key, wiz_name) - return wiz_name + return cls._get_name_cache.set(key, wiz_name) @classmethod def on_modification(cls, mode, keywords, field_names=None): diff --git a/trytond/trytond/ir/configuration.py b/trytond/trytond/ir/configuration.py index a803a1a017..8186ffafbf 100644 --- a/trytond/trytond/ir/configuration.py +++ b/trytond/trytond/ir/configuration.py @@ -25,8 +25,7 @@ class Configuration(ModelSingleton, ModelSQL): language = cls(1).language if not language: language = config.get('database', 'language') - cls._get_language_cache.set(None, language) - return language + return cls._get_language_cache.set(None, language) def check(self): "Check configuration coherence on pool initialisation" diff --git a/trytond/trytond/ir/lang.py b/trytond/trytond/ir/lang.py index 16554f3899..ca27c0d9fe 100644 --- a/trytond/trytond/ir/lang.py +++ b/trytond/trytond/ir/lang.py @@ -387,14 +387,15 @@ class Lang(DeactivableMixin, ModelSQL, ModelView): exception=exception)) from exception def get_translatable_languages(cls): - res = cls._lang_cache.get('translatable_languages') - if res is None: + languages = cls._lang_cache.get('translatable_languages') + if languages is None: langs = cls.search([ ('translatable', '=', True), ]) - res = [x.code for x in langs] - cls._lang_cache.set('translatable_languages', res) - return res + languages = [x.code for x in langs] + languages = cls._lang_cache.set( + 'translatable_languages', languages) + return languages @classmethod def check_modification(cls, mode, languages, values=None, external=False): diff --git a/trytond/trytond/ir/message.py b/trytond/trytond/ir/message.py index d32c6705c1..f1b67caa61 100644 --- a/trytond/trytond/ir/message.py +++ b/trytond/trytond/ir/message.py @@ -37,7 +37,7 @@ class Message(ModelSQL, ModelView): text = translation.value or message.text else: text = message.text - cls._message_cache.set(key, text) + text = cls._message_cache.set(key, text) return text if not variables else text % variables @classmethod @@ -68,7 +68,7 @@ class Message(ModelSQL, ModelView): text = values[index] or fallback else: text = fallback - cls._message_cache.set(key, text) + text = cls._message_cache.set(key, text) return text if not variables else text % variables def _get_translation(self, language, fuzzy=False): diff --git a/trytond/trytond/ir/model.py b/trytond/trytond/ir/model.py index f387f1b898..6403093711 100644 --- a/trytond/trytond/ir/model.py +++ b/trytond/trytond/ir/model.py @@ -185,11 +185,8 @@ class Model( items = ( (m, n) for m, n in items if issubclass(pool_get(m), classes)) - items = list(items) - cls._get_names_cache.set(key, items) - else: - items = list(items) - return items + items = cls._get_names_cache.set(key, list(items)) + return list(items) @classmethod def get_names(cls, classes=None): @@ -198,7 +195,7 @@ class Model( dict_ = cls._get_names_cache.get(key) if dict_ is None: dict_ = dict(cls.get_name_items()) - cls._get_names_cache.set(key, dict_) + dict_ = cls._get_names_cache.set(key, dict_) return dict_ @classmethod @@ -476,7 +473,7 @@ class ModelField( if fields: field, = fields name = field.string - cls._get_name_cache.set((model, field), name) + name = cls._get_name_cache.set((model, field), name) else: name = field return name @@ -1078,8 +1075,7 @@ class ModelButton( else: button, = buttons reset = [b.name for b in button.reset] - cls._reset_cache.set(key, reset) - return reset + return cls._reset_cache.set(key, reset) @classmethod def get_groups(cls, model, name): @@ -1099,8 +1095,7 @@ class ModelButton( else: button, = buttons groups = set(g.id for g in button.groups) - cls._groups_cache.set(key, groups) - return groups + return cls._groups_cache.set(key, groups) @classmethod def get_view_attributes(cls, model, name): @@ -1122,8 +1117,7 @@ class ModelButton( 'help': button.help, 'confirm': button.confirm, } - cls._view_attributes_cache.set(key, attributes) - return attributes + return cls._view_attributes_cache.set(key, attributes) class ModelButtonGroup(DeactivableMixin, ModelSQL): @@ -1374,8 +1368,8 @@ class ModelData( cursor = Transaction().connection.cursor() cursor.execute(*table.select(table.model, group_by=[table.model])) - models = [m for m, in cursor] - cls._has_model_cache.set(None, models) + models = set(m for m, in cursor) + models = cls._has_model_cache.set(None, models) return model in models @classmethod @@ -1446,8 +1440,7 @@ class ModelData( except ValueError: raise KeyError(f"Reference to '{module}.{fs_id}' not found") - cls._get_id_cache.set(key, data.db_id) - return data.db_id + return cls._get_id_cache.set(key, data.db_id) @classmethod def dump_values(cls, values): diff --git a/trytond/trytond/ir/rule.py b/trytond/trytond/ir/rule.py index 5e6ca02cb1..c28945c58c 100644 --- a/trytond/trytond/ir/rule.py +++ b/trytond/trytond/ir/rule.py @@ -326,8 +326,7 @@ class Rule(ModelSQL, ModelView): elif clause_global: clause = clause_global - cls._domain_get_cache.set(key, clause) - return clause + return cls._domain_get_cache.set(key, clause) @classmethod def query_get(cls, model_name, mode='read'): diff --git a/trytond/trytond/ir/translation.py b/trytond/trytond/ir/translation.py index 23bd6ef1e8..49f64eb381 100644 --- a/trytond/trytond/ir/translation.py +++ b/trytond/trytond/ir/translation.py @@ -319,8 +319,7 @@ class Translation( Lang = pool.get('ir.lang') langs = Lang.search([]) result = [(lang.code, lang.name) for lang in langs] - cls._get_language_cache.set(language, result) - return result + return cls._get_language_cache.set(language, result) @classmethod def view_attributes(cls): diff --git a/trytond/trytond/ir/ui/view.py b/trytond/trytond/ir/ui/view.py index 24c2df76dc..6a89383cf1 100644 --- a/trytond/trytond/ir/ui/view.py +++ b/trytond/trytond/ir/ui/view.py @@ -197,7 +197,7 @@ class View( rng_name = os.path.join(os.path.dirname(__file__), type_ + '.rng') with open(rng_name, 'rb') as fp: rng = etree.fromstring(fp.read()) - cls._get_rng_cache.set(key, rng) + rng = cls._get_rng_cache.set(key, rng) return rng @property @@ -333,8 +333,7 @@ class View( 'arch': arch, 'field_childs': self.field_childs, } - self._view_get_cache.set(key, result) - return result + return self._view_get_cache.set(key, result) @classmethod def inherit_apply(cls, tree, inherit): diff --git a/trytond/trytond/model/dictschema.py b/trytond/trytond/model/dictschema.py index b39d916502..1d3e4e64f9 100644 --- a/trytond/trytond/model/dictschema.py +++ b/trytond/trytond/model/dictschema.py @@ -207,8 +207,7 @@ class DictSchemaMixin(object): return fields keys = cls.get_keys(cls.search([])) fields = {k['name']: k for k in keys} - cls._relation_fields_cache.set(cls.__name__, fields) - return fields + return cls._relation_fields_cache.set(cls.__name__, fields) @classmethod def on_modification(cls, mode, records, field_names=None): diff --git a/trytond/trytond/model/digits.py b/trytond/trytond/model/digits.py index 47a29511a3..dd168f2422 100644 --- a/trytond/trytond/model/digits.py +++ b/trytond/trytond/model/digits.py @@ -22,8 +22,7 @@ class DigitsMixin: if digits is not None: return digits digits = self._get_digits() - self._digits_cache.set(key, digits) - return digits + return self._digits_cache.set(key, digits) def _get_digits(self): return (None, self.digits) diff --git a/trytond/trytond/model/modelsql.py b/trytond/trytond/model/modelsql.py index 74db967ded..dab7bfbacf 100644 --- a/trytond/trytond/model/modelsql.py +++ b/trytond/trytond/model/modelsql.py @@ -736,7 +736,7 @@ class ModelSQL(ModelStorage): if count is None: count = transaction.database.estimated_count( transaction.connection, cls.__table__()) - cls._count_cache.set(cls.__name__, count) + count = cls._count_cache.set(cls.__name__, count) return count @classmethod diff --git a/trytond/trytond/model/modelstorage.py b/trytond/trytond/model/modelstorage.py index 06e6106a2a..4f09f86205 100644 --- a/trytond/trytond/model/modelstorage.py +++ b/trytond/trytond/model/modelstorage.py @@ -783,7 +783,7 @@ class ModelStorage(Model): count = cls._count_cache.get(cls.__name__) if count is None: count = cls.search([], count=True) - cls._count_cache.set(cls.__name__, count) + count = cls._count_cache.set(cls.__name__, count) return count def resources(self): diff --git a/trytond/trytond/model/modelview.py b/trytond/trytond/model/modelview.py index ee49fe0673..f9c075e03c 100644 --- a/trytond/trytond/model/modelview.py +++ b/trytond/trytond/model/modelview.py @@ -273,7 +273,6 @@ class ModelView(Model): result = cls._fields_view_get_cache.get(key) if result: return result - result = {'model': cls.__name__} pool = Pool() View = pool.get('ir.ui.view') @@ -297,10 +296,11 @@ class ModelView(Model): # if a view was found if view: - result = view.view_get(model=cls.__name__) + result = dict(view.view_get(model=cls.__name__)) # otherwise, build some kind of default view else: + result = {} if view_type == 'form': res = cls.fields_get() xml = '''''' \ @@ -348,8 +348,7 @@ class ModelView(Model): tree, result['type'], view_id=view_id, field_children=result['field_childs'], level=level) - cls._fields_view_get_cache.set(key, result) - return result + return cls._fields_view_get_cache.set(key, result) @classmethod def view_toolbar_get(cls): @@ -389,8 +388,7 @@ class ModelView(Model): 'exports': exports, 'emails': emails, } - cls._view_toolbar_get_cache.set(key, result) - return result + return cls._view_toolbar_get_cache.set(key, result) @classmethod def view_attributes(cls): diff --git a/trytond/trytond/report/report.py b/trytond/trytond/report/report.py index 136aa76ae0..ca7cd3ae4b 100644 --- a/trytond/trytond/report/report.py +++ b/trytond/trytond/report/report.py @@ -392,7 +392,7 @@ class Report(URLMixin, PoolBase): loader = relatorio.reporting.MIMETemplateLoader() klass = loader.factories[loader.get_type(mimetype)] template = klass(BytesIO(report.report_content)) - report.set_template_cached(template) + template = report.set_template_cached(template) translate = cls._callback_loader(report, template) if translate: report_context = report_context.copy() diff --git a/trytond/trytond/res/user.py b/trytond/trytond/res/user.py index e3d6fbf0e1..32bebaf3ca 100644 --- a/trytond/trytond/res/user.py +++ b/trytond/trytond/res/user.py @@ -634,8 +634,7 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView): & (group.active == Literal(True))), order_by=[user_group.group.asc])) groups = tuple(g for g, in cursor) - cls._get_groups_cache.set(user, groups) - return groups + return cls._get_groups_cache.set(user, groups) @classmethod def _get_login(cls, login):