Skip to content

Commit 13f6f05

Browse files
data_store: optimise rtconfig serialisation
1 parent 816699a commit 13f6f05

4 files changed

Lines changed: 72 additions & 16 deletions

File tree

cylc/flow/data_store_mgr.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
from cylc.flow.network import API
108108
from cylc.flow.parsec.util import (
109109
listjoin,
110+
fast_listjoin as listjoin,
110111
pdeepcopy,
111112
poverride,
112113
)

cylc/flow/parsec/util.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,31 @@ def listjoin(lst, none_str=''):
113113
for item in lst:
114114
if item is None:
115115
items.append(none_str)
116-
elif any(char in str(item) for char in ',#"\''):
117-
items.append(repr(item)) # will be quoted
118116
else:
119-
items.append(str(item))
117+
string = str(item)
118+
if any(char in string for char in ',#"\''):
119+
items.append(repr(item)) # will be quoted
120+
else:
121+
items.append(string)
120122
return ', '.join(items)
121123

122124

125+
def fast_listjoin(lst, none_str=''):
126+
"""Faster variant of listjoin suitable in select cases.
127+
128+
Compared to listjoin, this does *not*:
129+
* Rationalise and pretty-format integer lists.
130+
* Intelligently quote arguments which need quoting.
131+
132+
Suitable for use in situations where you know the data type of the field
133+
being joined and the above is of no concern.
134+
"""
135+
return ', '.join((
136+
none_str if item is None else str(item)
137+
for item in lst or [None]
138+
))
139+
140+
123141
def printcfg(cfg, level=0, indent=0, prefix='', none_str='',
124142
handle=None):
125143
"""Pretty-print a parsec config item or section (nested dict).

cylc/flow/parsec/validate.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
Also provides default values from the spec as a nested dict.
2323
"""
2424

25+
from functools import lru_cache
2526
import re
2627
import shlex
2728
from collections import deque
@@ -655,12 +656,18 @@ def parsec_validate(cfg_root, spec_root):
655656
return ParsecValidator().validate(cfg_root, spec_root)
656657

657658

658-
class DurationFloat(float):
659-
"""Duration in floating point seconds, but stringify as ISO8601 format."""
660-
661-
def __str__(self):
659+
class _DurationFloat(float):
660+
def _str(self):
662661
return str(Duration(seconds=self, standardize=True))
663662

663+
# NOTE: This object is immutable so we cache the value of __str__.
664+
__str__ = lru_cache(1)(_str)
665+
666+
667+
# NOTE: Prevent duplicate DurationFloat objects being created for the same
668+
# duration value. This allows __str__ caching to be effective.
669+
DurationFloat = lru_cache(None)(_DurationFloat.__call__)
670+
664671

665672
class Range(tuple):
666673

tests/unit/parsec/test_util.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from cylc.flow.parsec.util import (
2323
SECTION_EXPAND_PATTERN,
2424
expand_many_section,
25+
fast_listjoin,
2526
itemstr,
2627
listjoin,
2728
m_override,
@@ -31,15 +32,44 @@
3132
replicate,
3233
un_many,
3334
)
34-
35-
36-
def test_listjoin():
37-
assert listjoin(None) == ''
38-
assert listjoin(None, 'test') == 'test'
39-
assert listjoin([], 'test') == 'test'
40-
assert listjoin([None], 'test') == 'test'
41-
assert listjoin(['test', 'test']) == 'test, test'
42-
assert listjoin(['test,', 'test']) == '\'test,\', test'
35+
from cylc.flow.parsec.validate import DurationFloat
36+
37+
38+
@pytest.mark.parametrize('method', (listjoin, fast_listjoin))
39+
def test_listjoin(method):
40+
assert method(None) == ''
41+
assert method(None, 'test') == 'test'
42+
assert method([]) == ''
43+
assert method([], 'test') == 'test'
44+
assert method([None], 'test') == 'test'
45+
assert method(['test', 'test']) == 'test, test'
46+
if method == listjoin:
47+
# intelligent quoting
48+
assert method(['a#', 'b,', "c'"]) == "'a#', 'b,', \"c'\""
49+
assert method(['test,', 'test']) == '\'test,\', test'
50+
51+
# integer list rationalisation
52+
assert method([10, 11, 12]) == '10..12'
53+
assert method([10, 10, 10]) == '3*10'
54+
assert method([10, 10, 10, 42]) == '3*10, 42'
55+
56+
57+
def test_duration_float():
58+
"""It should prevent the creation of duplicate instances."""
59+
# these calls should only create two discrete instances of DurationFloat
60+
a = DurationFloat(1.0)
61+
b = DurationFloat(1.0)
62+
c = DurationFloat(42.0)
63+
64+
# a and b should be no only equal but also the same instance
65+
assert a == b
66+
assert id(a) == id(b)
67+
assert str(a) == 'PT1S'
68+
69+
# a and c should be unequal and different instances
70+
assert a != c
71+
assert id(a) != id(c)
72+
assert str(c) == 'PT42S'
4373

4474

4575
# --- printcfg

0 commit comments

Comments
 (0)