Belle II Software development
tolerate_missing_key_formatter.py
1
8
9import string
10import unittest
11
12
14
15 """This class serves as a placeholder for keys in not found during lookup into
16 keyword or positional arguments during a call to TolerateMissingKeyFormatter.
17
18 It records potential item and attribute lookups, conversion and format_spec to
19 reproduce original replacement_field entry into the formatted string.
20
21 Parameters
22 ----------
23 field_name : str
24 field_name part of the replacement_field specifier
25 conversion : str
26 conversion part of the replacement_field specifier
27 format_spec : str%
28 format_spec part of the replacement_field specifier
29
30 Notes
31 -----
32 replacement_field, field_name, conversion and format_spec have the meaning as outlined in
33 https://docs.python.org/2/library/string.html#format-string-syntax
34 """
35
36 def __init__(self, field_name, conversion=None, format_spec=None):
37 """Constructor"""
38
39
40 self.field_name = field_name
41
42 self.conversion = conversion
43
44 self.format_spec = format_spec
45
46 def __getattr__(self, attr):
47 """Record attribute lookup"""
48 return NoReplacementField(self.field_name + "." + attr)
49
50 def __getitem__(self, attr):
51 """Record item lookup"""
52 return NoReplacementField(self.field_name + "[" + str(attr) + "]")
53
54 def compose(self):
55 """Compose a replacement_field string equivalent to the original replacement_field in the string formatting."""
56 text_in_brackets = self.field_name
57
58 if self.conversion is not None:
59 text_in_brackets += "!" + self.conversion
60
61 if self.format_spec is not None:
62 text_in_brackets += ":" + self.format_spec
63
64 replacement_field = "{" + text_in_brackets + "}"
65 return replacement_field
66
67
68class TolerateMissingKeyFormatter(string.Formatter):
69
70 """A string formatter that implements format most equivalent to the str.format, but does not replace keys,
71 that are not present in the replacements dictionary
72
73 Example
74 -------
75 >>> formatter = TolerateMissingKeyFormatter()
76 >>> template = "{present}_{missing}"
77 >>> partially_substituted = formatter.format(template, present="replaced")
78 >>> print partially_substituted
79 "replaced_{missing}"
80
81 """
82
83 def get_value(self, key, args, kwds):
84 """Retrieves the value that corresponds to the key from either the positional or
85 the keyword arguments given to format
86
87 Overrides the standard lookup such that missing keys in the keyword arguments or
88 transformed in a NoReplacementField signal object.
89 """
90
91 if isinstance(key, str):
92 try:
93 return kwds[key]
94 except KeyError:
95 return NoReplacementField(key)
96 else:
97 return super().get_value(key, args, kwds)
98
99 def convert_field(self, value, conversion):
100 """Applies the conversion to the value.
101
102 Overrides the standard method such that a potential conversion is attached to the NoReplacementField
103 """
104
105 if isinstance(value, NoReplacementField):
106 conversion = conversion or None
107 value.conversion = conversion
108 return value
109 else:
110 return super().convert_field(value, conversion)
111
112 def format_field(self, value, format_spec):
113 """Applies the conversion to the value.
114
115 Overrides the standard method such that a potential format_spec is attached to the NoReplacementField.
116 Than composes the replacement_field specification to be inserted in the formatted string.
117 The outcome should be equivalent to the unformatted string for missing keys.
118 """
119
120 if isinstance(value, NoReplacementField):
121 format_spec = format_spec or None
122 value.format_spec = format_spec
123 return value.compose()
124 else:
125 return super().format_field(value, format_spec)
126
127
128class TolerateMissingKeyFormatterTest(unittest.TestCase):
129 """Test the string formatter for cases where the keys are missing"""
130
131 def setUp(self):
132 """Prepare for the string-formatter test"""
133
135
137 """Test for a missing key"""
138 template = "{present}_{missing}"
139 replaced = self.formatter.format(template, present="replaced")
140 self.assertEqual("replaced_{missing}", replaced)
141
143 """Test for a missing key attribute"""
144 template = "{present}_{missing.field}"
145 replaced = self.formatter.format(template, present="replaced")
146 self.assertEqual("replaced_{missing.field}", replaced)
147
149 """Test for a missing key item"""
150 template = "{present}_{missing[0]}"
151 replaced = self.formatter.format(template, present="replaced")
152 self.assertEqual("replaced_{missing[0]}", replaced)
153
155 """Test for a missing key item"""
156 template = "{present}_{missing!s}"
157 replaced = self.formatter.format(template, present="replaced")
158 self.assertEqual("replaced_{missing!s}", replaced)
159
161 """Test for a missing key item"""
162 template = "{present}_{missing!r}"
163 replaced = self.formatter.format(template, present="replaced")
164 self.assertEqual("replaced_{missing!r}", replaced)
165
167 """Test for a missing key item"""
168 template = "{present}_{missing!r:^10s}"
169 replaced = self.formatter.format(template, present="replaced")
170 self.assertEqual("replaced_{missing!r:^10s}", replaced)
171
173 """Test for a missing key item"""
174 template = "{present}_{missing:^10s}"
175 replaced = self.formatter.format(template, present="replaced")
176 self.assertEqual("replaced_{missing:^10s}", replaced)
177
178
179if __name__ == '__main__':
180 unittest.main()
def __init__(self, field_name, conversion=None, format_spec=None)
formatter
Use the custom string formatter that tolerates missing values for keywords.