1 # Copyright (C) 2010-2012 W. Trevor King <wking@tremily.us>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with Hooke. If not, see <http://www.gnu.org/licenses/>.
18 """Add representers to YAML to support Hooke.
20 Without introspection, YAML cannot decide how to save some
21 objects. By refusing to save these objects, we obviously loose
22 that information, so make sure the things you drop are either
23 stored somewhere else or not important.
26 >>> a = numpy.array([1,2,3])
27 >>> print(yaml.dump(a))
32 The default behavior is to crash.
34 >>> yaml.Dumper.yaml_representers.pop(numpy.ndarray) # doctest: +ELLIPSIS
35 <function none_representer at 0x...>
36 >>> print(yaml.dump(a)) # doctest: +REPORT_UDIFF
37 !!python/object/apply:numpy.core.multiarray._reconstruct
39 - !!python/name:numpy.ndarray ''
45 - !!python/object/apply:numpy.dtype
47 state: !!python/tuple [3, <, null, null, null, -1, -1, 0]
49 - "\\x01\\0\\0\\0\\x02\\0\\0\\0\\x03\\0\\0\\0"
52 Hmm, at one point that crashed like this::
54 Traceback (most recent call last):
56 if data in [None, ()]:
57 TypeError: data type not understood
59 Must be because of the other representers I've loaded since.
61 Restore the representer for future tests.
63 >>> yaml.add_representer(numpy.ndarray, none_representer)
65 We also avoid !!python/unicode tags by sacrificing the string/unicode
68 >>> yaml.dump('ascii', allow_unicode=True)
70 >>> yaml.dump(u'ascii', allow_unicode=True)
72 >>> a = yaml.dump(u'Fran\\xe7ois', allow_unicode=True)
74 'Fran\\xc3\\xa7ois\\n...\\n'
75 >>> unicode(a, 'utf-8')
76 u'Fran\\xe7ois\\n...\\n'
79 from __future__ import absolute_import
86 import yaml.constructor
87 from yaml.constructor import ConstructorError
88 import yaml.representer
90 from ..curve import Data, Curve
91 from ..playlist import FilePlaylist
94 DATA_INFO_TAG = u'!hooke.curve.DataInfo'
97 if False: # YAML dump debugging code
98 """To help isolate data types etc. that give YAML problems.
100 This is usually caused by external C modules (e.g. numpy) that
101 define new types (e.g. numpy.ndarray) which YAML cannot inspect.
103 def ignore_aliases(data):
104 print(' '.join(str(x) for x in [
105 data, repr(data), type(data), repr(type(data))]))
107 if data in [None, ()]:
109 if isinstance(data, (str, unicode, bool, int, float)):
111 yaml.representer.SafeRepresenter.ignore_aliases = staticmethod(
115 # numpy.dtype(numpy.int32) in [None, ()]
117 # http://projects.scipy.org/numpy/ticket/1001
118 def ignore_aliases(data):
120 if data in [None, ()]:
122 if isinstance(data, (str, unicode, bool, int, float)):
126 yaml.representer.SafeRepresenter.ignore_aliases = staticmethod(
129 def unicode_representer(dumper, data):
130 return dumper.represent_scalar(u'tag:yaml.org,2002:str', data)
131 yaml.add_representer(unicode, unicode_representer)
133 def none_representer(dumper, data):
134 return dumper.represent_none(None)
135 yaml.add_representer(numpy.ndarray, none_representer)
137 def bool_representer(dumper, data):
138 return dumper.represent_bool(data)
139 yaml.add_representer(numpy.bool_, bool_representer)
141 def int_representer(dumper, data):
142 return dumper.represent_int(data)
143 yaml.add_representer(numpy.int32, int_representer)
144 yaml.add_representer(numpy.dtype(numpy.int32), int_representer)
146 def long_representer(dumper, data):
147 return dumper.represent_long(data)
148 yaml.add_representer(numpy.int64, int_representer)
150 def float_representer(dumper, data):
151 return dumper.represent_float(data)
152 yaml.add_representer(numpy.float32, float_representer)
153 yaml.add_representer(numpy.float64, float_representer)
155 def data_representer(dumper, data):
156 info = dict(data.info)
157 for key in info.keys():
158 if key.startswith('raw '):
160 return dumper.represent_mapping(DATA_INFO_TAG, info)
161 yaml.add_representer(Data, data_representer)
163 def data_constructor(loader, node):
164 info = loader.construct_mapping(node)
165 return Data(shape=(0,0), dtype=numpy.float32, info=info)
166 yaml.add_constructor(DATA_INFO_TAG, data_constructor)
168 def object_representer(dumper, data):
170 if cls in copy_reg.dispatch_table:
171 reduce = copy_reg.dispatch_table[cls](data)
172 elif hasattr(data, '__reduce_ex__'):
173 reduce = data.__reduce_ex__(2)
174 elif hasattr(data, '__reduce__'):
175 reduce = data.__reduce__()
177 raise RepresenterError("cannot represent object: %r" % data)
178 reduce = (list(reduce)+[None]*5)[:5]
179 function, args, state, listitems, dictitems = reduce
183 if isinstance(state, dict) and '_default_attrs' in state:
184 for key in state['_default_attrs']:
185 if key in state and state[key] == state['_default_attrs'][key]:
187 del(state['_default_attrs'])
188 if listitems is not None:
189 listitems = list(listitems)
190 if dictitems is not None:
191 dictitems = dict(dictitems)
192 if function.__name__ == '__newobj__':
195 tag = u'tag:yaml.org,2002:python/object/new:'
198 tag = u'tag:yaml.org,2002:python/object/apply:'
200 function_name = u'%s.%s' % (function.__module__, function.__name__)
201 if not args and not listitems and not dictitems \
202 and isinstance(state, dict) and newobj:
203 return dumper.represent_mapping(
204 u'tag:yaml.org,2002:python/object:'+function_name, state)
205 if not listitems and not dictitems \
206 and isinstance(state, dict) and not state:
207 return dumper.represent_sequence(tag+function_name, args)
211 if state or not isinstance(state, dict):
212 value['state'] = state
214 value['listitems'] = listitems
216 value['dictitems'] = dictitems
217 return dumper.represent_mapping(tag+function_name, value)
218 yaml.add_representer(FilePlaylist, object_representer)
219 yaml.add_representer(Curve, object_representer)
222 # Monkey patch PyYAML bug 159.
223 # Yaml failed to restore loops in objects when __setstate__ is defined
224 # http://pyyaml.org/ticket/159
225 # With viktor.x.voroshylo@jpmchase.com's patch
226 def construct_object(self, node, deep=False):
228 old_deep = self.deep_construct
229 self.deep_construct = True
230 if node in self.constructed_objects:
231 return self.constructed_objects[node]
232 if node in self.recursive_objects:
233 obj = self.recursive_objects[node]
235 raise ConstructorError(None, None,
236 "found unconstructable recursive node", node.start_mark)
238 self.recursive_objects[node] = None
241 if node.tag in self.yaml_constructors:
242 constructor = self.yaml_constructors[node.tag]
244 for tag_prefix in self.yaml_multi_constructors:
245 if node.tag.startswith(tag_prefix):
246 tag_suffix = node.tag[len(tag_prefix):]
247 constructor = self.yaml_multi_constructors[tag_prefix]
250 if None in self.yaml_multi_constructors:
251 tag_suffix = node.tag
252 constructor = self.yaml_multi_constructors[None]
253 elif None in self.yaml_constructors:
254 constructor = self.yaml_constructors[None]
255 elif isinstance(node, ScalarNode):
256 constructor = self.__class__.construct_scalar
257 elif isinstance(node, SequenceNode):
258 constructor = self.__class__.construct_sequence
259 elif isinstance(node, MappingNode):
260 constructor = self.__class__.construct_mapping
261 if tag_suffix is None:
262 data = constructor(self, node)
264 data = constructor(self, tag_suffix, node)
265 if isinstance(data, types.GeneratorType):
267 data = generator.next()
268 if self.deep_construct:
269 self.recursive_objects[node] = data
270 for dummy in generator:
273 self.state_generators.append(generator)
274 self.constructed_objects[node] = data
275 del self.recursive_objects[node]
277 self.deep_construct = old_deep
279 yaml.constructor.BaseConstructor.construct_object = construct_object