@@ -67,7 +67,8 @@ def _fixturefile_loadrelative(relative_path, fixture_format=None):
6767 elif fixture_format == 'json' :
6868 with open (tmp_path ) as jsonfile :
6969 data = json .load (jsonfile , object_hook = _FixtureHint .object_hook )
70- _hints_apply_if_present (tmp_path , data )
70+ _hints_apply_from_instances_if_present (data )
71+ _hints_apply_from_fixture_ini_if_present (tmp_path , data )
7172 else :
7273 raise AssertionError (
7374 "unsupported fixture format: %s" % (fixture_format ,))
@@ -83,14 +84,34 @@ def _fixturefile_normname(relative_path, prefix=''):
8384 return normname
8485
8586
87+ # The following chunk of code is all related to "hints": small transformations
88+ # that can be requested to data as it read (and in some cases written) in the
89+ # course of a test run.
90+ #
91+ # The observation here is that the on-disk format of various structures may not
92+ # always be suitable for either as an actual or expected value in a comparison
93+ # or as a human-centric fixture format. But, we explicitly wish to consume the
94+ # value as written by the production code.
95+ #
96+ # Thus, we provide a series of small named transformations which can be
97+ # explicitly requested at a few strategic points (e.g. loading an on-disk file)
98+ # that allows assertions in tests to succinctly make assertions as opposed to
99+ # the intent of the check becoming drowned in the details of conversions etc.
100+ #
101+ # <hints>
102+
86103def _hints_apply_array_of_tuples (value , modifier ):
87- """Generate values for array_of_tuples hint."""
104+ """
105+ Convert list of lists such that its values are instead tuples.
106+ """
88107 assert modifier is None
89108 return [tuple (x ) for x in value ]
90109
91110
92111def _hints_apply_today_relative (value , modifier ):
93- """Generate values for today_relative hint."""
112+ """
113+ Geneate a time value by applying a declared delta to today's date.
114+ """
94115
95116 kind , delta = modifier .split ('|' )
96117 if kind == "days" :
@@ -101,7 +122,42 @@ def _hints_apply_today_relative(value, modifier):
101122 raise NotImplementedError ("unspported today_relative modifier" )
102123
103124
104- def _hints_apply_dict_bytes_to_strings_kv (input_dict ):
125+ def _hints_apply_dict_bytes_to_strings_kv (input_dict , modifier ):
126+ """
127+ Convert a dictionary whose keys/values are bytes to one whose
128+ keys/values are strings.
129+ """
130+
131+ assert modifier is None
132+
133+ output_dict = {}
134+
135+ for k , v in input_dict .items ():
136+ key_to_use = k
137+ if isinstance (k , bytes ):
138+ key_to_use = str (k , 'utf8' )
139+
140+ if isinstance (v , dict ):
141+ output_dict [key_to_use ] = _hints_apply_dict_bytes_to_strings_kv (v , modifier )
142+ continue
143+
144+ val_to_use = v
145+ if isinstance (v , bytes ):
146+ val_to_use = str (v , 'utf8' )
147+
148+ output_dict [key_to_use ] = val_to_use
149+
150+ return output_dict
151+
152+
153+ def _hints_apply_dict_strings_to_bytes_kv (input_dict , modifier ):
154+ """
155+ Convert a dictionary whose keys/values are strings to one whose
156+ keys/values are bytes.
157+ """
158+
159+ assert modifier is None
160+
105161 output_dict = {}
106162
107163 for k , v in input_dict .items ():
@@ -110,7 +166,7 @@ def _hints_apply_dict_bytes_to_strings_kv(input_dict):
110166 key_to_use = bytes (k , 'utf8' )
111167
112168 if isinstance (v , dict ):
113- output_dict [key_to_use ] = _hints_apply_dict_bytes_to_strings_kv ( v )
169+ output_dict [key_to_use ] = _hints_apply_dict_strings_to_bytes_kv ( v , modifier )
114170 continue
115171
116172 val_to_use = v
@@ -122,26 +178,28 @@ def _hints_apply_dict_bytes_to_strings_kv(input_dict):
122178 return output_dict
123179
124180
125- _FIXTUREFILE_APPLIERS_ATTRIBUTES = {
181+ # hints that can be aplied without an additional modifier argument
182+ _HINTS_APPLIERS_ARGLESS = {
126183 'array_of_tuples' : _hints_apply_array_of_tuples ,
127184 'today_relative' : _hints_apply_today_relative ,
185+ 'convert_dict_bytes_to_strings_kv' : _hints_apply_dict_bytes_to_strings_kv ,
186+ 'convert_dict_strings_to_bytes_kv' : _hints_apply_dict_strings_to_bytes_kv ,
128187}
129188
189+ # hints applicable to the conversion of attributes during fixture loading
190+ _FIXTUREFILE_APPLIERS_ATTRIBUTES = {
191+ 'array_of_tuples' : _hints_apply_array_of_tuples ,
192+ 'today_relative' : _hints_apply_today_relative ,
193+ }
130194
195+ # hints applied when writing the contents of a fixture as a temporary file
131196_FIXTUREFILE_APPLIERS_ONWRITE = {
132- 'convert_dict_bytes_to_strings_kv ' : _hints_apply_dict_bytes_to_strings_kv ,
197+ 'convert_dict_strings_to_bytes_kv ' : _hints_apply_dict_strings_to_bytes_kv ,
133198}
134199
135200
136- def _hints_apply_if_present (fixture_path , json_object ):
137- """Apply hints to the supplied data in-place if relevant."""
138-
139- _hints_apply_from_instances_if_present (json_object )
140- _hints_apply_from_ini_if_present (fixture_path , json_object )
141-
142-
143201def _hints_apply_from_instances_if_present (json_object ):
144- """Recursively aply hints to any hint instances in the supplied data."""
202+ """Recursively apply hints to any hint instances in the supplied data."""
145203
146204 for k , v in json_object .items ():
147205 if isinstance (v , dict ):
@@ -153,7 +211,7 @@ def _hints_apply_from_instances_if_present(json_object):
153211 pass
154212
155213
156- def _hints_for_fixture (fixture_path ):
214+ def _load_hints_ini_for_fixture_if_present (fixture_path ):
157215 """Load any hints that may be specified for a given fixture."""
158216
159217 hints = ConfigParser ()
@@ -174,10 +232,13 @@ def _hints_for_fixture(fixture_path):
174232 return hints
175233
176234
177- def _hints_apply_from_ini_if_present (fixture_path , json_object ):
178- """Amend the supplied object in place with any applicable hints."""
235+ def _hints_apply_from_fixture_ini_if_present (fixture_path , json_object ):
236+ """
237+ Amend the supplied object loaded from a fixture in place as specified
238+ by an optional ini file corresponding to the fixture itself.
239+ """
179240
180- hints = _hints_for_fixture (fixture_path )
241+ hints = _load_hints_ini_for_fixture_if_present (fixture_path )
181242
182243 # apply any attriutes hints ahead of specified conversions such that any
183244 # key can be specified matching what is visible within the loaded fixture
@@ -198,7 +259,7 @@ def _hints_apply_from_ini_if_present(fixture_path, json_object):
198259
199260
200261class _FixtureHint :
201- """Named type allowing idenfication of fixture hints."""
262+ """Named type allowing identification of fixture hints."""
202263
203264 def __init__ (self , hint = None , modifier = None , value = None ):
204265 self .hint = hint
@@ -225,6 +286,8 @@ def object_hook(decoded_object):
225286
226287 return decoded_object
227288
289+ # </hints>
290+
228291
229292def fixturepath (relative_path ):
230293 """Get absolute fixture path for relative_path"""
@@ -290,7 +353,7 @@ def write_to_dir(self, target_dir, output_format=None):
290353 output_data = self .fixture_data
291354
292355 # now apply any onwrite conversions
293- hints = _hints_for_fixture (self .fixture_path )
356+ hints = _load_hints_ini_for_fixture_if_present (self .fixture_path )
294357 for item_name in hints ['ONWRITE' ]:
295358 if item_name not in _FIXTUREFILE_APPLIERS_ONWRITE :
296359 raise AssertionError (
@@ -300,8 +363,8 @@ def write_to_dir(self, target_dir, output_format=None):
300363 if not enabled :
301364 continue
302365
303- apply_conversion = _FIXTUREFILE_APPLIERS_ONWRITE [item_name ]
304- output_data = apply_conversion (output_data )
366+ hint_fn = _FIXTUREFILE_APPLIERS_ONWRITE [item_name ]
367+ output_data = hint_fn (output_data , None )
305368
306369 if output_format == 'binary' :
307370 with open (fixture_file_target , 'wb' ) as fixture_outputfile :
0 commit comments