Coverage for /usr/local/lib/python3.13/dist-packages/pyrocko/guts.py: 97%

1417 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-12-04 10:41 +0000

1# https://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6''' 

7Lightweight declarative YAML and XML data binding for Python. 

8''' 

9 

10import datetime 

11import calendar 

12import re 

13import sys 

14import os 

15import types 

16import copy 

17import logging 

18import os.path as op 

19from collections import defaultdict 

20from base64 import b64decode, b64encode 

21 

22from io import BytesIO 

23 

24try: 

25 import numpy as num 

26except ImportError: 

27 num = None 

28 

29import yaml 

30try: 

31 from yaml import CSafeLoader as SafeLoader, CSafeDumper as SafeDumper 

32except ImportError: 

33 from yaml import SafeLoader, SafeDumper 

34 

35from .util import time_to_str, str_to_time, TimeStrError, hpfloat, \ 

36 get_time_float 

37 

38 

39logger = logging.getLogger('pyrocko.guts') 

40 

41ALLOW_INCLUDE = False 

42 

43 

44class GutsSafeDumper(SafeDumper): 

45 pass 

46 

47 

48class GutsSafeLoader(SafeLoader): 

49 pass 

50 

51 

52g_iprop = 0 

53 

54g_deferred = {} 

55g_deferred_content = {} 

56 

57g_tagname_to_class = {} 

58g_xmltagname_to_class = {} 

59g_guessable_xmlns = {} 

60 

61guts_types = [ 

62 'Object', 'SObject', 'String', 'Unicode', 'Int', 'Float', 

63 'Complex', 'Bool', 'Timestamp', 'DateTimestamp', 'StringPattern', 

64 'UnicodePattern', 'StringChoice', 'IntChoice', 'List', 'Dict', 'Tuple', 

65 'StringUnion', 'Choice', 'Any'] 

66 

67us_to_cc_regex = re.compile(r'([a-z])_([a-z])') 

68 

69 

70class literal(str): 

71 pass 

72 

73 

74class folded(str): 

75 pass 

76 

77 

78class singlequoted(str): 

79 pass 

80 

81 

82class doublequoted(str): 

83 pass 

84 

85 

86def make_str_presenter(style): 

87 def presenter(dumper, data): 

88 return dumper.represent_scalar( 

89 'tag:yaml.org,2002:str', str(data), style=style) 

90 

91 return presenter 

92 

93 

94str_style_map = { 

95 None: lambda x: x, 

96 '|': literal, 

97 '>': folded, 

98 "'": singlequoted, 

99 '"': doublequoted} 

100 

101for (style, cls) in str_style_map.items(): 

102 if style: 

103 GutsSafeDumper.add_representer(cls, make_str_presenter(style)) 

104 

105 

106class uliteral(str): 

107 pass 

108 

109 

110class ufolded(str): 

111 pass 

112 

113 

114class usinglequoted(str): 

115 pass 

116 

117 

118class udoublequoted(str): 

119 pass 

120 

121 

122def make_unicode_presenter(style): 

123 def presenter(dumper, data): 

124 return dumper.represent_scalar( 

125 'tag:yaml.org,2002:str', str(data), style=style) 

126 

127 return presenter 

128 

129 

130unicode_style_map = { 

131 None: lambda x: x, 

132 '|': literal, 

133 '>': folded, 

134 "'": singlequoted, 

135 '"': doublequoted} 

136 

137for (style, cls) in unicode_style_map.items(): 

138 if style: 

139 GutsSafeDumper.add_representer(cls, make_unicode_presenter(style)) 

140 

141 

142class blist(list): 

143 pass 

144 

145 

146class flist(list): 

147 pass 

148 

149 

150list_style_map = { 

151 None: list, 

152 'block': blist, 

153 'flow': flist} 

154 

155 

156def make_list_presenter(flow_style): 

157 def presenter(dumper, data): 

158 return dumper.represent_sequence( 

159 'tag:yaml.org,2002:seq', data, flow_style=flow_style) 

160 

161 return presenter 

162 

163 

164GutsSafeDumper.add_representer(blist, make_list_presenter(False)) 

165GutsSafeDumper.add_representer(flist, make_list_presenter(True)) 

166 

167if num: 

168 def numpy_float_presenter(dumper, data): 

169 return dumper.represent_float(float(data)) 

170 

171 def numpy_int_presenter(dumper, data): 

172 return dumper.represent_int(int(data)) 

173 

174 for dtype in (num.float64, num.float32): 

175 GutsSafeDumper.add_representer(dtype, numpy_float_presenter) 

176 

177 for dtype in (num.int32, num.int64): 

178 GutsSafeDumper.add_representer(dtype, numpy_int_presenter) 

179 

180 

181def us_to_cc(s): 

182 return us_to_cc_regex.sub(lambda pat: pat.group(1)+pat.group(2).upper(), s) 

183 

184 

185cc_to_us_regex1 = re.compile(r'([a-z])([A-Z]+)([a-z]|$)') 

186cc_to_us_regex2 = re.compile(r'([A-Z])([A-Z][a-z])') 

187 

188 

189def cc_to_us(s): 

190 return cc_to_us_regex2.sub('\\1_\\2', cc_to_us_regex1.sub( 

191 '\\1_\\2\\3', s)).lower() 

192 

193 

194re_frac = re.compile(r'\.[1-9]FRAC') 

195frac_formats = dict([('.%sFRAC' % x, '%.'+x+'f') for x in '123456789']) 

196 

197 

198def encode_utf8(s): 

199 return s.encode('utf-8') 

200 

201 

202def no_encode(s): 

203 return s 

204 

205 

206def make_xmltagname_from_name(name): 

207 return us_to_cc(name) 

208 

209 

210def make_name_from_xmltagname(xmltagname): 

211 return cc_to_us(xmltagname) 

212 

213 

214def make_content_name(name): 

215 if name.endswith('_list'): 

216 return name[:-5] 

217 elif name.endswith('s'): 

218 return name[:-1] 

219 else: 

220 return name 

221 

222 

223def classnames(cls): 

224 if isinstance(cls, tuple): 

225 return '(%s)' % ', '.join(x.__name__ for x in cls) 

226 else: 

227 return cls.__name__ 

228 

229 

230def expand_stream_args(mode): 

231 def wrap(f): 

232 ''' 

233 Decorator to enhance functions taking stream objects. 

234 

235 Wraps a function f(..., stream, ...) so that it can also be called as 

236 f(..., filename='myfilename', ...) or as f(..., string='mydata', ...). 

237 ''' 

238 

239 def g(*args, **kwargs): 

240 stream = kwargs.pop('stream', None) 

241 filename = kwargs.get('filename', None) 

242 if mode != 'r': 

243 filename = kwargs.pop('filename', None) 

244 string = kwargs.pop('string', None) 

245 

246 assert sum(x is not None for x in (stream, filename, string)) <= 1 

247 

248 if stream is not None: 

249 kwargs['stream'] = stream 

250 return f(*args, **kwargs) 

251 

252 elif filename is not None: 

253 stream = open(filename, mode+'b') 

254 kwargs['stream'] = stream 

255 retval = f(*args, **kwargs) 

256 if isinstance(retval, types.GeneratorType): 

257 def wrap_generator(gen): 

258 try: 

259 for x in gen: 

260 yield x 

261 

262 except GeneratorExit: 

263 pass 

264 

265 stream.close() 

266 

267 return wrap_generator(retval) 

268 

269 else: 

270 stream.close() 

271 return retval 

272 

273 elif string is not None: 

274 assert mode == 'r', \ 

275 'Keyword argument string=... cannot be used in dumper ' \ 

276 'function.' 

277 

278 kwargs['stream'] = BytesIO(string.encode('utf-8')) 

279 return f(*args, **kwargs) 

280 

281 else: 

282 assert mode == 'w', \ 

283 'Use keyword argument stream=... or filename=... in ' \ 

284 'loader function.' 

285 

286 sout = BytesIO() 

287 f(stream=sout, *args, **kwargs) 

288 return sout.getvalue().decode('utf-8') 

289 

290 g.__doc__ = f.__doc__ 

291 return g 

292 

293 return wrap 

294 

295 

296class Defer(object): 

297 def __init__(self, classname, *args, **kwargs): 

298 global g_iprop 

299 if kwargs.get('position', None) is None: 

300 kwargs['position'] = g_iprop 

301 

302 g_iprop += 1 

303 

304 self.classname = classname 

305 self.args = args 

306 self.kwargs = kwargs 

307 

308 

309class TBase(object): 

310 ''' 

311 Base class for Guts type definitions. 

312 

313 :param default: 

314 Default value or :py:class:`DefaultMaker` object (see 

315 :py:meth:`Object.D`) to be used to generate a default. 

316 

317 :param optional: 

318 If ``True``, the attribute is optional and may be set to ``None``. 

319 :type optional: 

320 bool 

321 

322 :param xmlstyle: 

323 Controls how the attribute is serialized in XML. :Choices: 

324 ``'element'``, ``'attribute'``, ``'content'``. Only scalar and 

325 :py:class:`SObject` values may be serialized as ``'attribute'``. Only 

326 one attribute in a class may be serialized as ``'content'``. 

327 :type xmlstyle: 

328 str 

329 

330 :param xmltagname: 

331 XML tag name to be used. By default, the attribute name converted to 

332 camel-case is used. 

333 :type xmltagname: 

334 str 

335 

336 :param xmlns: 

337 XML namespace to be used for this attribute. 

338 :type xmlns: 

339 str 

340 

341 :param help: 

342 Description of the attribute used in documentation and online help. 

343 :type help: 

344 rst formatted :py:class:`str` 

345 

346 :param position: 

347 Position index to be used when the attribute should be output in 

348 non-default order. 

349 :type position: 

350 int 

351 ''' 

352 

353 strict = False 

354 multivalued = None 

355 force_regularize = False 

356 propnames = [] 

357 _dummy_cls = None 

358 _cls = None 

359 _sphinx_doc_skip = False 

360 

361 @classmethod 

362 def init_propertystuff(cls): 

363 cls.properties = [] 

364 cls.xmltagname_to_name = {} 

365 cls.xmltagname_to_name_multivalued = {} 

366 cls.xmltagname_to_class = {} 

367 cls.content_property = None 

368 

369 def __init__( 

370 self, 

371 default=None, 

372 optional=False, 

373 xmlstyle='element', 

374 xmltagname=None, 

375 xmlns=None, 

376 help=None, 

377 position=None): 

378 

379 global g_iprop 

380 if position is not None: 

381 self.position = position 

382 else: 

383 self.position = g_iprop 

384 

385 g_iprop += 1 

386 self._default = default 

387 

388 self.optional = optional 

389 self.name = None 

390 self._xmltagname = xmltagname 

391 self._xmlns = xmlns 

392 self.parent = None 

393 self.xmlstyle = xmlstyle 

394 self.help = help 

395 self._sphinx_doc_skip = True 

396 

397 def default(self): 

398 return make_default(self._default) 

399 

400 def is_default(self, val): 

401 if self._default is None: 

402 return val is None 

403 else: 

404 return self._default == val 

405 

406 def has_default(self): 

407 return self._default is not None 

408 

409 def xname(self): 

410 if self.name is not None: 

411 return self.name 

412 elif self.parent is not None: 

413 return 'element of %s' % self.parent.xname() 

414 else: 

415 return '?' 

416 

417 def set_xmlns(self, xmlns): 

418 if self._xmlns is None and not self.xmlns: 

419 self._xmlns = xmlns 

420 

421 if self.multivalued: 

422 self.content_t.set_xmlns(xmlns) 

423 

424 def get_xmlns(self): 

425 return self._xmlns or self.xmlns 

426 

427 def get_xmltagname(self): 

428 if self._xmltagname is not None: 

429 return self.get_xmlns() + ' ' + self._xmltagname 

430 elif self.name: 

431 return self.get_xmlns() + ' ' \ 

432 + make_xmltagname_from_name(self.name) 

433 elif self.xmltagname: 

434 return self.get_xmlns() + ' ' + self.xmltagname 

435 else: 

436 assert False 

437 

438 @classmethod 

439 def get_property(cls, name): 

440 for prop in cls.properties: 

441 if prop.name == name: 

442 return prop 

443 

444 raise ValueError() 

445 

446 @classmethod 

447 def remove_property(cls, name): 

448 

449 prop = cls.get_property(name) 

450 

451 if not prop.multivalued: 

452 del cls.xmltagname_to_class[prop.effective_xmltagname] 

453 del cls.xmltagname_to_name[prop.effective_xmltagname] 

454 else: 

455 del cls.xmltagname_to_class[prop.content_t.effective_xmltagname] 

456 del cls.xmltagname_to_name_multivalued[ 

457 prop.content_t.effective_xmltagname] 

458 

459 if cls.content_property is prop: 

460 cls.content_property = None 

461 

462 cls.properties.remove(prop) 

463 cls.propnames.remove(name) 

464 

465 return prop 

466 

467 @classmethod 

468 def add_property(cls, name, prop): 

469 

470 prop.instance = prop 

471 prop.name = name 

472 prop.set_xmlns(cls.xmlns) 

473 

474 if isinstance(prop, Choice.T): 

475 for tc in prop.choices: 

476 tc.effective_xmltagname = tc.get_xmltagname() 

477 cls.xmltagname_to_class[tc.effective_xmltagname] = tc._cls 

478 cls.xmltagname_to_name[tc.effective_xmltagname] = prop.name 

479 elif not prop.multivalued: 

480 prop.effective_xmltagname = prop.get_xmltagname() 

481 cls.xmltagname_to_class[prop.effective_xmltagname] = prop._cls 

482 cls.xmltagname_to_name[prop.effective_xmltagname] = prop.name 

483 else: 

484 prop.content_t.name = make_content_name(prop.name) 

485 prop.content_t.effective_xmltagname = \ 

486 prop.content_t.get_xmltagname() 

487 cls.xmltagname_to_class[ 

488 prop.content_t.effective_xmltagname] = prop.content_t._cls 

489 cls.xmltagname_to_name_multivalued[ 

490 prop.content_t.effective_xmltagname] = prop.name 

491 

492 cls.properties.append(prop) 

493 

494 cls.properties.sort(key=lambda x: x.position) 

495 

496 cls.propnames = [p.name for p in cls.properties] 

497 

498 if prop.xmlstyle == 'content': 

499 cls.content_property = prop 

500 

501 @classmethod 

502 def ivals(cls, val): 

503 for prop in cls.properties: 

504 yield getattr(val, prop.name) 

505 

506 @classmethod 

507 def ipropvals(cls, val): 

508 for prop in cls.properties: 

509 yield prop, getattr(val, prop.name) 

510 

511 @classmethod 

512 def inamevals(cls, val): 

513 for prop in cls.properties: 

514 yield prop.name, getattr(val, prop.name) 

515 

516 @classmethod 

517 def ipropvals_to_save(cls, val, xmlmode=False): 

518 for prop in cls.properties: 

519 v = getattr(val, prop.name) 

520 if v is not None and ( 

521 not (prop.optional or (prop.multivalued and not v)) 

522 or (not prop.is_default(v))): 

523 

524 if xmlmode: 

525 yield prop, prop.to_save_xml(v) 

526 else: 

527 yield prop, prop.to_save(v) 

528 

529 @classmethod 

530 def inamevals_to_save(cls, val, xmlmode=False): 

531 for prop, v in cls.ipropvals_to_save(val, xmlmode): 

532 yield prop.name, v 

533 

534 @classmethod 

535 def translate_from_xml(cls, list_of_pairs, strict): 

536 d = {} 

537 for k, v in list_of_pairs: 

538 if k in cls.xmltagname_to_name_multivalued: 

539 k2 = cls.xmltagname_to_name_multivalued[k] 

540 if k2 not in d: 

541 d[k2] = [] 

542 

543 d[k2].append(v) 

544 elif k in cls.xmltagname_to_name: 

545 k2 = cls.xmltagname_to_name[k] 

546 if k2 in d: 

547 raise ArgumentError( 

548 'Unexpectedly found more than one child element "%s" ' 

549 'within "%s".' % (k, cls.tagname)) 

550 

551 d[k2] = v 

552 elif k is None: 

553 if cls.content_property: 

554 k2 = cls.content_property.name 

555 d[k2] = v 

556 else: 

557 if strict: 

558 raise ArgumentError( 

559 'Unexpected child element "%s" found within "%s".' % ( 

560 k, cls.tagname)) 

561 

562 return d 

563 

564 def validate(self, val, regularize=False, depth=-1): 

565 if self.optional and val is None: 

566 return val 

567 

568 is_derived = isinstance(val, self._cls) 

569 is_exact = type(val) is self._cls 

570 

571 not_ok = not self.strict and not is_derived or \ 

572 self.strict and not is_exact 

573 

574 if not_ok or self.force_regularize: 

575 if regularize: 

576 try: 

577 val = self.regularize_extra(val) 

578 except ValueError: 

579 raise ValidationError( 

580 '%s: could not convert "%s" to type %s' % ( 

581 self.xname(), val, classnames(self._cls))) 

582 else: 

583 raise ValidationError( 

584 '%s: "%s" (type: %s) is not of type %s' % ( 

585 self.xname(), val, type(val), classnames(self._cls))) 

586 

587 validator = self 

588 if isinstance(self._cls, tuple): 

589 clss = self._cls 

590 else: 

591 clss = (self._cls,) 

592 

593 for cls in clss: 

594 try: 

595 if type(val) is not cls and isinstance(val, cls): 

596 validator = val.T.instance 

597 

598 except AttributeError: 

599 pass 

600 

601 validator.validate_extra(val) 

602 

603 if depth != 0: 

604 val = validator.validate_children(val, regularize, depth) 

605 

606 return val 

607 

608 def regularize_extra(self, val): 

609 return self._cls(val) 

610 

611 def validate_extra(self, val): 

612 pass 

613 

614 def validate_children(self, val, regularize, depth): 

615 for prop, propval in self.ipropvals(val): 

616 newpropval = prop.validate(propval, regularize, depth-1) 

617 if regularize and (newpropval is not propval): 

618 setattr(val, prop.name, newpropval) 

619 

620 return val 

621 

622 def to_save(self, val): 

623 return val 

624 

625 def to_save_xml(self, val): 

626 return self.to_save(val) 

627 

628 def extend_xmlelements(self, elems, v): 

629 if self.multivalued: 

630 for x in v: 

631 elems.append((self.content_t.effective_xmltagname, x)) 

632 else: 

633 elems.append((self.effective_xmltagname, v)) 

634 

635 def deferred(self): 

636 return [] 

637 

638 def classname_for_help(self, strip_module=''): 

639 

640 if self._dummy_cls is not self._cls: 

641 if self._dummy_cls.__module__ == strip_module: 

642 sadd = ' (:py:class:`%s`)' % ( 

643 self._dummy_cls.__name__) 

644 else: 

645 sadd = ' (:py:class:`%s.%s`)' % ( 

646 self._dummy_cls.__module__, self._dummy_cls.__name__) 

647 else: 

648 sadd = '' 

649 

650 if self._dummy_cls in guts_plain_dummy_types: 

651 return '``%s``' % self._cls.__name__ 

652 

653 elif self._dummy_cls.dummy_for_description: 

654 return '%s%s' % (self._dummy_cls.dummy_for_description, sadd) 

655 

656 else: 

657 def sclass(cls): 

658 mod = cls.__module__ 

659 clsn = cls.__name__ 

660 if mod == '__builtin__' or mod == 'builtins': 

661 return '``%s``' % clsn 

662 

663 elif mod == strip_module: 

664 return ':py:class:`%s`' % clsn 

665 

666 else: 

667 return ':py:class:`%s.%s`' % (mod, clsn) 

668 

669 if isinstance(self._cls, tuple): 

670 return '(%s)%s' % ( 

671 ' | '.join(sclass(cls) for cls in self._cls), sadd) 

672 else: 

673 return '%s%s' % (sclass(self._cls), sadd) 

674 

675 @classmethod 

676 def props_help_string(cls): 

677 baseprops = [] 

678 for base in cls._dummy_cls.__bases__: 

679 if hasattr(base, 'T'): 

680 baseprops.extend(base.T.properties) 

681 

682 hlp = [] 

683 hlp.append('') 

684 for prop in cls.properties: 

685 if prop in baseprops: 

686 continue 

687 

688 descr = [ 

689 prop.classname_for_help( 

690 strip_module=cls._dummy_cls.__module__)] 

691 

692 if prop.optional: 

693 descr.append('*optional*') 

694 

695 if isinstance(prop._default, DefaultMaker): 

696 descr.append('*default:* ``%s``' % repr(prop._default)) 

697 else: 

698 d = prop.default() 

699 if d is not None: 

700 descr.append('*default:* ``%s``' % repr(d)) 

701 

702 hlp.append(' .. py:gattribute:: %s' % prop.name) 

703 hlp.append('') 

704 hlp.append(' %s' % ', '.join(descr)) 

705 hlp.append(' ') 

706 if prop.help is not None: 

707 hlp.append(' %s' % prop.help) 

708 hlp.append('') 

709 

710 return '\n'.join(hlp) 

711 

712 @classmethod 

713 def class_help_string(cls): 

714 return cls._dummy_cls.__doc_template__ 

715 

716 @classmethod 

717 def class_signature(cls): 

718 r = [] 

719 for prop in cls.properties: 

720 d = prop.default() 

721 if d is not None: 

722 arg = repr(d) 

723 

724 elif prop.optional: 

725 arg = 'None' 

726 

727 else: 

728 arg = '...' 

729 

730 r.append('%s=%s' % (prop.name, arg)) 

731 

732 return '(%s)' % ', '.join(r) 

733 

734 @classmethod 

735 def help(cls): 

736 return cls.props_help_string() 

737 

738 

739class ObjectMetaClass(type): 

740 def __new__(meta, classname, bases, class_dict): 

741 classname = class_dict.get('class_name', classname) 

742 cls = type.__new__(meta, classname, bases, class_dict) 

743 if classname != 'Object': 

744 t_class_attr_name = '_%s__T' % classname 

745 if not hasattr(cls, t_class_attr_name): 

746 if hasattr(cls, 'T'): 

747 class T(cls.T): 

748 _sphinx_doc_skip = True 

749 

750 T.__doc__ = cls.T.__doc__ 

751 else: 

752 class T(TBase): 

753 _sphinx_doc_skip = True 

754 

755 T.__doc__ = TBase.__doc__ 

756 

757 setattr(cls, t_class_attr_name, T) 

758 

759 T = getattr(cls, t_class_attr_name) 

760 T.__name__ = 'T' 

761 T.__qualname__ = T.__qualname__.replace('__T', 'T') 

762 

763 if cls.dummy_for is not None: 

764 T._cls = cls.dummy_for 

765 else: 

766 T._cls = cls 

767 

768 T._dummy_cls = cls 

769 

770 if hasattr(cls, 'xmltagname'): 

771 T.xmltagname = cls.xmltagname 

772 else: 

773 T.xmltagname = classname 

774 

775 mod = sys.modules[cls.__module__] 

776 

777 if hasattr(cls, 'xmlns'): 

778 T.xmlns = cls.xmlns 

779 elif hasattr(mod, 'guts_xmlns'): 

780 T.xmlns = mod.guts_xmlns 

781 else: 

782 T.xmlns = '' 

783 

784 if T.xmlns and hasattr(cls, 'guessable_xmlns'): 

785 g_guessable_xmlns[T.xmltagname] = cls.guessable_xmlns 

786 

787 if hasattr(mod, 'guts_prefix'): 

788 if mod.guts_prefix: 

789 T.tagname = mod.guts_prefix + '.' + classname 

790 else: 

791 T.tagname = classname 

792 else: 

793 if cls.__module__ != '__main__': 

794 T.tagname = cls.__module__ + '.' + classname 

795 else: 

796 T.tagname = classname 

797 

798 T.classname = classname 

799 

800 T.init_propertystuff() 

801 

802 for k in dir(cls): 

803 prop = getattr(cls, k) 

804 

805 if k.endswith('__'): 

806 k = k[:-2] 

807 

808 if isinstance(prop, TBase): 

809 if prop.deferred(): 

810 for defer in prop.deferred(): 

811 g_deferred_content.setdefault( 

812 defer.classname[:-2], []).append((prop, defer)) 

813 g_deferred.setdefault( 

814 defer.classname[:-2], []).append((T, k, prop)) 

815 

816 else: 

817 T.add_property(k, prop) 

818 

819 elif isinstance(prop, Defer): 

820 g_deferred.setdefault(prop.classname[:-2], []).append( 

821 (T, k, prop)) 

822 

823 if classname in g_deferred_content: 

824 for prop, defer in g_deferred_content[classname]: 

825 prop.process_deferred( 

826 defer, T(*defer.args, **defer.kwargs)) 

827 

828 del g_deferred_content[classname] 

829 

830 if classname in g_deferred: 

831 for (T_, k_, prop_) in g_deferred.get(classname, []): 

832 if isinstance(prop_, Defer): 

833 prop_ = T(*prop_.args, **prop_.kwargs) 

834 

835 if not prop_.deferred(): 

836 T_.add_property(k_, prop_) 

837 

838 del g_deferred[classname] 

839 

840 g_tagname_to_class[T.tagname] = cls 

841 if hasattr(cls, 'xmltagname'): 

842 g_xmltagname_to_class[T.xmlns + ' ' + T.xmltagname] = cls 

843 

844 cls.T = T 

845 T.instance = T() 

846 

847 cls.__doc_template__ = cls.__doc__ 

848 cls.__doc__ = T.class_help_string() 

849 

850 if cls.__doc__ is None: 

851 cls.__doc__ = 'Undocumented.' 

852 

853 cls.__doc__ += '\n' + T.props_help_string() 

854 

855 return cls 

856 

857 

858class ValidationError(ValueError): 

859 ''' 

860 Raised when an object is invalid according to its definition. 

861 ''' 

862 pass 

863 

864 

865class ArgumentError(ValueError): 

866 ''' 

867 Raised when invalid arguments would be used in an object's initialization. 

868 ''' 

869 pass 

870 

871 

872def make_default(x): 

873 if isinstance(x, DefaultMaker): 

874 return x.make() 

875 elif isinstance(x, Object): 

876 return clone(x) 

877 else: 

878 return x 

879 

880 

881class DefaultMaker(object): 

882 ''' 

883 Base class for default value factories. 

884 ''' 

885 def make(self): 

886 ''' 

887 Create a new object. 

888 ''' 

889 raise NotImplementedError 

890 

891 

892class ObjectDefaultMaker(DefaultMaker): 

893 ''' 

894 Default value factory for :py:class:`Object` derived classes. 

895 ''' 

896 def __init__(self, cls, args, kwargs): 

897 DefaultMaker.__init__(self) 

898 self._cls = cls 

899 self.args = args 

900 self.kwargs = kwargs 

901 self.instance = None 

902 

903 def make(self): 

904 return self._cls( 

905 *[make_default(x) for x in self.args], 

906 **dict((k, make_default(v)) for (k, v) in self.kwargs.items())) 

907 

908 def __eq__(self, other): 

909 if self.instance is None: 

910 self.instance = self.make() 

911 

912 return self.instance == other 

913 

914 def __repr__(self): 

915 sargs = [] 

916 for arg in self.args: 

917 sargs.append(repr(arg)) 

918 

919 for k, v in self.kwargs.items(): 

920 sargs.append( 

921 '%s=%s' % ( 

922 k, 

923 ' '.join(line.strip() for line in repr(v).splitlines()))) 

924 

925 return '%s(%s)' % (self._cls.__name__, ', '.join(sargs)) 

926 

927 

928class TimestampDefaultMaker(DefaultMaker): 

929 def __init__(self, s, format='%Y-%m-%d %H:%M:%S.OPTFRAC'): 

930 DefaultMaker.__init__(self) 

931 self._stime = s 

932 self._format = format 

933 

934 def make(self): 

935 return str_to_time(self._stime, self._format) 

936 

937 def __repr__(self): 

938 return 'str_to_time(%s)' % repr(self._stime) 

939 

940 

941def with_metaclass(meta, *bases): 

942 # inlined py2/py3 compat solution from python-future 

943 class metaclass(meta): 

944 __call__ = type.__call__ 

945 __init__ = type.__init__ 

946 

947 def __new__(cls, name, this_bases, d): 

948 if this_bases is None: 

949 return type.__new__(cls, name, (), d) 

950 return meta(name, bases, d) 

951 

952 return metaclass('temp', None, {}) 

953 

954 

955class Object(with_metaclass(ObjectMetaClass, object)): 

956 ''' 

957 Base class for Guts objects. 

958 

959 :cvar dummy_for: 

960 (class variable) If set, this indicates that the containing class is a 

961 dummy for another type. This can be used to hold native Python objects 

962 and non-Guts based objects as children of a Guts object. 

963 :cvar dummy_for_description: 

964 (class variable) Overrides the name shown in the "dummy for ..." 

965 documentation strings. 

966 ''' 

967 

968 dummy_for = None 

969 dummy_for_description = None 

970 

971 def __init__(self, **kwargs): 

972 if not kwargs.get('init_props', True): 

973 return 

974 

975 for prop in self.T.properties: 

976 k = prop.name 

977 if k in kwargs: 

978 setattr(self, k, kwargs.pop(k)) 

979 else: 

980 if not prop.optional and not prop.has_default(): 

981 raise ArgumentError('Missing argument to %s: %s' % ( 

982 self.T.tagname, prop.name)) 

983 else: 

984 setattr(self, k, prop.default()) 

985 

986 if kwargs: 

987 raise ArgumentError('Invalid argument to %s: %s' % ( 

988 self.T.tagname, ', '.join(list(kwargs.keys())))) 

989 

990 self.post_init() 

991 

992 def post_init(self): 

993 pass 

994 

995 @classmethod 

996 def D(cls, *args, **kwargs): 

997 ''' 

998 Get a default value factory for this class, configured with 

999 specified arguments. 

1000 

1001 :returns: 

1002 Factory for default values. 

1003 :rtype: 

1004 :py:class:`ObjectDefaultMaker` object 

1005 ''' 

1006 return ObjectDefaultMaker(cls, args, kwargs) 

1007 

1008 def validate(self, regularize=False, depth=-1): 

1009 ''' 

1010 Validate this object. 

1011 

1012 Raises :py:class:`ValidationError` when the object is invalid. 

1013 

1014 :param depth: 

1015 Maximum depth to descend into child objects. 

1016 :type depth: 

1017 int 

1018 ''' 

1019 self.T.instance.validate(self, regularize, depth) 

1020 

1021 def regularize(self, depth=-1): 

1022 ''' 

1023 Regularize this object. 

1024 

1025 Regularization tries to convert child objects of invalid types to the 

1026 expected types. 

1027 

1028 Raises :py:class:`ValidationError` when the object is invalid and 

1029 cannot be regularized. 

1030 

1031 :param depth: 

1032 Maximum depth to descend into child objects. 

1033 :type depth: 

1034 int 

1035 ''' 

1036 self.validate(regularize=True, depth=depth) 

1037 

1038 def dump(self, stream=None, filename=None, header=False): 

1039 ''' 

1040 Serialize to YAML. 

1041 

1042 If neither ``stream`` nor ``filename`` is set, a string containing the 

1043 serialized data is returned. 

1044 

1045 :param stream: 

1046 Output to stream. 

1047 

1048 :param filename: 

1049 Output to file of given name. 

1050 :type filename: 

1051 str 

1052 

1053 :param header: 

1054 File header to prepend to the output. 

1055 :type header: 

1056 str 

1057 ''' 

1058 return dump(self, stream=stream, filename=filename, header=header) 

1059 

1060 def dump_xml( 

1061 self, stream=None, filename=None, header=False, ns_ignore=False): 

1062 ''' 

1063 Serialize to XML. 

1064 

1065 If neither ``stream`` nor ``filename`` is set, a string containing the 

1066 serialized data is returned. 

1067 

1068 :param stream: 

1069 Output to stream. 

1070 

1071 :param filename: 

1072 Output to file of given name. 

1073 :type filename: 

1074 str 

1075 

1076 :param header: 

1077 File header to prepend to the output. 

1078 :type header: 

1079 str 

1080 

1081 :param ns_ignore: 

1082 Whether to ignore the XML namespace. 

1083 :type ns_ignore: 

1084 bool 

1085 ''' 

1086 return dump_xml( 

1087 self, stream=stream, filename=filename, header=header, 

1088 ns_ignore=ns_ignore) 

1089 

1090 @classmethod 

1091 def load(cls, stream=None, filename=None, string=None): 

1092 ''' 

1093 Deserialize from YAML. 

1094 

1095 :param stream: 

1096 Read input from stream. 

1097 

1098 :param filename: 

1099 Read input from file of given name. 

1100 :type filename: 

1101 str 

1102 

1103 :param string: 

1104 Read input from string. 

1105 :type string: 

1106 str 

1107 ''' 

1108 return load(stream=stream, filename=filename, string=string) 

1109 

1110 @classmethod 

1111 def load_xml(cls, stream=None, filename=None, string=None, ns_hints=None, 

1112 ns_ignore=False): 

1113 ''' 

1114 Deserialize from XML. 

1115 

1116 :param stream: 

1117 Read input from stream. 

1118 

1119 :param filename: 

1120 Read input from file of given name. 

1121 :type filename: 

1122 str 

1123 

1124 :param string: 

1125 Read input from string. 

1126 :type string: 

1127 str 

1128 ''' 

1129 

1130 if ns_hints is None: 

1131 ns_hints = [cls.T.instance.get_xmlns()] 

1132 

1133 return load_xml( 

1134 stream=stream, 

1135 filename=filename, 

1136 string=string, 

1137 ns_hints=ns_hints, 

1138 ns_ignore=ns_ignore) 

1139 

1140 def __str__(self): 

1141 return self.dump() 

1142 

1143 

1144def to_dict(obj): 

1145 ''' 

1146 Get dict of guts object attributes. 

1147 

1148 :param obj: :py:class`Object` object 

1149 ''' 

1150 

1151 return dict(obj.T.inamevals(obj)) 

1152 

1153 

1154class SObject(Object): 

1155 ''' 

1156 Base class for simple str-serializable Guts objects. 

1157 

1158 Derived classes must support (de)serialization as in ``X(str(x))``. 

1159 ''' 

1160 

1161 class __T(TBase): 

1162 def regularize_extra(self, val): 

1163 if isinstance(val, str): 

1164 return self._cls(val) 

1165 

1166 return val 

1167 

1168 def to_save(self, val): 

1169 return str(val) 

1170 

1171 def to_save_xml(self, val): 

1172 return str(val) 

1173 

1174 

1175class Any(Object): 

1176 ''' 

1177 Placeholder for any object. 

1178 ''' 

1179 

1180 class __T(TBase): 

1181 def validate(self, val, regularize=False, depth=-1): 

1182 if isinstance(val, Object): 

1183 val.validate(regularize, depth) 

1184 

1185 return val 

1186 

1187 

1188class Int(Object): 

1189 ''' 

1190 Placeholder for :py:class:`int`. 

1191 ''' 

1192 dummy_for = int 

1193 

1194 class __T(TBase): 

1195 strict = True 

1196 

1197 def to_save_xml(self, value): 

1198 return repr(value) 

1199 

1200 

1201class Float(Object): 

1202 ''' 

1203 Placeholder for :py:class:`float`. 

1204 ''' 

1205 dummy_for = float 

1206 

1207 class __T(TBase): 

1208 strict = True 

1209 

1210 def to_save_xml(self, value): 

1211 return repr(value) 

1212 

1213 

1214class Complex(Object): 

1215 ''' 

1216 Placeholder for :py:class:`complex`. 

1217 ''' 

1218 dummy_for = complex 

1219 

1220 class __T(TBase): 

1221 strict = True 

1222 

1223 def regularize_extra(self, val): 

1224 

1225 if isinstance(val, list) or isinstance(val, tuple): 

1226 assert len(val) == 2 

1227 val = complex(*val) 

1228 

1229 elif not isinstance(val, complex): 

1230 val = complex(val) 

1231 

1232 return val 

1233 

1234 def to_save(self, value): 

1235 return repr(complex(value)) 

1236 

1237 def to_save_xml(self, value): 

1238 return repr(complex(value)) 

1239 

1240 

1241class Bool(Object): 

1242 ''' 

1243 Placeholder for :py:class:`bool`. 

1244 ''' 

1245 dummy_for = bool 

1246 

1247 class __T(TBase): 

1248 strict = True 

1249 

1250 def regularize_extra(self, val): 

1251 if isinstance(val, str): 

1252 if val.lower().strip() in ('0', 'false'): 

1253 return False 

1254 

1255 return bool(val) 

1256 

1257 def to_save_xml(self, value): 

1258 return repr(bool(value)).lower() 

1259 

1260 

1261class String(Object): 

1262 ''' 

1263 Placeholder for :py:class:`str`. 

1264 ''' 

1265 dummy_for = str 

1266 

1267 class __T(TBase): 

1268 def __init__(self, *args, yamlstyle=None, **kwargs): 

1269 TBase.__init__(self, *args, **kwargs) 

1270 self.style_cls = str_style_map[yamlstyle] 

1271 

1272 def to_save(self, val): 

1273 return self.style_cls(val) 

1274 

1275 

1276class Bytes(Object): 

1277 ''' 

1278 Placeholder for :py:class:`bytes`. 

1279 ''' 

1280 dummy_for = bytes 

1281 

1282 class __T(TBase): 

1283 

1284 def regularize_extra(self, val): 

1285 if isinstance(val, str): 

1286 val = b64decode(val) 

1287 

1288 return val 

1289 

1290 def to_save(self, val): 

1291 return literal(b64encode(val).decode('utf-8')) 

1292 

1293 

1294class Unicode(Object): 

1295 ''' 

1296 Placeholder for :py:class:`str`. 

1297 ''' 

1298 dummy_for = str 

1299 

1300 class __T(TBase): 

1301 def __init__(self, *args, yamlstyle=None, **kwargs): 

1302 TBase.__init__(self, *args, **kwargs) 

1303 self.style_cls = unicode_style_map[yamlstyle] 

1304 

1305 def to_save(self, val): 

1306 return self.style_cls(val) 

1307 

1308 

1309guts_plain_dummy_types = (String, Unicode, Int, Float, Complex, Bool) 

1310 

1311 

1312class Dict(Object): 

1313 ''' 

1314 Placeholder for :py:class:`dict`. 

1315 ''' 

1316 dummy_for = dict 

1317 

1318 class __T(TBase): 

1319 multivalued = dict 

1320 

1321 def __init__(self, key_t=Any.T(), content_t=Any.T(), *args, **kwargs): 

1322 TBase.__init__(self, *args, **kwargs) 

1323 assert isinstance(key_t, TBase) 

1324 assert isinstance(content_t, TBase) 

1325 self.key_t = key_t 

1326 self.content_t = content_t 

1327 self.content_t.parent = self 

1328 

1329 def default(self): 

1330 if self._default is not None: 

1331 return dict( 

1332 (make_default(k), make_default(v)) 

1333 for (k, v) in self._default.items()) 

1334 

1335 if self.optional: 

1336 return None 

1337 else: 

1338 return {} 

1339 

1340 def has_default(self): 

1341 return True 

1342 

1343 def validate(self, val, regularize, depth): 

1344 return TBase.validate(self, val, regularize, depth+1) 

1345 

1346 def validate_children(self, val, regularize, depth): 

1347 for key, ele in list(val.items()): 

1348 newkey = self.key_t.validate(key, regularize, depth-1) 

1349 newele = self.content_t.validate(ele, regularize, depth-1) 

1350 if regularize: 

1351 if newkey is not key or newele is not ele: 

1352 del val[key] 

1353 val[newkey] = newele 

1354 

1355 return val 

1356 

1357 def to_save(self, val): 

1358 return dict((self.key_t.to_save(k), self.content_t.to_save(v)) 

1359 for (k, v) in val.items()) 

1360 

1361 def to_save_xml(self, val): 

1362 raise NotImplementedError 

1363 

1364 def classname_for_help(self, strip_module=''): 

1365 return '``dict`` of %s objects' % \ 

1366 self.content_t.classname_for_help(strip_module=strip_module) 

1367 

1368 

1369class List(Object): 

1370 ''' 

1371 Placeholder for :py:class:`list`. 

1372 ''' 

1373 dummy_for = list 

1374 

1375 class __T(TBase): 

1376 multivalued = list 

1377 

1378 def __init__(self, content_t=Any.T(), *args, yamlstyle=None, **kwargs): 

1379 TBase.__init__(self, *args, **kwargs) 

1380 assert isinstance(content_t, TBase) or isinstance(content_t, Defer) 

1381 self.content_t = content_t 

1382 self.content_t.parent = self 

1383 self.style_cls = list_style_map[yamlstyle] 

1384 

1385 def default(self): 

1386 if self._default is not None: 

1387 return [make_default(x) for x in self._default] 

1388 if self.optional: 

1389 return None 

1390 else: 

1391 return [] 

1392 

1393 def has_default(self): 

1394 return True 

1395 

1396 def validate(self, val, regularize, depth): 

1397 return TBase.validate(self, val, regularize, depth+1) 

1398 

1399 def validate_children(self, val, regularize, depth): 

1400 for i, ele in enumerate(val): 

1401 newele = self.content_t.validate(ele, regularize, depth-1) 

1402 if regularize and newele is not ele: 

1403 val[i] = newele 

1404 

1405 return val 

1406 

1407 def to_save(self, val): 

1408 return self.style_cls(self.content_t.to_save(v) for v in val) 

1409 

1410 def to_save_xml(self, val): 

1411 return [self.content_t.to_save_xml(v) for v in val] 

1412 

1413 def deferred(self): 

1414 if isinstance(self.content_t, Defer): 

1415 return [self.content_t] 

1416 

1417 return [] 

1418 

1419 def process_deferred(self, defer, t_inst): 

1420 if defer is self.content_t: 

1421 self.content_t = t_inst 

1422 

1423 def classname_for_help(self, strip_module=''): 

1424 return '``list`` of %s objects' % \ 

1425 self.content_t.classname_for_help(strip_module=strip_module) 

1426 

1427 

1428def make_typed_list_class(t): 

1429 class TL(List): 

1430 class __T(List.T): 

1431 def __init__(self, *args, **kwargs): 

1432 List.T.__init__(self, content_t=t.T(), *args, **kwargs) 

1433 

1434 return TL 

1435 

1436 

1437class Tuple(Object): 

1438 ''' 

1439 Placeholder for :py:class:`tuple`. 

1440 ''' 

1441 dummy_for = tuple 

1442 

1443 class __T(TBase): 

1444 multivalued = tuple 

1445 

1446 def __init__(self, n=None, content_t=Any.T(), *args, **kwargs): 

1447 TBase.__init__(self, *args, **kwargs) 

1448 assert isinstance(content_t, TBase) 

1449 self.content_t = content_t 

1450 self.content_t.parent = self 

1451 self.n = n 

1452 

1453 def default(self): 

1454 if self._default is not None: 

1455 return tuple( 

1456 make_default(x) for x in self._default) 

1457 

1458 elif self.optional: 

1459 return None 

1460 else: 

1461 if self.n is not None: 

1462 return tuple( 

1463 self.content_t.default() for x in range(self.n)) 

1464 else: 

1465 return tuple() 

1466 

1467 def has_default(self): 

1468 return True 

1469 

1470 def validate(self, val, regularize, depth): 

1471 return TBase.validate(self, val, regularize, depth+1) 

1472 

1473 def validate_extra(self, val): 

1474 if self.n is not None and len(val) != self.n: 

1475 raise ValidationError( 

1476 '%s should have length %i' % (self.xname(), self.n)) 

1477 

1478 def validate_children(self, val, regularize, depth): 

1479 if not regularize: 

1480 for ele in val: 

1481 self.content_t.validate(ele, regularize, depth-1) 

1482 

1483 return val 

1484 else: 

1485 newval = [] 

1486 isnew = False 

1487 for ele in val: 

1488 newele = self.content_t.validate(ele, regularize, depth-1) 

1489 newval.append(newele) 

1490 if newele is not ele: 

1491 isnew = True 

1492 

1493 if isnew: 

1494 return tuple(newval) 

1495 else: 

1496 return val 

1497 

1498 def to_save(self, val): 

1499 return tuple(self.content_t.to_save(v) for v in val) 

1500 

1501 def to_save_xml(self, val): 

1502 return [self.content_t.to_save_xml(v) for v in val] 

1503 

1504 def classname_for_help(self, strip_module=''): 

1505 if self.n is not None: 

1506 return '``tuple`` of %i %s objects' % ( 

1507 self.n, self.content_t.classname_for_help( 

1508 strip_module=strip_module)) 

1509 else: 

1510 return '``tuple`` of %s objects' % ( 

1511 self.content_t.classname_for_help( 

1512 strip_module=strip_module)) 

1513 

1514 

1515duration_unit_factors = dict( 

1516 s=1.0, 

1517 m=60.0, 

1518 h=3600.0, 

1519 d=24*3600.0, 

1520 y=365*24*3600.0) 

1521 

1522 

1523def parse_duration(s): 

1524 unit = s[-1] 

1525 if unit in duration_unit_factors: 

1526 return float(s[:-1]) * duration_unit_factors[unit] 

1527 else: 

1528 return float(s) 

1529 

1530 

1531def str_duration(d): 

1532 for k in 'ydhms': 

1533 if abs(d) >= duration_unit_factors[k]: 

1534 return '%g' % (d / duration_unit_factors[k]) + k 

1535 

1536 return '%g' % d 

1537 

1538 

1539class Duration(Object): 

1540 ''' 

1541 Placeholder for :py:class:`float` time duration [s] with human-readable 

1542 (de)serialization. 

1543 

1544 Examples: 

1545 

1546 - ``'1s'`` -> 1 second 

1547 - ``'1m'`` -> 1 minute 

1548 - ``'1h'`` -> 1 hour 

1549 - ``'1d'`` -> 1 day 

1550 - ``'1y'`` -> about 1 year = 365*24*3600 seconds 

1551 ''' 

1552 dummy_for = float 

1553 

1554 class __T(TBase): 

1555 def regularize_extra(self, val): 

1556 if isinstance(val, str): 

1557 return parse_duration(val) 

1558 

1559 return val 

1560 

1561 def to_save(self, val): 

1562 return str_duration(val) 

1563 

1564 def to_save_xml(self, val): 

1565 return str_duration(val) 

1566 

1567 

1568re_tz = re.compile(r'(Z|([+-][0-2][0-9])(:?([0-5][0-9]))?)$') 

1569 

1570 

1571class Timestamp(Object): 

1572 ''' 

1573 Placeholder for a UTC timestamp. 

1574 ''' 

1575 dummy_for = (hpfloat, float) 

1576 dummy_for_description = 'pyrocko.util.get_time_float' 

1577 

1578 class __T(TBase): 

1579 

1580 def regularize_extra(self, val): 

1581 

1582 time_float = get_time_float() 

1583 

1584 if isinstance(val, datetime.datetime): 

1585 tt = val.utctimetuple() 

1586 val = time_float(calendar.timegm(tt)) + val.microsecond * 1e-6 

1587 

1588 elif isinstance(val, datetime.date): 

1589 tt = val.timetuple() 

1590 val = time_float(calendar.timegm(tt)) 

1591 

1592 elif isinstance(val, str): 

1593 val = val.strip() 

1594 tz_offset = 0 

1595 

1596 m = re_tz.search(val) 

1597 if m: 

1598 sh = m.group(2) 

1599 sm = m.group(4) 

1600 tz_offset = (int(sh)*3600 if sh else 0) \ 

1601 + (int(sm)*60 if sm else 0) 

1602 

1603 val = re_tz.sub('', val) 

1604 

1605 if len(val) > 10 and val[10] == 'T': 

1606 val = val.replace('T', ' ', 1) 

1607 

1608 try: 

1609 val = str_to_time(val) - tz_offset 

1610 except TimeStrError: 

1611 raise ValidationError( 

1612 '%s: cannot parse time/date: %s' % (self.xname(), val)) 

1613 

1614 elif isinstance(val, (int, float)): 

1615 val = time_float(val) 

1616 

1617 else: 

1618 raise ValidationError( 

1619 '%s: cannot convert "%s" to type %s' % ( 

1620 self.xname(), val, time_float)) 

1621 

1622 return val 

1623 

1624 def to_save(self, val): 

1625 return time_to_str(val, format='%Y-%m-%d %H:%M:%S.9FRAC')\ 

1626 .rstrip('0').rstrip('.') 

1627 

1628 def to_save_xml(self, val): 

1629 return time_to_str(val, format='%Y-%m-%dT%H:%M:%S.9FRAC')\ 

1630 .rstrip('0').rstrip('.') + 'Z' 

1631 

1632 @classmethod 

1633 def D(self, s): 

1634 return TimestampDefaultMaker(s) 

1635 

1636 

1637class DateTimestamp(Object): 

1638 ''' 

1639 Placeholder for a UTC timestamp which (de)serializes as a date string. 

1640 ''' 

1641 dummy_for = (hpfloat, float) 

1642 dummy_for_description = 'pyrocko.util.get_time_float' 

1643 

1644 class __T(TBase): 

1645 

1646 def regularize_extra(self, val): 

1647 

1648 time_float = get_time_float() 

1649 

1650 if isinstance(val, datetime.datetime): 

1651 tt = val.utctimetuple() 

1652 val = time_float(calendar.timegm(tt)) + val.microsecond * 1e-6 

1653 

1654 elif isinstance(val, datetime.date): 

1655 tt = val.timetuple() 

1656 val = time_float(calendar.timegm(tt)) 

1657 

1658 elif isinstance(val, str): 

1659 val = str_to_time(val, format='%Y-%m-%d') 

1660 

1661 elif isinstance(val, int): 

1662 val = time_float(val) 

1663 

1664 return val 

1665 

1666 def to_save(self, val): 

1667 return time_to_str(val, format='%Y-%m-%d') 

1668 

1669 def to_save_xml(self, val): 

1670 return time_to_str(val, format='%Y-%m-%d') 

1671 

1672 @classmethod 

1673 def D(self, s): 

1674 return TimestampDefaultMaker(s, format='%Y-%m-%d') 

1675 

1676 

1677class StringPattern(String): 

1678 

1679 ''' 

1680 Any :py:class:`str` matching pattern ``%(pattern)s``. 

1681 ''' 

1682 

1683 dummy_for = str 

1684 pattern = '.*' 

1685 

1686 class __T(String.T): 

1687 def __init__(self, pattern=None, *args, **kwargs): 

1688 String.T.__init__(self, *args, **kwargs) 

1689 

1690 if pattern is not None: 

1691 self.pattern = pattern 

1692 else: 

1693 self.pattern = self._dummy_cls.pattern 

1694 

1695 def validate_extra(self, val): 

1696 pat = self.pattern 

1697 if not re.search(pat, val): 

1698 raise ValidationError('%s: "%s" does not match pattern %s' % ( 

1699 self.xname(), val, repr(pat))) 

1700 

1701 @classmethod 

1702 def class_help_string(cls): 

1703 dcls = cls._dummy_cls 

1704 doc = dcls.__doc_template__ or StringPattern.__doc_template__ 

1705 return doc % {'pattern': repr(dcls.pattern)} 

1706 

1707 

1708class UnicodePattern(Unicode): 

1709 

1710 ''' 

1711 Any :py:class:`str` matching pattern ``%(pattern)s``. 

1712 ''' 

1713 

1714 dummy_for = str 

1715 pattern = '.*' 

1716 

1717 class __T(TBase): 

1718 def __init__(self, pattern=None, *args, **kwargs): 

1719 TBase.__init__(self, *args, **kwargs) 

1720 

1721 if pattern is not None: 

1722 self.pattern = pattern 

1723 else: 

1724 self.pattern = self._dummy_cls.pattern 

1725 

1726 def validate_extra(self, val): 

1727 pat = self.pattern 

1728 if not re.search(pat, val, flags=re.UNICODE): 

1729 raise ValidationError('%s: "%s" does not match pattern %s' % ( 

1730 self.xname(), val, repr(pat))) 

1731 

1732 @classmethod 

1733 def class_help_string(cls): 

1734 dcls = cls._dummy_cls 

1735 doc = dcls.__doc_template__ or UnicodePattern.__doc_template__ 

1736 return doc % {'pattern': repr(dcls.pattern)} 

1737 

1738 

1739class StringChoice(String): 

1740 

1741 ''' 

1742 Any :py:class:`str` out of ``%(choices)s``. 

1743 

1744 :cvar choices: 

1745 Allowed choices (:py:class:`list` of :py:class:`str`). 

1746 :cvar ignore_case: 

1747 Whether to behave case-insensitive (:py:class:`bool`, default: 

1748 ``False``). 

1749 ''' 

1750 

1751 dummy_for = str 

1752 choices = [] 

1753 ignore_case = False 

1754 

1755 class __T(String.T): 

1756 def __init__(self, choices=None, ignore_case=None, *args, **kwargs): 

1757 String.T.__init__(self, *args, **kwargs) 

1758 

1759 if choices is not None: 

1760 self.choices = choices 

1761 else: 

1762 self.choices = self._dummy_cls.choices 

1763 

1764 if ignore_case is not None: 

1765 self.ignore_case = ignore_case 

1766 else: 

1767 self.ignore_case = self._dummy_cls.ignore_case 

1768 

1769 if self.ignore_case: 

1770 self.choices = [x.upper() for x in self.choices] 

1771 

1772 def validate_extra(self, val): 

1773 if self.ignore_case: 

1774 val = val.upper() 

1775 

1776 if val not in self.choices: 

1777 raise ValidationError( 

1778 '%s: "%s" is not a valid choice out of %s' % ( 

1779 self.xname(), val, repr(self.choices))) 

1780 

1781 @classmethod 

1782 def class_help_string(cls): 

1783 dcls = cls._dummy_cls 

1784 doc = dcls.__doc_template__ or StringChoice.__doc_template__ 

1785 return doc % {'choices': repr(dcls.choices)} 

1786 

1787 

1788class IntChoice(Int): 

1789 

1790 ''' 

1791 Any :py:class:`int` out of ``%(choices)s``. 

1792 ''' 

1793 

1794 dummy_for = int 

1795 choices = [] 

1796 

1797 class __T(Int.T): 

1798 def __init__(self, choices=None, *args, **kwargs): 

1799 Int.T.__init__(self, *args, **kwargs) 

1800 

1801 if choices is not None: 

1802 self.choices = choices 

1803 else: 

1804 self.choices = self._dummy_cls.choices 

1805 

1806 def validate_extra(self, val): 

1807 if val not in self.choices: 

1808 raise ValidationError( 

1809 '%s: %i is not a valid choice out of %s' % ( 

1810 self.xname(), val, repr(self.choices))) 

1811 

1812 @classmethod 

1813 def class_help_string(cls): 

1814 dcls = cls._dummy_cls 

1815 doc = dcls.__doc_template__ or IntChoice.__doc_template__ 

1816 return doc % {'choices': repr(dcls.choices)} 

1817 

1818 

1819# this will not always work... 

1820class StringUnion(Object): 

1821 ''' 

1822 Any :py:class:`str` matching any of a set of constraints. 

1823 

1824 :cvar members: 

1825 List of constraints, e.g. :py:class:`StringChoice`, 

1826 :py:class:`StringPattern`, ... (:py:class:`list` of :py:class:`TBase` 

1827 derived objects). 

1828 

1829 ''' 

1830 

1831 members = [] 

1832 

1833 dummy_for = str 

1834 

1835 class __T(TBase): 

1836 def __init__(self, members=None, *args, **kwargs): 

1837 TBase.__init__(self, *args, **kwargs) 

1838 if members is not None: 

1839 self.members = members 

1840 else: 

1841 self.members = self._dummy_cls.members 

1842 

1843 def validate(self, val, regularize=False, depth=-1): 

1844 assert self.members 

1845 e2 = None 

1846 for member in self.members: 

1847 try: 

1848 return member.validate(val, regularize, depth=depth) 

1849 except ValidationError as e: 

1850 e2 = e 

1851 

1852 raise e2 

1853 

1854 

1855class Choice(Object): 

1856 ''' 

1857 Any out of a set of different types. 

1858 

1859 :cvar choices: 

1860 Allowed types (:py:class:`list` of :py:class:`TBase` derived objects). 

1861 

1862 ''' 

1863 choices = [] 

1864 

1865 class __T(TBase): 

1866 def __init__(self, choices=None, *args, **kwargs): 

1867 TBase.__init__(self, *args, **kwargs) 

1868 if choices is not None: 

1869 self.choices = choices 

1870 else: 

1871 self.choices = self._dummy_cls.choices 

1872 

1873 self.cls_to_xmltagname = dict( 

1874 (t._cls, t.get_xmltagname()) for t in self.choices) 

1875 

1876 def validate(self, val, regularize=False, depth=-1): 

1877 if self.optional and val is None: 

1878 return val 

1879 

1880 t = None 

1881 for tc in self.choices: 

1882 is_derived = isinstance(val, tc._cls) 

1883 is_exact = type(val) is tc._cls 

1884 if not (not tc.strict and not is_derived or 

1885 tc.strict and not is_exact): 

1886 

1887 t = tc 

1888 break 

1889 

1890 if t is None: 

1891 if regularize: 

1892 ok = False 

1893 for tc in self.choices: 

1894 try: 

1895 val = tc.regularize_extra(val) 

1896 ok = True 

1897 t = tc 

1898 break 

1899 except (ValidationError, ValueError): 

1900 pass 

1901 

1902 if not ok: 

1903 raise ValidationError( 

1904 '%s: could not convert "%s" to any type out of ' 

1905 '(%s)' % (self.xname(), val, ','.join( 

1906 classnames(x._cls) for x in self.choices))) 

1907 else: 

1908 raise ValidationError( 

1909 '%s: "%s" (type: %s) is not of any type out of ' 

1910 '(%s)' % (self.xname(), val, type(val), ','.join( 

1911 classnames(x._cls) for x in self.choices))) 

1912 

1913 validator = t 

1914 

1915 if isinstance(t._cls, tuple): 

1916 clss = t._cls 

1917 else: 

1918 clss = (t._cls,) 

1919 

1920 for cls in clss: 

1921 try: 

1922 if type(val) is not cls and isinstance(val, cls): 

1923 validator = val.T.instance 

1924 

1925 except AttributeError: 

1926 pass 

1927 

1928 validator.validate_extra(val) 

1929 

1930 if depth != 0: 

1931 val = validator.validate_children(val, regularize, depth) 

1932 

1933 return val 

1934 

1935 def extend_xmlelements(self, elems, v): 

1936 elems.append(( 

1937 self.cls_to_xmltagname[type(v)].split(' ', 1)[-1], v)) 

1938 

1939 

1940def _dump( 

1941 object, stream, 

1942 header=False, 

1943 Dumper=GutsSafeDumper, 

1944 _dump_function=yaml.dump): 

1945 

1946 if not getattr(stream, 'encoding', None): 

1947 enc = encode_utf8 

1948 else: 

1949 enc = no_encode 

1950 

1951 if header: 

1952 stream.write(enc(u'%YAML 1.1\n')) 

1953 if isinstance(header, str): 

1954 banner = u'\n'.join('# ' + x for x in header.splitlines()) + '\n' 

1955 stream.write(enc(banner)) 

1956 

1957 _dump_function( 

1958 object, 

1959 stream=stream, 

1960 encoding='utf-8', 

1961 explicit_start=True, 

1962 Dumper=Dumper) 

1963 

1964 

1965def _dump_all(object, stream, header=True, Dumper=GutsSafeDumper): 

1966 _dump(object, stream=stream, header=header, _dump_function=yaml.dump_all) 

1967 

1968 

1969def _load(stream, 

1970 Loader=GutsSafeLoader, allow_include=None, filename=None, 

1971 included_files=None): 

1972 

1973 class _Loader(Loader): 

1974 _filename = filename 

1975 _allow_include = allow_include 

1976 _included_files = included_files or [] 

1977 

1978 return yaml.load(stream=stream, Loader=_Loader) 

1979 

1980 

1981def _load_all(stream, 

1982 Loader=GutsSafeLoader, allow_include=None, filename=None): 

1983 

1984 class _Loader(Loader): 

1985 _filename = filename 

1986 _allow_include = allow_include 

1987 

1988 return list(yaml.load_all(stream=stream, Loader=_Loader)) 

1989 

1990 

1991def _iload_all(stream, 

1992 Loader=GutsSafeLoader, allow_include=None, filename=None): 

1993 

1994 class _Loader(Loader): 

1995 _filename = filename 

1996 _allow_include = allow_include 

1997 

1998 return yaml.load_all(stream=stream, Loader=_Loader) 

1999 

2000 

2001def multi_representer(dumper, data): 

2002 node = dumper.represent_mapping( 

2003 '!'+data.T.tagname, data.T.inamevals_to_save(data), flow_style=False) 

2004 

2005 return node 

2006 

2007 

2008# hack for compatibility with early GF Store versions 

2009re_compatibility = re.compile( 

2010 r'^pyrocko\.(trace|gf\.(meta|seismosizer)|fomosto\.' 

2011 r'(dummy|poel|qseis|qssp))\.' 

2012) 

2013 

2014 

2015def multi_constructor(loader, tag_suffix, node): 

2016 tagname = str(tag_suffix) 

2017 

2018 tagname = re_compatibility.sub('pf.', tagname) 

2019 

2020 cls = g_tagname_to_class[tagname] 

2021 kwargs = dict(iter(loader.construct_pairs(node, deep=True))) 

2022 o = cls(**kwargs) 

2023 o.validate(regularize=True, depth=1) 

2024 return o 

2025 

2026 

2027def include_constructor(loader, node): 

2028 allow_include = loader._allow_include \ 

2029 if loader._allow_include is not None \ 

2030 else ALLOW_INCLUDE 

2031 

2032 if not allow_include: 

2033 raise EnvironmentError( 

2034 'Not allowed to include YAML. Load with allow_include=True') 

2035 

2036 if isinstance(node, yaml.nodes.ScalarNode): 

2037 inc_file = loader.construct_scalar(node) 

2038 else: 

2039 raise TypeError('Unsupported YAML node %s' % repr(node)) 

2040 

2041 if loader._filename is not None and not op.isabs(inc_file): 

2042 inc_file = op.join(op.dirname(loader._filename), inc_file) 

2043 

2044 if not op.isfile(inc_file): 

2045 raise FileNotFoundError(inc_file) 

2046 

2047 included_files = list(loader._included_files) 

2048 if loader._filename is not None: 

2049 included_files.append(op.abspath(loader._filename)) 

2050 

2051 for included_file in loader._included_files: 

2052 if op.samefile(inc_file, included_file): 

2053 raise ImportError( 

2054 'Circular import of file "%s". Include path: %s' % ( 

2055 op.abspath(inc_file), 

2056 ' -> '.join('"%s"' % s for s in included_files))) 

2057 

2058 with open(inc_file, 'rb') as f: 

2059 return _load( 

2060 f, 

2061 Loader=loader.__class__, filename=inc_file, 

2062 allow_include=True, 

2063 included_files=included_files) 

2064 

2065 

2066def dict_noflow_representer(dumper, data): 

2067 return dumper.represent_mapping( 

2068 'tag:yaml.org,2002:map', data, flow_style=False) 

2069 

2070 

2071yaml.add_multi_representer(Object, multi_representer, Dumper=GutsSafeDumper) 

2072yaml.add_constructor('!include', include_constructor, Loader=GutsSafeLoader) 

2073yaml.add_multi_constructor('!', multi_constructor, Loader=GutsSafeLoader) 

2074yaml.add_representer(dict, dict_noflow_representer, Dumper=GutsSafeDumper) 

2075 

2076 

2077def str_representer(dumper, data): 

2078 return dumper.represent_scalar( 

2079 'tag:yaml.org,2002:str', str(data)) 

2080 

2081 

2082yaml.add_representer(str, str_representer, Dumper=GutsSafeDumper) 

2083 

2084 

2085class Constructor(object): 

2086 def __init__(self, add_namespace_maps=False, strict=False, ns_hints=None, 

2087 ns_ignore=False): 

2088 

2089 self.stack = [] 

2090 self.queue = [] 

2091 self.namespaces = defaultdict(list) 

2092 self.add_namespace_maps = add_namespace_maps 

2093 self.strict = strict 

2094 self.ns_hints = ns_hints 

2095 self.ns_ignore = ns_ignore 

2096 

2097 def start_element(self, ns_name, attrs): 

2098 if self.ns_ignore: 

2099 ns_name = ns_name.split(' ')[-1] 

2100 

2101 if -1 == ns_name.find(' '): 

2102 if self.ns_hints is None and ns_name in g_guessable_xmlns: 

2103 self.ns_hints = g_guessable_xmlns[ns_name] 

2104 

2105 if self.ns_hints: 

2106 ns_names = [ 

2107 ns_hint + ' ' + ns_name for ns_hint in self.ns_hints] 

2108 

2109 elif self.ns_hints is None: 

2110 ns_names = [' ' + ns_name] 

2111 

2112 else: 

2113 ns_names = [ns_name] 

2114 

2115 for ns_name in ns_names: 

2116 if self.stack and self.stack[-1][1] is not None: 

2117 cls = self.stack[-1][1].T.xmltagname_to_class.get( 

2118 ns_name, None) 

2119 

2120 if isinstance(cls, tuple): 

2121 cls = None 

2122 else: 

2123 if cls is not None and ( 

2124 not issubclass(cls, Object) 

2125 or issubclass(cls, SObject)): 

2126 cls = None 

2127 else: 

2128 cls = g_xmltagname_to_class.get(ns_name, None) 

2129 

2130 if cls: 

2131 break 

2132 

2133 self.stack.append((ns_name, cls, attrs, [], [])) 

2134 

2135 def end_element(self, _): 

2136 ns_name, cls, attrs, content2, content1 = self.stack.pop() 

2137 

2138 ns = ns_name.split(' ', 1)[0] 

2139 

2140 if cls is not None: 

2141 content2.extend( 

2142 (ns + ' ' + k if -1 == k.find(' ') else k, v) 

2143 for (k, v) in attrs.items()) 

2144 content2.append((None, ''.join(content1))) 

2145 o = cls(**cls.T.translate_from_xml(content2, self.strict)) 

2146 o.validate(regularize=True, depth=1) 

2147 if self.add_namespace_maps: 

2148 o.namespace_map = self.get_current_namespace_map() 

2149 

2150 if self.stack and not all(x[1] is None for x in self.stack): 

2151 self.stack[-1][-2].append((ns_name, o)) 

2152 else: 

2153 self.queue.append(o) 

2154 else: 

2155 content = [''.join(content1)] 

2156 if self.stack: 

2157 for c in content: 

2158 self.stack[-1][-2].append((ns_name, c)) 

2159 

2160 def characters(self, char_content): 

2161 if self.stack: 

2162 self.stack[-1][-1].append(char_content) 

2163 

2164 def start_namespace(self, ns, uri): 

2165 self.namespaces[ns].append(uri) 

2166 

2167 def end_namespace(self, ns): 

2168 self.namespaces[ns].pop() 

2169 

2170 def get_current_namespace_map(self): 

2171 return dict((k, v[-1]) for (k, v) in self.namespaces.items() if v) 

2172 

2173 def get_queued_elements(self): 

2174 queue = self.queue 

2175 self.queue = [] 

2176 return queue 

2177 

2178 

2179def _iload_all_xml( 

2180 stream, 

2181 bufsize=100000, 

2182 add_namespace_maps=False, 

2183 strict=False, 

2184 ns_hints=None, 

2185 ns_ignore=False): 

2186 

2187 from xml.parsers.expat import ParserCreate 

2188 from pyrocko import progress 

2189 

2190 parser = ParserCreate('UTF-8', namespace_separator=' ') 

2191 

2192 handler = Constructor( 

2193 add_namespace_maps=add_namespace_maps, 

2194 strict=strict, 

2195 ns_hints=ns_hints, 

2196 ns_ignore=ns_ignore) 

2197 

2198 parser.StartElementHandler = handler.start_element 

2199 parser.EndElementHandler = handler.end_element 

2200 parser.CharacterDataHandler = handler.characters 

2201 parser.StartNamespaceDeclHandler = handler.start_namespace 

2202 parser.EndNamespaceDeclHandler = handler.end_namespace 

2203 

2204 try: 

2205 nbytes = os.fstat(stream.fileno()).st_size - stream.tell() 

2206 except Exception: 

2207 nbytes = None 

2208 

2209 ibytes = 0 

2210 task = progress.task('Parsing XML', nbytes, logger=logger) 

2211 try: 

2212 while True: 

2213 data = stream.read(bufsize) 

2214 ibytes += len(data) 

2215 parser.Parse(data, bool(not data)) 

2216 for element in handler.get_queued_elements(): 

2217 yield element 

2218 

2219 task.update(ibytes) 

2220 

2221 if not data: 

2222 break 

2223 

2224 except Exception: 

2225 task.fail() 

2226 raise 

2227 

2228 finally: 

2229 task.done() 

2230 

2231 

2232def _load_all_xml(*args, **kwargs): 

2233 return list(_iload_all_xml(*args, **kwargs)) 

2234 

2235 

2236def _load_xml(*args, **kwargs): 

2237 g = _iload_all_xml(*args, **kwargs) 

2238 return next(g) 

2239 

2240 

2241def _dump_all_xml(objects, stream, root_element_name='root', header=True): 

2242 

2243 if not getattr(stream, 'encoding', None): 

2244 enc = encode_utf8 

2245 else: 

2246 enc = no_encode 

2247 

2248 _dump_xml_header(stream, header) 

2249 

2250 beg = u'<%s>\n' % root_element_name 

2251 end = u'</%s>\n' % root_element_name 

2252 

2253 stream.write(enc(beg)) 

2254 

2255 for ob in objects: 

2256 _dump_xml(ob, stream=stream) 

2257 

2258 stream.write(enc(end)) 

2259 

2260 

2261def _dump_xml_header(stream, banner=None): 

2262 

2263 if not getattr(stream, 'encoding', None): 

2264 enc = encode_utf8 

2265 else: 

2266 enc = no_encode 

2267 

2268 stream.write(enc(u'<?xml version="1.0" encoding="UTF-8" ?>\n')) 

2269 if isinstance(banner, str): 

2270 stream.write(enc(u'<!-- %s -->\n' % banner)) 

2271 

2272 

2273def _dump_xml( 

2274 obj, stream, depth=0, ns_name=None, header=False, ns_map=[], 

2275 ns_ignore=False): 

2276 

2277 from xml.sax.saxutils import escape, quoteattr 

2278 

2279 if not getattr(stream, 'encoding', None): 

2280 enc = encode_utf8 

2281 else: 

2282 enc = no_encode 

2283 

2284 if depth == 0 and header: 

2285 _dump_xml_header(stream, header) 

2286 

2287 indent = ' '*depth*2 

2288 if ns_name is None: 

2289 ns_name = obj.T.instance.get_xmltagname() 

2290 

2291 if -1 != ns_name.find(' '): 

2292 ns, name = ns_name.split(' ') 

2293 else: 

2294 ns, name = '', ns_name 

2295 

2296 if isinstance(obj, Object): 

2297 obj.validate(depth=1) 

2298 attrs = [] 

2299 elems = [] 

2300 

2301 added_ns = False 

2302 if not ns_ignore and ns and (not ns_map or ns_map[-1] != ns): 

2303 attrs.append(('xmlns', ns)) 

2304 ns_map.append(ns) 

2305 added_ns = True 

2306 

2307 for prop, v in obj.T.ipropvals_to_save(obj, xmlmode=True): 

2308 if prop.xmlstyle == 'attribute': 

2309 assert not prop.multivalued 

2310 assert not isinstance(v, Object) 

2311 attrs.append((prop.effective_xmltagname, v)) 

2312 

2313 elif prop.xmlstyle == 'content': 

2314 assert not prop.multivalued 

2315 assert not isinstance(v, Object) 

2316 elems.append((None, v)) 

2317 

2318 else: 

2319 prop.extend_xmlelements(elems, v) 

2320 

2321 attr_str = '' 

2322 if attrs: 

2323 attr_str = ' ' + ' '.join( 

2324 '%s=%s' % (k.split(' ')[-1], quoteattr(str(v))) 

2325 for (k, v) in attrs) 

2326 

2327 if not elems: 

2328 stream.write(enc(u'%s<%s%s />\n' % (indent, name, attr_str))) 

2329 else: 

2330 oneline = len(elems) == 1 and elems[0][0] is None 

2331 stream.write(enc(u'%s<%s%s>%s' % ( 

2332 indent, 

2333 name, 

2334 attr_str, 

2335 '' if oneline else '\n'))) 

2336 

2337 for (k, v) in elems: 

2338 if k is None: 

2339 stream.write(enc(escape(str(v), {'\0': '&#00;'}))) 

2340 else: 

2341 _dump_xml(v, stream, depth+1, k, False, ns_map, ns_ignore) 

2342 

2343 stream.write(enc(u'%s</%s>\n' % ( 

2344 '' if oneline else indent, name))) 

2345 

2346 if added_ns: 

2347 ns_map.pop() 

2348 

2349 else: 

2350 stream.write(enc(u'%s<%s>%s</%s>\n' % ( 

2351 indent, 

2352 name, 

2353 escape(str(obj), {'\0': '&#00;'}), 

2354 name))) 

2355 

2356 

2357def walk(x, typ=None, path=()): 

2358 if typ is None or isinstance(x, typ): 

2359 yield path, x 

2360 

2361 if isinstance(x, Object): 

2362 for (prop, val) in x.T.ipropvals(x): 

2363 if prop.multivalued: 

2364 if val is not None: 

2365 for iele, ele in enumerate(val): 

2366 for y in walk(ele, typ, 

2367 path=path + ((prop.name, iele),)): 

2368 yield y 

2369 else: 

2370 for y in walk(val, typ, path=path+(prop.name,)): 

2371 yield y 

2372 

2373 

2374def clone(x, pool=None): 

2375 ''' 

2376 Clone guts object tree. 

2377 

2378 Traverses guts object tree and recursively clones all guts attributes, 

2379 falling back to :py:func:`copy.deepcopy` for non-guts objects. Objects 

2380 deriving from :py:class:`Object` are instantiated using their respective 

2381 init function. Multiply referenced objects in the source tree are multiply 

2382 referenced also in the destination tree. 

2383 

2384 This function can be used to clone guts objects ignoring any contained 

2385 run-time state, i.e. any of their attributes not defined as a guts 

2386 property. 

2387 ''' 

2388 

2389 if pool is None: 

2390 pool = {} 

2391 

2392 if id(x) in pool: 

2393 x_copy = pool[id(x)][1] 

2394 

2395 else: 

2396 if isinstance(x, SObject): 

2397 x_copy = x.__class__(str(x)) 

2398 elif isinstance(x, Object): 

2399 d = {} 

2400 for (prop, y) in x.T.ipropvals(x): 

2401 if y is not None: 

2402 if not prop.multivalued: 

2403 y_copy = clone(y, pool) 

2404 elif prop.multivalued is dict: 

2405 y_copy = dict( 

2406 (clone(zk, pool), clone(zv, pool)) 

2407 for (zk, zv) in y.items()) 

2408 else: 

2409 y_copy = type(y)(clone(z, pool) for z in y) 

2410 else: 

2411 y_copy = y 

2412 

2413 d[prop.name] = y_copy 

2414 

2415 x_copy = x.__class__(**d) 

2416 

2417 else: 

2418 x_copy = copy.deepcopy(x) 

2419 

2420 pool[id(x)] = (x, x_copy) 

2421 return x_copy 

2422 

2423 

2424class YPathError(Exception): 

2425 ''' 

2426 This exception is raised for invalid ypath specifications. 

2427 ''' 

2428 pass 

2429 

2430 

2431def _parse_yname(yname): 

2432 ident = r'[a-zA-Z][a-zA-Z0-9_]*' 

2433 rint = r'-?[0-9]+' 

2434 m = re.match( 

2435 r'^(%s)(\[((%s)?(:)(%s)?|(%s))\])?$' 

2436 % (ident, rint, rint, rint), yname) 

2437 

2438 if not m: 

2439 raise YPathError('Syntax error in component: "%s"' % yname) 

2440 

2441 d = dict( 

2442 name=m.group(1)) 

2443 

2444 if m.group(2): 

2445 if m.group(5): 

2446 istart = iend = None 

2447 if m.group(4): 

2448 istart = int(m.group(4)) 

2449 if m.group(6): 

2450 iend = int(m.group(6)) 

2451 

2452 d['slice'] = (istart, iend) 

2453 else: 

2454 d['index'] = int(m.group(7)) 

2455 

2456 return d 

2457 

2458 

2459def _decend(obj, ynames): 

2460 if ynames: 

2461 for sobj in iter_elements(obj, ynames): 

2462 yield sobj 

2463 else: 

2464 yield obj 

2465 

2466 

2467def iter_elements(obj, ypath): 

2468 ''' 

2469 Generator yielding elements matching a given ypath specification. 

2470 

2471 :param obj: guts :py:class:`Object` instance 

2472 :param ypath: Dot-separated object path (e.g. 'root.child.child'). 

2473 To access list objects use slice notatation (e.g. 

2474 'root.child[:].child[1:3].child[1]'). 

2475 

2476 Raises :py:exc:`YPathError` on failure. 

2477 ''' 

2478 

2479 try: 

2480 if isinstance(ypath, str): 

2481 ynames = ypath.split('.') 

2482 else: 

2483 ynames = ypath 

2484 

2485 yname = ynames[0] 

2486 ynames = ynames[1:] 

2487 d = _parse_yname(yname) 

2488 if d['name'] not in obj.T.propnames: 

2489 raise AttributeError(d['name']) 

2490 

2491 obj = getattr(obj, d['name']) 

2492 

2493 if 'index' in d: 

2494 sobj = obj[d['index']] 

2495 for ssobj in _decend(sobj, ynames): 

2496 yield ssobj 

2497 

2498 elif 'slice' in d: 

2499 for i in range(*slice(*d['slice']).indices(len(obj))): 

2500 sobj = obj[i] 

2501 for ssobj in _decend(sobj, ynames): 

2502 yield ssobj 

2503 else: 

2504 for sobj in _decend(obj, ynames): 

2505 yield sobj 

2506 

2507 except (AttributeError, IndexError) as e: 

2508 raise YPathError('Invalid ypath: "%s" (%s)' % (ypath, str(e))) 

2509 

2510 

2511def get_elements(obj, ypath): 

2512 ''' 

2513 Get all elements matching a given ypath specification. 

2514 

2515 :param obj: guts :py:class:`Object` instance 

2516 :param ypath: Dot-separated object path (e.g. 'root.child.child'). 

2517 To access list objects use slice notatation (e.g. 

2518 'root.child[:].child[1:3].child[1]'). 

2519 

2520 Raises :py:exc:`YPathError` on failure. 

2521 ''' 

2522 return list(iter_elements(obj, ypath)) 

2523 

2524 

2525def set_elements(obj, ypath, value, validate=False, regularize=False): 

2526 ''' 

2527 Set elements matching a given ypath specification. 

2528 

2529 :param obj: guts :py:class:`Object` instance 

2530 :param ypath: Dot-separated object path (e.g. 'root.child.child'). 

2531 To access list objects use slice notatation (e.g. 

2532 'root.child[:].child[1:3].child[1]'). 

2533 :param value: All matching elements will be set to `value`. 

2534 :param validate: Whether to validate affected subtrees. 

2535 :param regularize: Whether to regularize affected subtrees. 

2536 

2537 Raises :py:exc:`YPathError` on failure. 

2538 ''' 

2539 

2540 ynames = ypath.split('.') 

2541 try: 

2542 d = _parse_yname(ynames[-1]) 

2543 if ynames[:-1]: 

2544 it = iter_elements(obj, ynames[:-1]) 

2545 else: 

2546 it = [obj] 

2547 

2548 for sobj in it: 

2549 if d['name'] not in sobj.T.propnames: 

2550 raise AttributeError(d['name']) 

2551 

2552 if 'index' in d: 

2553 ssobj = getattr(sobj, d['name']) 

2554 ssobj[d['index']] = value 

2555 elif 'slice' in d: 

2556 ssobj = getattr(sobj, d['name']) 

2557 for i in range(*slice(*d['slice']).indices(len(ssobj))): 

2558 ssobj[i] = value 

2559 else: 

2560 setattr(sobj, d['name'], value) 

2561 if regularize: 

2562 sobj.regularize() 

2563 if validate: 

2564 sobj.validate() 

2565 

2566 except (AttributeError, IndexError, YPathError) as e: 

2567 raise YPathError('Invalid ypath: "%s" (%s)' % (ypath, str(e))) 

2568 

2569 

2570def zip_walk(x, typ=None, path=(), stack=()): 

2571 if typ is None or isinstance(x, typ): 

2572 yield path, stack + (x,) 

2573 

2574 if isinstance(x, Object): 

2575 for (prop, val) in x.T.ipropvals(x): 

2576 if prop.multivalued: 

2577 if val is not None: 

2578 for iele, ele in enumerate(val): 

2579 for y in zip_walk( 

2580 ele, typ, 

2581 path=path + ((prop.name, iele),), 

2582 stack=stack + (x,)): 

2583 

2584 yield y 

2585 else: 

2586 for y in zip_walk(val, typ, 

2587 path=path+(prop.name,), 

2588 stack=stack + (x,)): 

2589 yield y 

2590 

2591 

2592def path_element(x): 

2593 if isinstance(x, tuple): 

2594 if len(x) == 2: 

2595 return '%s[%i]' % x 

2596 elif len(x) == 3: 

2597 return '%s[%i:%i]' % x 

2598 

2599 else: 

2600 return x 

2601 

2602 

2603def path_to_str(path): 

2604 return '.'.join(path_element(x) for x in path) 

2605 

2606 

2607@expand_stream_args('w') 

2608def dump(obj, stream, **kwargs): 

2609 ''' 

2610 Serialize to YAML. 

2611 

2612 If neither ``stream`` nor ``filename`` is set, a string containing the 

2613 serialized data is returned. 

2614 

2615 :param obj: 

2616 Object to be serialized. 

2617 :type obj: 

2618 :py:class:`Object` 

2619 

2620 :param stream: 

2621 Output to stream. 

2622 

2623 :param filename: 

2624 Output to file of given name. 

2625 :type filename: 

2626 str 

2627 

2628 :param header: 

2629 File header to prepend to the output. 

2630 :type header: 

2631 str 

2632 ''' 

2633 return _dump(obj, stream, **kwargs) 

2634 

2635 

2636@expand_stream_args('r') 

2637def load(stream, **kwargs): 

2638 return _load(stream, **kwargs) 

2639 

2640 

2641def load_string(s, **kwargs): 

2642 return load(string=s, **kwargs) 

2643 

2644 

2645@expand_stream_args('w') 

2646def dump_all(obj, stream, **kwargs): 

2647 return _dump_all(obj, stream, **kwargs) 

2648 

2649 

2650@expand_stream_args('r') 

2651def load_all(stream, **kwargs): 

2652 return _load_all(stream, **kwargs) 

2653 

2654 

2655@expand_stream_args('r') 

2656def iload_all(stream, **kwargs): 

2657 return _iload_all(stream, **kwargs) 

2658 

2659 

2660@expand_stream_args('w') 

2661def dump_xml(obj, stream, **kwargs): 

2662 ''' 

2663 Serialize to XML. 

2664 

2665 If neither ``stream`` nor ``filename`` is set, a string containing the 

2666 serialized data is returned. 

2667 

2668 :param obj: 

2669 Object to be serialized. 

2670 :type obj: 

2671 :py:class:`Object` 

2672 

2673 :param stream: 

2674 Output to stream. 

2675 

2676 :param filename: 

2677 Output to file of given name. 

2678 :type filename: 

2679 str 

2680 

2681 :param header: 

2682 File header to prepend to the output. 

2683 :type header: 

2684 str 

2685 

2686 :param ns_ignore: 

2687 Whether to ignore the XML namespace. 

2688 :type ns_ignore: 

2689 bool 

2690 ''' 

2691 return _dump_xml(obj, stream, **kwargs) 

2692 

2693 

2694@expand_stream_args('r') 

2695def load_xml(stream, **kwargs): 

2696 kwargs.pop('filename', None) 

2697 return _load_xml(stream, **kwargs) 

2698 

2699 

2700def load_xml_string(s, **kwargs): 

2701 return load_xml(string=s, **kwargs) 

2702 

2703 

2704@expand_stream_args('w') 

2705def dump_all_xml(obj, stream, **kwargs): 

2706 return _dump_all_xml(obj, stream, **kwargs) 

2707 

2708 

2709@expand_stream_args('r') 

2710def load_all_xml(stream, **kwargs): 

2711 kwargs.pop('filename', None) 

2712 return _load_all_xml(stream, **kwargs) 

2713 

2714 

2715@expand_stream_args('r') 

2716def iload_all_xml(stream, **kwargs): 

2717 kwargs.pop('filename', None) 

2718 return _iload_all_xml(stream, **kwargs) 

2719 

2720 

2721def _dump_all_spickle(objects, stream): 

2722 import pickle 

2723 header = b'SPICKLE'.ljust(512) 

2724 stream.write(header, ) 

2725 for obj in objects: 

2726 pickle.dump(obj, stream) 

2727 

2728 

2729def _iload_all_spickle(stream): 

2730 for obj, _ in _iload_all_spickle_internal(stream): 

2731 yield obj 

2732 

2733 

2734def _load_one_spickle_internal(stream): 

2735 import pickle 

2736 fpos = stream.tell() 

2737 return pickle.load(stream), fpos 

2738 

2739 

2740def _iload_all_spickle_internal(stream, offset=None): 

2741 if offset is not None: 

2742 stream.seek(offset, 0) 

2743 else: 

2744 header = stream.read(512) 

2745 if not header.startswith(b'SPICKLE'): 

2746 raise ValueError('Not a SPICKLE file.') 

2747 

2748 while True: 

2749 try: 

2750 yield _load_one_spickle_internal(stream) 

2751 except EOFError: 

2752 break 

2753 

2754 

2755def _load_all_spickle(stream): 

2756 return list(_iload_all_spickle(stream)) 

2757 

2758 

2759@expand_stream_args('w') 

2760def dump_all_spickle(objects, stream, **kwargs): 

2761 return _dump_all_spickle(objects, stream) 

2762 

2763 

2764@expand_stream_args('r') 

2765def iload_all_spickle(stream, **kwargs): 

2766 _iload_all_spickle(stream) 

2767 

2768 

2769@expand_stream_args('r') 

2770def load_all_spickle(stream, **kwargs): 

2771 kwargs.pop('filename', None) 

2772 return _load_all_spickle(stream, **kwargs) 

2773 

2774 

2775__all__ = guts_types + [ 

2776 'guts_types', 'TBase', 'ValidationError', 

2777 'ArgumentError', 'Defer', 

2778 'DefaultMaker', 'ObjectDefaultMaker', 

2779 'clone', 

2780 'dump', 'load', 

2781 'dump_all', 'load_all', 'iload_all', 

2782 'dump_xml', 'load_xml', 

2783 'dump_all_xml', 'load_all_xml', 'iload_all_xml', 

2784 'load_string', 

2785 'load_xml_string', 

2786 'make_typed_list_class', 'walk', 'zip_walk', 'path_to_str' 

2787]