Adjust Hooke internals to use unsafe YAML (not just builtin Python types).
[hooke.git] / hooke / util / yaml.py
1 # Copyright
2
3 """Add representers to YAML to support Hooke.
4
5 Without introspection, YAML cannot decide how to save some
6 objects.  By refusing to save these objects, we obviously loose
7 that information, so make sure the things you drop are either
8 stored somewhere else or not important.
9
10 >>> import yaml
11 >>> a = numpy.array([1,2,3])
12 >>> print yaml.dump(a)
13 null
14 ...
15 <BLANKLINE>
16
17 The default behavior is to crash.
18
19 >>> yaml.Dumper.yaml_representers.pop(numpy.ndarray)  # doctest: +ELLIPSIS
20 <function none_representer at 0x...>
21 >>> print yaml.dump(a)
22 !!python/object/apply:numpy.core.multiarray._reconstruct
23 args:
24 - !!python/name:numpy.ndarray ''
25 - !!python/tuple [0]
26 - b
27 state: !!python/tuple
28 - 1
29 - !!python/tuple [3]
30 - null
31 - false
32 - "\\x01\\0\\0\\0\\x02\\0\\0\\0\\x03\\0\\0\\0"
33 <BLANKLINE>
34
35 Hmm, at one point that crashed like this::
36
37     Traceback (most recent call last):
38       ...
39         if data in [None, ()]:
40     TypeError: data type not understood
41
42 Must be because of the other representers I've loaded since.
43
44 Restore the representer for future tests.
45
46 >>> yaml.add_representer(numpy.ndarray, none_representer)
47 """
48
49 from __future__ import absolute_import
50 import copy_reg
51 import sys
52 import types
53
54 import numpy
55 import yaml
56 import yaml.constructor
57 import yaml.representer
58
59 from ..curve import Data, Curve
60 from ..playlist import FilePlaylist
61
62
63 if False: # YAML dump debugging code
64     """To help isolate data types etc. that give YAML problems.
65
66     This is usually caused by external C modules (e.g. numpy) that
67     define new types (e.g. numpy.ndarray) which YAML cannot inspect.
68     """
69     def ignore_aliases(data):
70         print data, repr(data), type(data), repr(type(data))
71         sys.stdout.flush()
72         if data in [None, ()]:
73             return True
74         if isinstance(data, (str, unicode, bool, int, float)):
75             return True
76     yaml.representer.SafeRepresenter.ignore_aliases = staticmethod(
77         ignore_aliases)
78 else:
79     # Avoid error with
80     #   numpy.dtype(numpy.int32) in [None, ()]
81     # See
82     #   http://projects.scipy.org/numpy/ticket/1001
83     def ignore_aliases(data):
84         try:
85             if data in [None, ()]:
86                 return True
87             if isinstance(data, (str, unicode, bool, int, float)):
88                 return True
89         except TypeError, e:
90             pass
91     yaml.representer.SafeRepresenter.ignore_aliases = staticmethod(
92         ignore_aliases)
93
94
95 def none_representer(dumper, data):
96     return dumper.represent_none(None)
97 yaml.add_representer(numpy.ndarray, none_representer)
98 yaml.add_representer(numpy.dtype, none_representer)
99
100 def bool_representer(dumper, data):
101     return dumper.represent_bool(data)
102 yaml.add_representer(numpy.bool_, bool_representer)
103
104 def int_representer(dumper, data):
105     return dumper.represent_int(data)
106 yaml.add_representer(numpy.int32, int_representer)
107 yaml.add_representer(numpy.dtype(numpy.int32), int_representer)
108
109 def long_representer(dumper, data):
110     return dumper.represent_long(data)
111 yaml.add_representer(numpy.int64, int_representer)
112
113 def float_representer(dumper, data):
114     return dumper.represent_float(data)
115 yaml.add_representer(numpy.float32, float_representer)
116 yaml.add_representer(numpy.float64, float_representer)
117
118 def data_representer(dumper, data):
119     info = dict(data.info)
120     for key in info.keys():
121         if key.startswith('raw '):
122             del(info[key])
123     return dumper.represent_mapping(u'!hooke.curve.DataInfo', info)
124 yaml.add_representer(Data, data_representer)
125
126 def object_representer(dumper, data):
127     cls = type(data)
128     if cls in copy_reg.dispatch_table:
129         reduce = copy_reg.dispatch_table[cls](data)
130     elif hasattr(data, '__reduce_ex__'):
131         reduce = data.__reduce_ex__(2)
132     elif hasattr(data, '__reduce__'):
133         reduce = data.__reduce__()
134     else:
135         raise RepresenterError("cannot represent object: %r" % data)
136     reduce = (list(reduce)+[None]*5)[:5]
137     function, args, state, listitems, dictitems = reduce
138     args = list(args)
139     if state is None:
140         state = {}
141     if isinstance(state, dict) and '_default_attrs' in state:
142         for key in state['_default_attrs']:
143             if key in state and state[key] == state['_default_attrs'][key]:
144                 del(state[key])
145         del(state['_default_attrs'])
146     if listitems is not None:
147         listitems = list(listitems)
148     if dictitems is not None:
149         dictitems = dict(dictitems)
150     if function.__name__ == '__newobj__':
151         function = args[0]
152         args = args[1:]
153         tag = u'tag:yaml.org,2002:python/object/new:'
154         newobj = True
155     else:
156         tag = u'tag:yaml.org,2002:python/object/apply:'
157         newobj = False
158     function_name = u'%s.%s' % (function.__module__, function.__name__)
159     if not args and not listitems and not dictitems \
160             and isinstance(state, dict) and newobj:
161         return dumper.represent_mapping(
162                 u'tag:yaml.org,2002:python/object:'+function_name, state)
163     if not listitems and not dictitems  \
164             and isinstance(state, dict) and not state:
165         return dumper.represent_sequence(tag+function_name, args)
166     value = {}
167     if args:
168         value['args'] = args
169     if state or not isinstance(state, dict):
170         value['state'] = state
171     if listitems:
172         value['listitems'] = listitems
173     if dictitems:
174         value['dictitems'] = dictitems
175     return dumper.represent_mapping(tag+function_name, value)
176 yaml.add_representer(FilePlaylist, object_representer)
177 yaml.add_representer(Curve, object_representer)
178
179
180 # Monkey patch PyYAML bug 159.
181 #   Yaml failed to restore loops in objects when __setstate__ is defined
182 #   http://pyyaml.org/ticket/159
183 # With viktor.x.voroshylo@jpmchase.com's patch
184 def construct_object(self, node, deep=False):
185     if deep:
186         old_deep = self.deep_construct
187         self.deep_construct = True
188     if node in self.constructed_objects:
189         return self.constructed_objects[node]
190     if node in self.recursive_objects:
191         obj = self.recursive_objects[node]
192         if obj is None :
193             raise ConstructorError(None, None,
194                  "found unconstructable recursive node", node.start_mark)
195         return obj
196     self.recursive_objects[node] = None
197     constructor = None
198     tag_suffix = None
199     if node.tag in self.yaml_constructors:
200         constructor = self.yaml_constructors[node.tag]
201     else:
202         for tag_prefix in self.yaml_multi_constructors:
203             if node.tag.startswith(tag_prefix):
204                 tag_suffix = node.tag[len(tag_prefix):]
205                 constructor = self.yaml_multi_constructors[tag_prefix]
206                 break
207         else:
208             if None in self.yaml_multi_constructors:
209                 tag_suffix = node.tag
210                 constructor = self.yaml_multi_constructors[None]
211             elif None in self.yaml_constructors:
212                 constructor = self.yaml_constructors[None]
213             elif isinstance(node, ScalarNode):
214                 constructor = self.__class__.construct_scalar
215             elif isinstance(node, SequenceNode):
216                 constructor = self.__class__.construct_sequence
217             elif isinstance(node, MappingNode):
218                 constructor = self.__class__.construct_mapping
219     if tag_suffix is None:
220         data = constructor(self, node)
221     else:
222         data = constructor(self, tag_suffix, node)
223     if isinstance(data, types.GeneratorType):
224         generator = data
225         data = generator.next()
226         if self.deep_construct:
227             self.recursive_objects[node] = data
228             for dummy in generator:
229                 pass
230         else:
231             self.state_generators.append(generator)
232     self.constructed_objects[node] = data
233     del self.recursive_objects[node]
234     if deep:
235         self.deep_construct = old_deep
236     return data
237 yaml.constructor.BaseConstructor.construct_object = construct_object