Belle II Software  release-08-01-10
tolerate_missing_key_formatter.py
1 
8 
9 import string
10 import unittest
11 
12 
13 class NoReplacementField(object):
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_namefield_name = field_name
41 
42  self.conversionconversion = conversion
43 
44  self.format_specformat_spec = format_spec
45 
46  def __getattr__(self, attr):
47  """Record attribute lookup"""
48  return NoReplacementField(self.field_namefield_name + "." + attr)
49 
50  def __getitem__(self, attr):
51  """Record item lookup"""
52  return NoReplacementField(self.field_namefield_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_namefield_name
57 
58  if self.conversionconversion is not None:
59  text_in_brackets += "!" + self.conversionconversion
60 
61  if self.format_specformat_spec is not None:
62  text_in_brackets += ":" + self.format_specformat_spec
63 
64  replacement_field = "{" + text_in_brackets + "}"
65  return replacement_field
66 
67 
68 class 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 postional 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(TolerateMissingKeyFormatter, self).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(TolerateMissingKeyFormatter, self).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(TolerateMissingKeyFormatter, self).format_field(value, format_spec)
126 
127 
128 class 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 
136  def test_missing_key(self):
137  """Test for a missing key"""
138  template = "{present}_{missing}"
139  replaced = self.formatterformatter.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.formatterformatter.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.formatterformatter.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.formatterformatter.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.formatterformatter.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.formatterformatter.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.formatterformatter.format(template, present="replaced")
176  self.assertEqual("replaced_{missing:^10s}", replaced)
177 
178 
179 if __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.