diff --git a/trytond/trytond/tests/test_tools.py b/trytond/trytond/tests/test_tools.py index 6f33ce74ada..03f1ff410f4 100644 --- a/trytond/trytond/tests/test_tools.py +++ b/trytond/trytond/tests/test_tools.py @@ -6,6 +6,7 @@ import doctest import sys import unittest +from unittest.mock import Mock from io import BytesIO import sql @@ -469,6 +470,25 @@ def test_get_server_zoneinfo(self): now = dt.datetime(2022, 5, 17, tzinfo=zi) self.assertEqual(str(now), "2022-05-17 00:00:00+00:00") + def test_lazy_evaluation(self): + "Test that StringPartitioned doesn't evaluate its argument on __init__" + getter = Mock() + getter.side_effect = lambda s: s + ls = LazyString(getter, 'foo') + + s = StringPartitioned(ls) + getter.assert_not_called() + + str(s) + getter.assert_called_once() + + getter.reset_mock() + s = StringPartitioned(s) + getter.assert_not_called() + + str(s) + getter.assert_called_once() + class ImmutableDictTestCase(unittest.TestCase): "Test ImmutableDict" diff --git a/trytond/trytond/tools/string_.py b/trytond/trytond/tools/string_.py index 3fe34edab73..ce53938516f 100644 --- a/trytond/trytond/tools/string_.py +++ b/trytond/trytond/tools/string_.py @@ -86,6 +86,11 @@ class StringPartitioned(str): "A string subclass that stores parts that composes itself." __slots__ = ('_parts',) + def __new__(cls, base): + if isinstance(base, (LazyString, StringPartitioned)): + return super().__new__(cls) + return super().__new__(cls, base) + def __init__(self, base): super().__init__() if isinstance(base, StringPartitioned): @@ -106,6 +111,15 @@ def __radd__(self, other): new._parts = (other,) + self._parts return new + def __eq__(self, other): + return str(self) == str(other) + + def __bool__(self): + return bool(self._parts) + + def __str__(self): + return ''.join(str(p) for p in self._parts) + class LazyString(): def __init__(self, func, *args, **kwargs):