changeset c0f1b12134d8 in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset&node=c0f1b12134d8
description:
Use immutable dictionary for Transaction context
issue10691
review358741002
diffstat:
CHANGELOG | 1 +
doc/ref/tools/immutabledict.rst | 10 ++++++
doc/ref/tools/index.rst | 1 +
trytond/model/fields/dict.py | 20 +------------
trytond/tests/test_protocols.py | 2 +-
trytond/tests/test_tools.py | 62 +++++++++++++++++++++++++++++++++++++++++
trytond/tools/immutabledict.py | 20 +++++++++++++
trytond/transaction.py | 19 +++++++-----
8 files changed, 107 insertions(+), 28 deletions(-)
diffs (247 lines):
diff -r e5f04f5be526 -r c0f1b12134d8 CHANGELOG
--- a/CHANGELOG Sun Sep 19 00:08:37 2021 +0200
+++ b/CHANGELOG Sun Sep 19 00:30:35 2021 +0200
@@ -1,3 +1,4 @@
+* Use ImmutableDict for Transaction context
* Use UNION for 'Or'-ed domain with subqueries
* Add remove_forbidden_chars in tools
* Manage errors during non-interactive operations
diff -r e5f04f5be526 -r c0f1b12134d8 doc/ref/tools/immutabledict.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/ref/tools/immutabledict.rst Sun Sep 19 00:30:35 2021 +0200
@@ -0,0 +1,10 @@
+.. _ref-immutabledict:
+.. module:: trytond.tools.immutabledict
+
+=============
+ImmutableDict
+=============
+
+.. class:: ImmutableDict
+
+A class that implements an immutable dictionary.
diff -r e5f04f5be526 -r c0f1b12134d8 doc/ref/tools/index.rst
--- a/doc/ref/tools/index.rst Sun Sep 19 00:08:37 2021 +0200
+++ b/doc/ref/tools/index.rst Sun Sep 19 00:30:35 2021 +0200
@@ -11,3 +11,4 @@
misc
singleton
+ immutabledict
diff -r e5f04f5be526 -r c0f1b12134d8 trytond/model/fields/dict.py
--- a/trytond/model/fields/dict.py Sun Sep 19 00:08:37 2021 +0200
+++ b/trytond/model/fields/dict.py Sun Sep 19 00:30:35 2021 +0200
@@ -9,6 +9,7 @@
from trytond.pool import Pool
from trytond.protocols.jsonrpc import JSONDecoder, JSONEncoder
from trytond.tools import grouped_slice
+from trytond.tools.immutabledict import ImmutableDict
from trytond.transaction import Transaction
from .field import Field, SQL_OPERATORS
@@ -17,25 +18,6 @@
json.dumps, cls=JSONEncoder, separators=(',', ':'), sort_keys=True)
-class ImmutableDict(dict):
-
- __slots__ = ()
-
- def _not_allowed(cls, *args, **kwargs):
- raise TypeError("Operation not allowed on ImmutableDict")
-
- __setitem__ = _not_allowed
- __delitem__ = _not_allowed
- __ior__ = _not_allowed
- clear = _not_allowed
- pop = _not_allowed
- popitem = _not_allowed
- setdefault = _not_allowed
- update = _not_allowed
-
- del _not_allowed
-
-
class Dict(Field):
'Define dict field.'
_type = 'dict'
diff -r e5f04f5be526 -r c0f1b12134d8 trytond/tests/test_protocols.py
--- a/trytond/tests/test_protocols.py Sun Sep 19 00:08:37 2021 +0200
+++ b/trytond/tests/test_protocols.py Sun Sep 19 00:30:35 2021 +0200
@@ -6,7 +6,7 @@
import datetime
from decimal import Decimal
-from trytond.model.fields.dict import ImmutableDict
+from trytond.tools.immutabledict import ImmutableDict
from trytond.protocols.jsonrpc import JSONEncoder, JSONDecoder, JSONRequest
from trytond.protocols.xmlrpc import client, XMLRequest
diff -r e5f04f5be526 -r c0f1b12134d8 trytond/tests/test_tools.py
--- a/trytond/tests/test_tools.py Sun Sep 19 00:08:37 2021 +0200
+++ b/trytond/tests/test_tools.py Sun Sep 19 00:30:35 2021 +0200
@@ -20,6 +20,7 @@
domain_inversion, parse, simplify, merge, concat, unique_value,
eval_domain, localize_domain,
prepare_reference_domain, extract_reference_models)
+from trytond.tools.immutabledict import ImmutableDict
class ToolsTestCase(unittest.TestCase):
@@ -311,6 +312,66 @@
self.assertEqual(s, 'barfoo')
+class ImmutableDictTestCase(unittest.TestCase):
+ "Test ImmutableDict"
+
+ def test_setitem(self):
+ "__setitem__ not allowed"
+ d = ImmutableDict()
+
+ with self.assertRaises(TypeError):
+ d['foo'] = 'bar'
+
+ def test_delitem(self):
+ "__delitem__ not allowed"
+ d = ImmutableDict(foo='bar')
+
+ with self.assertRaises(TypeError):
+ del d['foo']
+
+ def test_ior(self):
+ "__ior__ not allowed"
+ d = ImmutableDict()
+
+ with self.assertRaises(TypeError):
+ d |= {'foo': 'bar'}
+
+ def test_clear(self):
+ "clear not allowed"
+ d = ImmutableDict(foo='bar')
+
+ with self.assertRaises(TypeError):
+ d.clear()
+
+ def test_pop(self):
+ "pop not allowed"
+ d = ImmutableDict(foo='bar')
+
+ with self.assertRaises(TypeError):
+ d.pop('foo')
+
+ def test_popitem(self):
+ "popitem not allowed"
+ d = ImmutableDict(foo='bar')
+
+ with self.assertRaises(TypeError):
+ d.popitem('foo')
+
+ def test_setdefault(self):
+ "setdefault not allowed"
+ d = ImmutableDict()
+
+ with self.assertRaises(TypeError):
+ d.setdefault('foo', 'bar')
+
+ def test_update(self):
+ "update not allowed"
+ d = ImmutableDict()
+
+ with self.assertRaises(TypeError):
+ d.update({'foo': 'bar'})
+
+
class DomainInversionTestCase(unittest.TestCase):
"Test domain_inversion"
@@ -866,6 +927,7 @@
suite = unittest.TestSuite()
for testcase in [ToolsTestCase,
StringPartitionedTestCase,
+ ImmutableDictTestCase,
DomainInversionTestCase]:
suite.addTests(func(testcase))
suite.addTest(doctest.DocTestSuite(decimal_))
diff -r e5f04f5be526 -r c0f1b12134d8 trytond/tools/immutabledict.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/trytond/tools/immutabledict.py Sun Sep 19 00:30:35 2021 +0200
@@ -0,0 +1,20 @@
+# This file is part of Tryton. The COPYRIGHT file at the toplevel of this
+# repository contains the full copyright notices and license terms.
+
+class ImmutableDict(dict):
+
+ __slots__ = ()
+
+ def _not_allowed(cls, *args, **kwargs):
+ raise TypeError("Operation not allowed on ImmutableDict")
+
+ __setitem__ = _not_allowed
+ __delitem__ = _not_allowed
+ __ior__ = _not_allowed
+ clear = _not_allowed
+ pop = _not_allowed
+ popitem = _not_allowed
+ setdefault = _not_allowed
+ update = _not_allowed
+
+ del _not_allowed
diff -r e5f04f5be526 -r c0f1b12134d8 trytond/transaction.py
--- a/trytond/transaction.py Sun Sep 19 00:08:37 2021 +0200
+++ b/trytond/transaction.py Sun Sep 19 00:30:35 2021 +0200
@@ -7,6 +7,7 @@
from sql import Flavor
from trytond.config import config
+from trytond.tools.immutabledict import ImmutableDict
_cache_model = config.getint('cache', 'model')
logger = logging.getLogger(__name__)
@@ -111,7 +112,7 @@
self.database = database
self.readonly = readonly
self.close = close
- self.context = context or {}
+ self.context = ImmutableDict(context or {})
self.create_records = defaultdict(set)
self.delete_records = defaultdict(set)
self.trigger_records = defaultdict(set)
@@ -174,15 +175,16 @@
if context is None:
context = {}
manager = _AttributeManager(context=self.context)
- self.context = self.context.copy()
- self.context.update(context)
+ ctx = self.context.copy()
+ ctx.update(context)
if kwargs:
- self.context.update(kwargs)
+ ctx.update(kwargs)
+ self.context = ImmutableDict(ctx)
return manager
def reset_context(self):
manager = _AttributeManager(context=self.context)
- self.context = {}
+ self.context = ImmutableDict()
return manager
def set_user(self, user, set_context=False):
@@ -190,12 +192,13 @@
raise ValueError('set_context only allowed for root')
manager = _AttributeManager(user=self.user,
context=self.context)
- self.context = self.context.copy()
+ ctx = self.context.copy()
if set_context:
if user != self.user:
- self.context['user'] = self.user
+ ctx['user'] = self.user
else:
- self.context.pop('user', None)
+ ctx.pop('user', None)
+ self.context = ImmutableDict(ctx)
self.user = user
return manager