| [25] | 1 | """ |
|---|
| 2 | syck.loaders is a high-level wrapper for the Syck YAML parser. |
|---|
| 3 | Do not use it directly, use the module 'syck' instead. |
|---|
| 4 | """ |
|---|
| [7] | 5 | |
|---|
| [18] | 6 | # Python 2.2 compatibility |
|---|
| 7 | from __future__ import generators |
|---|
| [7] | 8 | |
|---|
| [18] | 9 | try: |
|---|
| 10 | import datetime |
|---|
| 11 | except ImportError: |
|---|
| 12 | pass |
|---|
| 13 | |
|---|
| 14 | try: |
|---|
| 15 | import sets |
|---|
| 16 | except ImportError: |
|---|
| 17 | class _sets: |
|---|
| 18 | def Set(self, items): |
|---|
| 19 | set = {} |
|---|
| 20 | for items in items: |
|---|
| 21 | set[items] = None |
|---|
| 22 | return set |
|---|
| 23 | sets = _sets() |
|---|
| 24 | |
|---|
| [16] | 25 | import _syck |
|---|
| [9] | 26 | |
|---|
| [36] | 27 | import sys, re, warnings |
|---|
| [9] | 28 | |
|---|
| [16] | 29 | __all__ = ['GenericLoader', 'Loader', |
|---|
| [36] | 30 | 'parse', 'load', 'parse_documents', 'load_documents', |
|---|
| 31 | 'NotUnicodeInputWarning'] |
|---|
| [10] | 32 | |
|---|
| [36] | 33 | class NotUnicodeInputWarning(UserWarning): |
|---|
| 34 | pass |
|---|
| 35 | |
|---|
| [16] | 36 | class GenericLoader(_syck.Parser): |
|---|
| [25] | 37 | """ |
|---|
| 38 | GenericLoader constructs primitive Python objects from YAML documents. |
|---|
| 39 | """ |
|---|
| [8] | 40 | |
|---|
| [16] | 41 | def load(self): |
|---|
| [25] | 42 | """ |
|---|
| 43 | Loads a YAML document from the source and return a native Python |
|---|
| 44 | object. On EOF, returns None and set the eof attribute on. |
|---|
| 45 | """ |
|---|
| [16] | 46 | node = self.parse() |
|---|
| 47 | if self.eof: |
|---|
| 48 | return |
|---|
| 49 | return self._convert(node, {}) |
|---|
| [8] | 50 | |
|---|
| [16] | 51 | def _convert(self, node, node_to_object): |
|---|
| 52 | if node in node_to_object: |
|---|
| 53 | return node_to_object[node] |
|---|
| 54 | value = None |
|---|
| 55 | if node.kind == 'scalar': |
|---|
| 56 | value = node.value |
|---|
| 57 | elif node.kind == 'seq': |
|---|
| 58 | value = [] |
|---|
| 59 | for item_node in node.value: |
|---|
| 60 | value.append(self._convert(item_node, node_to_object)) |
|---|
| 61 | elif node.kind == 'map': |
|---|
| 62 | value = {} |
|---|
| 63 | for key_node in node.value: |
|---|
| 64 | key_object = self._convert(key_node, node_to_object) |
|---|
| 65 | value_object = self._convert(node.value[key_node], |
|---|
| 66 | node_to_object) |
|---|
| 67 | try: |
|---|
| [22] | 68 | if key_object in value: |
|---|
| 69 | value = None |
|---|
| 70 | break |
|---|
| [16] | 71 | value[key_object] = value_object |
|---|
| 72 | except TypeError: |
|---|
| 73 | value = None |
|---|
| 74 | break |
|---|
| 75 | if value is None: |
|---|
| 76 | value = [] |
|---|
| 77 | for key_node in node.value: |
|---|
| [22] | 78 | key_object = self._convert(key_node, node_to_object) |
|---|
| [16] | 79 | value_object = self._convert(node.value[key_node], |
|---|
| 80 | node_to_object) |
|---|
| 81 | value.append((key_object, value_object)) |
|---|
| 82 | node.value = value |
|---|
| 83 | object = self.construct(node) |
|---|
| 84 | node_to_object[node] = object |
|---|
| 85 | return object |
|---|
| [7] | 86 | |
|---|
| [16] | 87 | def construct(self, node): |
|---|
| [25] | 88 | """Constructs a Python object by the given node.""" |
|---|
| [16] | 89 | return node.value |
|---|
| [7] | 90 | |
|---|
| [8] | 91 | class Merge: |
|---|
| [25] | 92 | """Represents the merge key '<<'.""" |
|---|
| [8] | 93 | pass |
|---|
| 94 | |
|---|
| 95 | class Default: |
|---|
| [25] | 96 | """Represents the default key '='.""" |
|---|
| [8] | 97 | pass |
|---|
| 98 | |
|---|
| [16] | 99 | class Loader(GenericLoader): |
|---|
| [25] | 100 | """ |
|---|
| 101 | Loader constructs native Python objects from YAML documents. |
|---|
| 102 | """ |
|---|
| [7] | 103 | |
|---|
| 104 | inf_value = 1e300000 |
|---|
| 105 | nan_value = inf_value/inf_value |
|---|
| 106 | |
|---|
| 107 | timestamp_expr = re.compile(r'(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)' |
|---|
| 108 | r'(?:' |
|---|
| 109 | r'(?:[Tt]|[ \t]+)(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d)' |
|---|
| 110 | r'(?:\.(?P<micro>\d+)?)?' |
|---|
| 111 | r'[ \t]*(?:Z|(?P<zhour>[+-]\d\d)(?::(?P<zminute>\d\d))?)?' |
|---|
| 112 | r')?') |
|---|
| 113 | |
|---|
| [8] | 114 | merge_key = Merge() |
|---|
| 115 | default_key = Default() |
|---|
| 116 | |
|---|
| [21] | 117 | non_ascii = [] |
|---|
| 118 | for i in range(256): |
|---|
| 119 | ch = chr(i) |
|---|
| 120 | if ch.isalnum(): |
|---|
| 121 | non_ascii.append(ch) |
|---|
| 122 | else: |
|---|
| 123 | non_ascii.append('_') |
|---|
| 124 | non_ascii = ''.join(non_ascii) |
|---|
| [7] | 125 | |
|---|
| [21] | 126 | python_bools = {'True': True, 'False': False} |
|---|
| [7] | 127 | |
|---|
| [21] | 128 | class python_class: |
|---|
| 129 | pass |
|---|
| [7] | 130 | |
|---|
| [21] | 131 | def find_constructor(self, node): |
|---|
| [25] | 132 | """ |
|---|
| 133 | Returns the contructor for generating a Python object for the given |
|---|
| 134 | node. |
|---|
| 135 | |
|---|
| 136 | The node tags are mapped to constructors by the following rule: |
|---|
| 137 | |
|---|
| 138 | Tag Constructor |
|---|
| 139 | --- ----------- |
|---|
| 140 | tag:yaml.org,2002:type construct_type |
|---|
| 141 | tag:python.yaml.org,2002:type construct_python_type |
|---|
| 142 | x-private:type construct_private_type |
|---|
| 143 | tag:domain.tld,2002:type construct_domain_tld_2002_type |
|---|
| 144 | |
|---|
| 145 | See the method code for more details. |
|---|
| 146 | """ |
|---|
| [21] | 147 | parts = [] |
|---|
| 148 | if node.tag: |
|---|
| 149 | parts = node.tag.split(':') |
|---|
| 150 | if parts: |
|---|
| 151 | if parts[0] == 'tag': |
|---|
| 152 | parts.pop(0) |
|---|
| 153 | if parts: |
|---|
| 154 | if parts[0] == 'yaml.org,2002': |
|---|
| 155 | parts.pop(0) |
|---|
| 156 | elif parts[0] == 'python.yaml.org,2002': |
|---|
| 157 | parts[0] = 'python' |
|---|
| 158 | elif parts[0] == 'x-private': |
|---|
| 159 | parts[0] = 'private' |
|---|
| 160 | parts = [part.translate(self.non_ascii) for part in parts] |
|---|
| 161 | while parts: |
|---|
| 162 | method = 'construct_'+'_'.join(parts) |
|---|
| 163 | if hasattr(self, method): |
|---|
| 164 | return getattr(self, method) |
|---|
| 165 | parts.pop() |
|---|
| [7] | 166 | |
|---|
| [16] | 167 | def construct(self, node): |
|---|
| [25] | 168 | """Constructs a Python object by the given node.""" |
|---|
| [8] | 169 | if node.kind == 'map' and self.merge_key in node.value: |
|---|
| 170 | self.merge_maps(node) |
|---|
| [21] | 171 | constructor = self.find_constructor(node) |
|---|
| 172 | if constructor: |
|---|
| [16] | 173 | return constructor(node) |
|---|
| [7] | 174 | else: |
|---|
| 175 | return node.value |
|---|
| 176 | |
|---|
| [21] | 177 | def construct_null(self, node): |
|---|
| 178 | return None |
|---|
| [7] | 179 | |
|---|
| [21] | 180 | def construct_bool_yes(self, node): |
|---|
| 181 | return True |
|---|
| [16] | 182 | |
|---|
| [21] | 183 | def construct_bool_no(self, node): |
|---|
| 184 | return False |
|---|
| 185 | |
|---|
| [36] | 186 | def construct_str(self, node): |
|---|
| 187 | try: |
|---|
| 188 | value = unicode(node.value, 'utf-8') |
|---|
| 189 | except UnicodeDecodeError: |
|---|
| 190 | warnings.warn("scalar value is not utf-8", NotUnicodeInputWarning) |
|---|
| 191 | return node.value |
|---|
| 192 | try: |
|---|
| 193 | return value.encode('ascii') |
|---|
| 194 | except UnicodeEncodeError: |
|---|
| 195 | return value |
|---|
| 196 | |
|---|
| [21] | 197 | def construct_numeric_base60(self, num_type, node): |
|---|
| [7] | 198 | digits = [num_type(part) for part in node.value.split(':')] |
|---|
| 199 | digits.reverse() |
|---|
| 200 | base = 1 |
|---|
| 201 | value = num_type(0) |
|---|
| 202 | for digit in digits: |
|---|
| 203 | value += digit*base |
|---|
| 204 | base *= 60 |
|---|
| 205 | return value |
|---|
| 206 | |
|---|
| [21] | 207 | def construct_int(self, node): |
|---|
| 208 | return int(node.value) |
|---|
| 209 | |
|---|
| 210 | def construct_int_hex(self, node): |
|---|
| 211 | return int(node.value, 16) |
|---|
| 212 | |
|---|
| 213 | def construct_int_oct(self, node): |
|---|
| 214 | return int(node.value, 8) |
|---|
| 215 | |
|---|
| 216 | def construct_int_base60(self, node): |
|---|
| 217 | return self.construct_numeric_base60(int, node) |
|---|
| 218 | |
|---|
| 219 | def construct_float(self, node): |
|---|
| 220 | return float(node.value) |
|---|
| 221 | construct_float_fix = construct_float |
|---|
| 222 | construct_float_exp = construct_float |
|---|
| 223 | |
|---|
| 224 | def construct_float_base60(self, node): |
|---|
| 225 | return self.construct_numeric_base60(float, node) |
|---|
| 226 | |
|---|
| 227 | def construct_float_inf(self, node): |
|---|
| 228 | return self.inf_value |
|---|
| 229 | |
|---|
| 230 | def construct_float_neginf(self, node): |
|---|
| 231 | return -self.inf_value |
|---|
| 232 | |
|---|
| 233 | def construct_float_nan(self, node): |
|---|
| 234 | return self.nan_value |
|---|
| 235 | |
|---|
| 236 | def construct_binary(self, node): |
|---|
| 237 | return node.value.decode('base64') |
|---|
| 238 | |
|---|
| [16] | 239 | def construct_timestamp(self, node): |
|---|
| [7] | 240 | match = self.timestamp_expr.match(node.value) |
|---|
| 241 | values = match.groupdict() |
|---|
| 242 | for key in values: |
|---|
| 243 | if values[key]: |
|---|
| 244 | values[key] = int(values[key]) |
|---|
| 245 | else: |
|---|
| 246 | values[key] = 0 |
|---|
| 247 | micro = values['micro'] |
|---|
| 248 | if micro: |
|---|
| 249 | while 10*micro < 1000000: |
|---|
| 250 | micro *= 10 |
|---|
| 251 | stamp = datetime.datetime(values['year'], values['month'], values['day'], |
|---|
| 252 | values['hour'], values['minute'], values['second'], micro) |
|---|
| 253 | diff = datetime.timedelta(hours=values['zhour'], minutes=values['zminute']) |
|---|
| 254 | return stamp-diff |
|---|
| [21] | 255 | construct_timestamp_ymd = construct_timestamp |
|---|
| 256 | construct_timestamp_iso8601 = construct_timestamp |
|---|
| 257 | construct_timestamp_spaced = construct_timestamp |
|---|
| [7] | 258 | |
|---|
| [16] | 259 | def construct_merge(self, node): |
|---|
| [8] | 260 | return self.merge_key |
|---|
| 261 | |
|---|
| [16] | 262 | def construct_default(self, node): |
|---|
| [8] | 263 | return self.default_key |
|---|
| 264 | |
|---|
| 265 | def merge_maps(self, node): |
|---|
| 266 | maps = node.value[self.merge_key] |
|---|
| 267 | del node.value[self.merge_key] |
|---|
| 268 | if not isinstance(maps, list): |
|---|
| 269 | maps = [maps] |
|---|
| 270 | maps.reverse() |
|---|
| 271 | maps.append(node.value.copy()) |
|---|
| 272 | for item in maps: |
|---|
| 273 | node.value.update(item) |
|---|
| 274 | |
|---|
| [16] | 275 | def construct_omap(self, node): |
|---|
| [8] | 276 | omap = [] |
|---|
| 277 | for mapping in node.value: |
|---|
| 278 | for key in mapping: |
|---|
| 279 | omap.append((key, mapping[key])) |
|---|
| 280 | return omap |
|---|
| 281 | |
|---|
| [16] | 282 | def construct_pairs(self, node): # Same as construct_omap. |
|---|
| [8] | 283 | pairs = [] |
|---|
| 284 | for mapping in node.value: |
|---|
| 285 | for key in mapping: |
|---|
| 286 | pairs.append((key, mapping[key])) |
|---|
| 287 | return pairs |
|---|
| 288 | |
|---|
| [16] | 289 | def construct_set(self, node): |
|---|
| [8] | 290 | return sets.Set(node.value) |
|---|
| 291 | |
|---|
| [21] | 292 | def construct_python_none(self, node): |
|---|
| 293 | return None |
|---|
| 294 | |
|---|
| 295 | def construct_python_bool(self, node): |
|---|
| 296 | return self.python_bools[node.value] |
|---|
| 297 | |
|---|
| 298 | def construct_python_int(self, node): |
|---|
| 299 | return int(node.value) |
|---|
| 300 | |
|---|
| 301 | def construct_python_long(self, node): |
|---|
| 302 | return long(node.value) |
|---|
| 303 | |
|---|
| 304 | def construct_python_float(self, node): |
|---|
| 305 | return float(node.value) |
|---|
| 306 | |
|---|
| 307 | def construct_python_str(self, node): |
|---|
| 308 | return str(node.value) |
|---|
| 309 | |
|---|
| 310 | def construct_python_unicode(self, node): |
|---|
| 311 | return unicode(node.value, 'utf-8') |
|---|
| 312 | |
|---|
| 313 | def construct_python_list(self, node): |
|---|
| 314 | return node.value |
|---|
| 315 | |
|---|
| 316 | def construct_python_tuple(self, node): |
|---|
| 317 | return tuple(node.value) |
|---|
| 318 | |
|---|
| 319 | def construct_python_dict(self, node): |
|---|
| 320 | return node.value |
|---|
| 321 | |
|---|
| 322 | def find_python_object(self, node): |
|---|
| 323 | full_name = node.tag.split(':')[3] |
|---|
| 324 | parts = full_name.split('.') |
|---|
| 325 | object_name = parts.pop() |
|---|
| 326 | module_name = '.'.join(parts) |
|---|
| 327 | if not module_name: |
|---|
| 328 | module_name = '__builtin__' |
|---|
| 329 | else: |
|---|
| 330 | __import__(module_name) |
|---|
| 331 | return getattr(sys.modules[module_name], object_name) |
|---|
| 332 | |
|---|
| 333 | def find_python_state(self, node): |
|---|
| 334 | if node.kind == 'seq': |
|---|
| 335 | args = node.value |
|---|
| 336 | kwds = {} |
|---|
| 337 | state = {} |
|---|
| 338 | else: |
|---|
| 339 | args = node.value.get('args', []) |
|---|
| 340 | kwds = node.value.get('kwds', {}) |
|---|
| 341 | state = node.value.get('state', {}) |
|---|
| 342 | return args, kwds, state |
|---|
| 343 | |
|---|
| 344 | def set_python_state(self, object, state): |
|---|
| 345 | if hasattr(object, '__setstate__'): |
|---|
| 346 | object.__setstate__(state) |
|---|
| 347 | else: |
|---|
| 348 | slotstate = {} |
|---|
| 349 | if isinstance(state, tuple) and len(state) == 2: |
|---|
| 350 | state, slotstate = state |
|---|
| [25] | 351 | if hasattr(object, '__dict__'): |
|---|
| 352 | object.__dict__.update(state) |
|---|
| 353 | elif state: |
|---|
| 354 | slotstate.update(state) |
|---|
| [21] | 355 | for key, value in slotstate.items(): |
|---|
| 356 | setattr(object, key, value) |
|---|
| 357 | |
|---|
| 358 | def construct_python_name(self, node): |
|---|
| 359 | return self.find_python_object(node) |
|---|
| 360 | |
|---|
| 361 | def construct_python_object(self, node): |
|---|
| 362 | cls = self.find_python_object(node) |
|---|
| 363 | if type(cls) is type(self.python_class): |
|---|
| 364 | if hasattr(cls, '__getnewargs__'): |
|---|
| 365 | object = cls() |
|---|
| 366 | else: |
|---|
| 367 | object = self.python_class() |
|---|
| 368 | object.__class__ = cls |
|---|
| 369 | else: |
|---|
| 370 | object = cls.__new__(cls) |
|---|
| 371 | self.set_python_state(object, node.value) |
|---|
| 372 | return object |
|---|
| 373 | |
|---|
| 374 | def construct_python_new(self, node): |
|---|
| 375 | cls = self.find_python_object(node) |
|---|
| 376 | args, kwds, state = self.find_python_state(node) |
|---|
| 377 | if type(cls) is type(self.python_class): |
|---|
| 378 | object = cls(*args, **kwds) |
|---|
| 379 | else: |
|---|
| 380 | object = cls.__new__(cls, *args, **kwds) |
|---|
| 381 | self.set_python_state(object, state) |
|---|
| 382 | return object |
|---|
| 383 | |
|---|
| 384 | def construct_python_apply(self, node): |
|---|
| 385 | constructor = self.find_python_object(node) |
|---|
| 386 | args, kwds, state = self.find_python_state(node) |
|---|
| 387 | object = constructor(*args, **kwds) |
|---|
| 388 | self.set_python_state(object, state) |
|---|
| 389 | return object |
|---|
| 390 | |
|---|
| [22] | 391 | def parse(source, Loader=Loader, **parameters): |
|---|
| [16] | 392 | """Parses 'source' and returns the root of the 'Node' graph.""" |
|---|
| [22] | 393 | loader = Loader(source, **parameters) |
|---|
| [16] | 394 | return loader.parse() |
|---|
| 395 | |
|---|
| [22] | 396 | def load(source, Loader=Loader, **parameters): |
|---|
| [16] | 397 | """Parses 'source' and returns the root object.""" |
|---|
| [22] | 398 | loader = Loader(source, **parameters) |
|---|
| [16] | 399 | return loader.load() |
|---|
| 400 | |
|---|
| [22] | 401 | def parse_documents(source, Loader=Loader, **parameters): |
|---|
| [25] | 402 | """Iterates over 'source' and yields the root 'Node' for each document.""" |
|---|
| [22] | 403 | loader = Loader(source, **parameters) |
|---|
| [16] | 404 | while True: |
|---|
| 405 | node = loader.parse() |
|---|
| 406 | if loader.eof: |
|---|
| 407 | break |
|---|
| 408 | yield node |
|---|
| 409 | |
|---|
| [22] | 410 | def load_documents(source, Loader=Loader, **parameters): |
|---|
| [25] | 411 | """Iterates over 'source' and yields the root object for each document.""" |
|---|
| [22] | 412 | loader = Loader(source, **parameters) |
|---|
| [16] | 413 | while True: |
|---|
| 414 | object = loader.load() |
|---|
| 415 | if loader.eof: |
|---|
| 416 | break |
|---|
| 417 | yield object |
|---|
| 418 | |
|---|