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

1415 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-01-03 09:20 +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 @classmethod 

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

992 ''' 

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

994 specified arguments. 

995 

996 :returns: 

997 Factory for default values. 

998 :rtype: 

999 :py:class:`ObjectDefaultMaker` object 

1000 ''' 

1001 return ObjectDefaultMaker(cls, args, kwargs) 

1002 

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

1004 ''' 

1005 Validate this object. 

1006 

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

1008 

1009 :param depth: 

1010 Maximum depth to descend into child objects. 

1011 :type depth: 

1012 int 

1013 ''' 

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

1015 

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

1017 ''' 

1018 Regularize this object. 

1019 

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

1021 expected types. 

1022 

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

1024 cannot be regularized. 

1025 

1026 :param depth: 

1027 Maximum depth to descend into child objects. 

1028 :type depth: 

1029 int 

1030 ''' 

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

1032 

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

1034 ''' 

1035 Serialize to YAML. 

1036 

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

1038 serialized data is returned. 

1039 

1040 :param stream: 

1041 Output to stream. 

1042 

1043 :param filename: 

1044 Output to file of given name. 

1045 :type filename: 

1046 str 

1047 

1048 :param header: 

1049 File header to prepend to the output. 

1050 :type header: 

1051 str 

1052 ''' 

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

1054 

1055 def dump_xml( 

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

1057 ''' 

1058 Serialize to XML. 

1059 

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

1061 serialized data is returned. 

1062 

1063 :param stream: 

1064 Output to stream. 

1065 

1066 :param filename: 

1067 Output to file of given name. 

1068 :type filename: 

1069 str 

1070 

1071 :param header: 

1072 File header to prepend to the output. 

1073 :type header: 

1074 str 

1075 

1076 :param ns_ignore: 

1077 Whether to ignore the XML namespace. 

1078 :type ns_ignore: 

1079 bool 

1080 ''' 

1081 return dump_xml( 

1082 self, stream=stream, filename=filename, header=header, 

1083 ns_ignore=ns_ignore) 

1084 

1085 @classmethod 

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

1087 ''' 

1088 Deserialize from YAML. 

1089 

1090 :param stream: 

1091 Read input from stream. 

1092 

1093 :param filename: 

1094 Read input from file of given name. 

1095 :type filename: 

1096 str 

1097 

1098 :param string: 

1099 Read input from string. 

1100 :type string: 

1101 str 

1102 ''' 

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

1104 

1105 @classmethod 

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

1107 ns_ignore=False): 

1108 ''' 

1109 Deserialize from XML. 

1110 

1111 :param stream: 

1112 Read input from stream. 

1113 

1114 :param filename: 

1115 Read input from file of given name. 

1116 :type filename: 

1117 str 

1118 

1119 :param string: 

1120 Read input from string. 

1121 :type string: 

1122 str 

1123 ''' 

1124 

1125 if ns_hints is None: 

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

1127 

1128 return load_xml( 

1129 stream=stream, 

1130 filename=filename, 

1131 string=string, 

1132 ns_hints=ns_hints, 

1133 ns_ignore=ns_ignore) 

1134 

1135 def __str__(self): 

1136 return self.dump() 

1137 

1138 

1139def to_dict(obj): 

1140 ''' 

1141 Get dict of guts object attributes. 

1142 

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

1144 ''' 

1145 

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

1147 

1148 

1149class SObject(Object): 

1150 ''' 

1151 Base class for simple str-serializable Guts objects. 

1152 

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

1154 ''' 

1155 

1156 class __T(TBase): 

1157 def regularize_extra(self, val): 

1158 if isinstance(val, str): 

1159 return self._cls(val) 

1160 

1161 return val 

1162 

1163 def to_save(self, val): 

1164 return str(val) 

1165 

1166 def to_save_xml(self, val): 

1167 return str(val) 

1168 

1169 

1170class Any(Object): 

1171 ''' 

1172 Placeholder for any object. 

1173 ''' 

1174 

1175 class __T(TBase): 

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

1177 if isinstance(val, Object): 

1178 val.validate(regularize, depth) 

1179 

1180 return val 

1181 

1182 

1183class Int(Object): 

1184 ''' 

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

1186 ''' 

1187 dummy_for = int 

1188 

1189 class __T(TBase): 

1190 strict = True 

1191 

1192 def to_save_xml(self, value): 

1193 return repr(value) 

1194 

1195 

1196class Float(Object): 

1197 ''' 

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

1199 ''' 

1200 dummy_for = float 

1201 

1202 class __T(TBase): 

1203 strict = True 

1204 

1205 def to_save_xml(self, value): 

1206 return repr(value) 

1207 

1208 

1209class Complex(Object): 

1210 ''' 

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

1212 ''' 

1213 dummy_for = complex 

1214 

1215 class __T(TBase): 

1216 strict = True 

1217 

1218 def regularize_extra(self, val): 

1219 

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

1221 assert len(val) == 2 

1222 val = complex(*val) 

1223 

1224 elif not isinstance(val, complex): 

1225 val = complex(val) 

1226 

1227 return val 

1228 

1229 def to_save(self, value): 

1230 return repr(value) 

1231 

1232 def to_save_xml(self, value): 

1233 return repr(value) 

1234 

1235 

1236class Bool(Object): 

1237 ''' 

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

1239 ''' 

1240 dummy_for = bool 

1241 

1242 class __T(TBase): 

1243 strict = True 

1244 

1245 def regularize_extra(self, val): 

1246 if isinstance(val, str): 

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

1248 return False 

1249 

1250 return bool(val) 

1251 

1252 def to_save_xml(self, value): 

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

1254 

1255 

1256class String(Object): 

1257 ''' 

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

1259 ''' 

1260 dummy_for = str 

1261 

1262 class __T(TBase): 

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

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

1265 self.style_cls = str_style_map[yamlstyle] 

1266 

1267 def to_save(self, val): 

1268 return self.style_cls(val) 

1269 

1270 

1271class Bytes(Object): 

1272 ''' 

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

1274 ''' 

1275 dummy_for = bytes 

1276 

1277 class __T(TBase): 

1278 

1279 def regularize_extra(self, val): 

1280 if isinstance(val, str): 

1281 val = b64decode(val) 

1282 

1283 return val 

1284 

1285 def to_save(self, val): 

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

1287 

1288 

1289class Unicode(Object): 

1290 ''' 

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

1292 ''' 

1293 dummy_for = str 

1294 

1295 class __T(TBase): 

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

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

1298 self.style_cls = unicode_style_map[yamlstyle] 

1299 

1300 def to_save(self, val): 

1301 return self.style_cls(val) 

1302 

1303 

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

1305 

1306 

1307class Dict(Object): 

1308 ''' 

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

1310 ''' 

1311 dummy_for = dict 

1312 

1313 class __T(TBase): 

1314 multivalued = dict 

1315 

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

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

1318 assert isinstance(key_t, TBase) 

1319 assert isinstance(content_t, TBase) 

1320 self.key_t = key_t 

1321 self.content_t = content_t 

1322 self.content_t.parent = self 

1323 

1324 def default(self): 

1325 if self._default is not None: 

1326 return dict( 

1327 (make_default(k), make_default(v)) 

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

1329 

1330 if self.optional: 

1331 return None 

1332 else: 

1333 return {} 

1334 

1335 def has_default(self): 

1336 return True 

1337 

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

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

1340 

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

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

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

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

1345 if regularize: 

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

1347 del val[key] 

1348 val[newkey] = newele 

1349 

1350 return val 

1351 

1352 def to_save(self, val): 

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

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

1355 

1356 def to_save_xml(self, val): 

1357 raise NotImplementedError 

1358 

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

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

1361 self.content_t.classname_for_help(strip_module=strip_module) 

1362 

1363 

1364class List(Object): 

1365 ''' 

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

1367 ''' 

1368 dummy_for = list 

1369 

1370 class __T(TBase): 

1371 multivalued = list 

1372 

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

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

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

1376 self.content_t = content_t 

1377 self.content_t.parent = self 

1378 self.style_cls = list_style_map[yamlstyle] 

1379 

1380 def default(self): 

1381 if self._default is not None: 

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

1383 if self.optional: 

1384 return None 

1385 else: 

1386 return [] 

1387 

1388 def has_default(self): 

1389 return True 

1390 

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

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

1393 

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

1395 for i, ele in enumerate(val): 

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

1397 if regularize and newele is not ele: 

1398 val[i] = newele 

1399 

1400 return val 

1401 

1402 def to_save(self, val): 

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

1404 

1405 def to_save_xml(self, val): 

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

1407 

1408 def deferred(self): 

1409 if isinstance(self.content_t, Defer): 

1410 return [self.content_t] 

1411 

1412 return [] 

1413 

1414 def process_deferred(self, defer, t_inst): 

1415 if defer is self.content_t: 

1416 self.content_t = t_inst 

1417 

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

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

1420 self.content_t.classname_for_help(strip_module=strip_module) 

1421 

1422 

1423def make_typed_list_class(t): 

1424 class TL(List): 

1425 class __T(List.T): 

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

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

1428 

1429 return TL 

1430 

1431 

1432class Tuple(Object): 

1433 ''' 

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

1435 ''' 

1436 dummy_for = tuple 

1437 

1438 class __T(TBase): 

1439 multivalued = tuple 

1440 

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

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

1443 assert isinstance(content_t, TBase) 

1444 self.content_t = content_t 

1445 self.content_t.parent = self 

1446 self.n = n 

1447 

1448 def default(self): 

1449 if self._default is not None: 

1450 return tuple( 

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

1452 

1453 elif self.optional: 

1454 return None 

1455 else: 

1456 if self.n is not None: 

1457 return tuple( 

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

1459 else: 

1460 return tuple() 

1461 

1462 def has_default(self): 

1463 return True 

1464 

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

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

1467 

1468 def validate_extra(self, val): 

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

1470 raise ValidationError( 

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

1472 

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

1474 if not regularize: 

1475 for ele in val: 

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

1477 

1478 return val 

1479 else: 

1480 newval = [] 

1481 isnew = False 

1482 for ele in val: 

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

1484 newval.append(newele) 

1485 if newele is not ele: 

1486 isnew = True 

1487 

1488 if isnew: 

1489 return tuple(newval) 

1490 else: 

1491 return val 

1492 

1493 def to_save(self, val): 

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

1495 

1496 def to_save_xml(self, val): 

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

1498 

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

1500 if self.n is not None: 

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

1502 self.n, self.content_t.classname_for_help( 

1503 strip_module=strip_module)) 

1504 else: 

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

1506 self.content_t.classname_for_help( 

1507 strip_module=strip_module)) 

1508 

1509 

1510duration_unit_factors = dict( 

1511 s=1.0, 

1512 m=60.0, 

1513 h=3600.0, 

1514 d=24*3600.0, 

1515 y=365*24*3600.0) 

1516 

1517 

1518def parse_duration(s): 

1519 unit = s[-1] 

1520 if unit in duration_unit_factors: 

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

1522 else: 

1523 return float(s) 

1524 

1525 

1526def str_duration(d): 

1527 for k in 'ydhms': 

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

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

1530 

1531 return '%g' % d 

1532 

1533 

1534class Duration(Object): 

1535 ''' 

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

1537 (de)serialization. 

1538 

1539 Examples: 

1540 

1541 - ``'1s'`` -> 1 second 

1542 - ``'1m'`` -> 1 minute 

1543 - ``'1h'`` -> 1 hour 

1544 - ``'1d'`` -> 1 day 

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

1546 ''' 

1547 dummy_for = float 

1548 

1549 class __T(TBase): 

1550 def regularize_extra(self, val): 

1551 if isinstance(val, str): 

1552 return parse_duration(val) 

1553 

1554 return val 

1555 

1556 def to_save(self, val): 

1557 return str_duration(val) 

1558 

1559 def to_save_xml(self, val): 

1560 return str_duration(val) 

1561 

1562 

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

1564 

1565 

1566class Timestamp(Object): 

1567 ''' 

1568 Placeholder for a UTC timestamp. 

1569 ''' 

1570 dummy_for = (hpfloat, float) 

1571 dummy_for_description = 'pyrocko.util.get_time_float' 

1572 

1573 class __T(TBase): 

1574 

1575 def regularize_extra(self, val): 

1576 

1577 time_float = get_time_float() 

1578 

1579 if isinstance(val, datetime.datetime): 

1580 tt = val.utctimetuple() 

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

1582 

1583 elif isinstance(val, datetime.date): 

1584 tt = val.timetuple() 

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

1586 

1587 elif isinstance(val, str): 

1588 val = val.strip() 

1589 tz_offset = 0 

1590 

1591 m = re_tz.search(val) 

1592 if m: 

1593 sh = m.group(2) 

1594 sm = m.group(4) 

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

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

1597 

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

1599 

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

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

1602 

1603 try: 

1604 val = str_to_time(val) - tz_offset 

1605 except TimeStrError: 

1606 raise ValidationError( 

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

1608 

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

1610 val = time_float(val) 

1611 

1612 else: 

1613 raise ValidationError( 

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

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

1616 

1617 return val 

1618 

1619 def to_save(self, val): 

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

1621 .rstrip('0').rstrip('.') 

1622 

1623 def to_save_xml(self, val): 

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

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

1626 

1627 @classmethod 

1628 def D(self, s): 

1629 return TimestampDefaultMaker(s) 

1630 

1631 

1632class DateTimestamp(Object): 

1633 ''' 

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

1635 ''' 

1636 dummy_for = (hpfloat, float) 

1637 dummy_for_description = 'pyrocko.util.get_time_float' 

1638 

1639 class __T(TBase): 

1640 

1641 def regularize_extra(self, val): 

1642 

1643 time_float = get_time_float() 

1644 

1645 if isinstance(val, datetime.datetime): 

1646 tt = val.utctimetuple() 

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

1648 

1649 elif isinstance(val, datetime.date): 

1650 tt = val.timetuple() 

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

1652 

1653 elif isinstance(val, str): 

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

1655 

1656 elif isinstance(val, int): 

1657 val = time_float(val) 

1658 

1659 return val 

1660 

1661 def to_save(self, val): 

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

1663 

1664 def to_save_xml(self, val): 

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

1666 

1667 @classmethod 

1668 def D(self, s): 

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

1670 

1671 

1672class StringPattern(String): 

1673 

1674 ''' 

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

1676 ''' 

1677 

1678 dummy_for = str 

1679 pattern = '.*' 

1680 

1681 class __T(String.T): 

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

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

1684 

1685 if pattern is not None: 

1686 self.pattern = pattern 

1687 else: 

1688 self.pattern = self._dummy_cls.pattern 

1689 

1690 def validate_extra(self, val): 

1691 pat = self.pattern 

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

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

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

1695 

1696 @classmethod 

1697 def class_help_string(cls): 

1698 dcls = cls._dummy_cls 

1699 doc = dcls.__doc_template__ or StringPattern.__doc_template__ 

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

1701 

1702 

1703class UnicodePattern(Unicode): 

1704 

1705 ''' 

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

1707 ''' 

1708 

1709 dummy_for = str 

1710 pattern = '.*' 

1711 

1712 class __T(TBase): 

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

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

1715 

1716 if pattern is not None: 

1717 self.pattern = pattern 

1718 else: 

1719 self.pattern = self._dummy_cls.pattern 

1720 

1721 def validate_extra(self, val): 

1722 pat = self.pattern 

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

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

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

1726 

1727 @classmethod 

1728 def class_help_string(cls): 

1729 dcls = cls._dummy_cls 

1730 doc = dcls.__doc_template__ or UnicodePattern.__doc_template__ 

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

1732 

1733 

1734class StringChoice(String): 

1735 

1736 ''' 

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

1738 

1739 :cvar choices: 

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

1741 :cvar ignore_case: 

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

1743 ``False``). 

1744 ''' 

1745 

1746 dummy_for = str 

1747 choices = [] 

1748 ignore_case = False 

1749 

1750 class __T(String.T): 

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

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

1753 

1754 if choices is not None: 

1755 self.choices = choices 

1756 else: 

1757 self.choices = self._dummy_cls.choices 

1758 

1759 if ignore_case is not None: 

1760 self.ignore_case = ignore_case 

1761 else: 

1762 self.ignore_case = self._dummy_cls.ignore_case 

1763 

1764 if self.ignore_case: 

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

1766 

1767 def validate_extra(self, val): 

1768 if self.ignore_case: 

1769 val = val.upper() 

1770 

1771 if val not in self.choices: 

1772 raise ValidationError( 

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

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

1775 

1776 @classmethod 

1777 def class_help_string(cls): 

1778 dcls = cls._dummy_cls 

1779 doc = dcls.__doc_template__ or StringChoice.__doc_template__ 

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

1781 

1782 

1783class IntChoice(Int): 

1784 

1785 ''' 

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

1787 ''' 

1788 

1789 dummy_for = int 

1790 choices = [] 

1791 

1792 class __T(Int.T): 

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

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

1795 

1796 if choices is not None: 

1797 self.choices = choices 

1798 else: 

1799 self.choices = self._dummy_cls.choices 

1800 

1801 def validate_extra(self, val): 

1802 if val not in self.choices: 

1803 raise ValidationError( 

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

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

1806 

1807 @classmethod 

1808 def class_help_string(cls): 

1809 dcls = cls._dummy_cls 

1810 doc = dcls.__doc_template__ or IntChoice.__doc_template__ 

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

1812 

1813 

1814# this will not always work... 

1815class StringUnion(Object): 

1816 ''' 

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

1818 

1819 :cvar members: 

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

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

1822 derived objects). 

1823 

1824 ''' 

1825 

1826 members = [] 

1827 

1828 dummy_for = str 

1829 

1830 class __T(TBase): 

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

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

1833 if members is not None: 

1834 self.members = members 

1835 else: 

1836 self.members = self._dummy_cls.members 

1837 

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

1839 assert self.members 

1840 e2 = None 

1841 for member in self.members: 

1842 try: 

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

1844 except ValidationError as e: 

1845 e2 = e 

1846 

1847 raise e2 

1848 

1849 

1850class Choice(Object): 

1851 ''' 

1852 Any out of a set of different types. 

1853 

1854 :cvar choices: 

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

1856 

1857 ''' 

1858 choices = [] 

1859 

1860 class __T(TBase): 

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

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

1863 if choices is not None: 

1864 self.choices = choices 

1865 else: 

1866 self.choices = self._dummy_cls.choices 

1867 

1868 self.cls_to_xmltagname = dict( 

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

1870 

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

1872 if self.optional and val is None: 

1873 return val 

1874 

1875 t = None 

1876 for tc in self.choices: 

1877 is_derived = isinstance(val, tc._cls) 

1878 is_exact = type(val) is tc._cls 

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

1880 tc.strict and not is_exact): 

1881 

1882 t = tc 

1883 break 

1884 

1885 if t is None: 

1886 if regularize: 

1887 ok = False 

1888 for tc in self.choices: 

1889 try: 

1890 val = tc.regularize_extra(val) 

1891 ok = True 

1892 t = tc 

1893 break 

1894 except (ValidationError, ValueError): 

1895 pass 

1896 

1897 if not ok: 

1898 raise ValidationError( 

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

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

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

1902 else: 

1903 raise ValidationError( 

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

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

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

1907 

1908 validator = t 

1909 

1910 if isinstance(t._cls, tuple): 

1911 clss = t._cls 

1912 else: 

1913 clss = (t._cls,) 

1914 

1915 for cls in clss: 

1916 try: 

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

1918 validator = val.T.instance 

1919 

1920 except AttributeError: 

1921 pass 

1922 

1923 validator.validate_extra(val) 

1924 

1925 if depth != 0: 

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

1927 

1928 return val 

1929 

1930 def extend_xmlelements(self, elems, v): 

1931 elems.append(( 

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

1933 

1934 

1935def _dump( 

1936 object, stream, 

1937 header=False, 

1938 Dumper=GutsSafeDumper, 

1939 _dump_function=yaml.dump): 

1940 

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

1942 enc = encode_utf8 

1943 else: 

1944 enc = no_encode 

1945 

1946 if header: 

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

1948 if isinstance(header, str): 

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

1950 stream.write(enc(banner)) 

1951 

1952 _dump_function( 

1953 object, 

1954 stream=stream, 

1955 encoding='utf-8', 

1956 explicit_start=True, 

1957 Dumper=Dumper) 

1958 

1959 

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

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

1962 

1963 

1964def _load(stream, 

1965 Loader=GutsSafeLoader, allow_include=None, filename=None, 

1966 included_files=None): 

1967 

1968 class _Loader(Loader): 

1969 _filename = filename 

1970 _allow_include = allow_include 

1971 _included_files = included_files or [] 

1972 

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

1974 

1975 

1976def _load_all(stream, 

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

1978 

1979 class _Loader(Loader): 

1980 _filename = filename 

1981 _allow_include = allow_include 

1982 

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

1984 

1985 

1986def _iload_all(stream, 

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

1988 

1989 class _Loader(Loader): 

1990 _filename = filename 

1991 _allow_include = allow_include 

1992 

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

1994 

1995 

1996def multi_representer(dumper, data): 

1997 node = dumper.represent_mapping( 

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

1999 

2000 return node 

2001 

2002 

2003# hack for compatibility with early GF Store versions 

2004re_compatibility = re.compile( 

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

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

2007) 

2008 

2009 

2010def multi_constructor(loader, tag_suffix, node): 

2011 tagname = str(tag_suffix) 

2012 

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

2014 

2015 cls = g_tagname_to_class[tagname] 

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

2017 o = cls(**kwargs) 

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

2019 return o 

2020 

2021 

2022def include_constructor(loader, node): 

2023 allow_include = loader._allow_include \ 

2024 if loader._allow_include is not None \ 

2025 else ALLOW_INCLUDE 

2026 

2027 if not allow_include: 

2028 raise EnvironmentError( 

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

2030 

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

2032 inc_file = loader.construct_scalar(node) 

2033 else: 

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

2035 

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

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

2038 

2039 if not op.isfile(inc_file): 

2040 raise FileNotFoundError(inc_file) 

2041 

2042 included_files = list(loader._included_files) 

2043 if loader._filename is not None: 

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

2045 

2046 for included_file in loader._included_files: 

2047 if op.samefile(inc_file, included_file): 

2048 raise ImportError( 

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

2050 op.abspath(inc_file), 

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

2052 

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

2054 return _load( 

2055 f, 

2056 Loader=loader.__class__, filename=inc_file, 

2057 allow_include=True, 

2058 included_files=included_files) 

2059 

2060 

2061def dict_noflow_representer(dumper, data): 

2062 return dumper.represent_mapping( 

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

2064 

2065 

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

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

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

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

2070 

2071 

2072def str_representer(dumper, data): 

2073 return dumper.represent_scalar( 

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

2075 

2076 

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

2078 

2079 

2080class Constructor(object): 

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

2082 ns_ignore=False): 

2083 

2084 self.stack = [] 

2085 self.queue = [] 

2086 self.namespaces = defaultdict(list) 

2087 self.add_namespace_maps = add_namespace_maps 

2088 self.strict = strict 

2089 self.ns_hints = ns_hints 

2090 self.ns_ignore = ns_ignore 

2091 

2092 def start_element(self, ns_name, attrs): 

2093 if self.ns_ignore: 

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

2095 

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

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

2098 self.ns_hints = g_guessable_xmlns[ns_name] 

2099 

2100 if self.ns_hints: 

2101 ns_names = [ 

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

2103 

2104 elif self.ns_hints is None: 

2105 ns_names = [' ' + ns_name] 

2106 

2107 else: 

2108 ns_names = [ns_name] 

2109 

2110 for ns_name in ns_names: 

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

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

2113 ns_name, None) 

2114 

2115 if isinstance(cls, tuple): 

2116 cls = None 

2117 else: 

2118 if cls is not None and ( 

2119 not issubclass(cls, Object) 

2120 or issubclass(cls, SObject)): 

2121 cls = None 

2122 else: 

2123 cls = g_xmltagname_to_class.get(ns_name, None) 

2124 

2125 if cls: 

2126 break 

2127 

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

2129 

2130 def end_element(self, _): 

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

2132 

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

2134 

2135 if cls is not None: 

2136 content2.extend( 

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

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

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

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

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

2142 if self.add_namespace_maps: 

2143 o.namespace_map = self.get_current_namespace_map() 

2144 

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

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

2147 else: 

2148 self.queue.append(o) 

2149 else: 

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

2151 if self.stack: 

2152 for c in content: 

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

2154 

2155 def characters(self, char_content): 

2156 if self.stack: 

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

2158 

2159 def start_namespace(self, ns, uri): 

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

2161 

2162 def end_namespace(self, ns): 

2163 self.namespaces[ns].pop() 

2164 

2165 def get_current_namespace_map(self): 

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

2167 

2168 def get_queued_elements(self): 

2169 queue = self.queue 

2170 self.queue = [] 

2171 return queue 

2172 

2173 

2174def _iload_all_xml( 

2175 stream, 

2176 bufsize=100000, 

2177 add_namespace_maps=False, 

2178 strict=False, 

2179 ns_hints=None, 

2180 ns_ignore=False): 

2181 

2182 from xml.parsers.expat import ParserCreate 

2183 from pyrocko import progress 

2184 

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

2186 

2187 handler = Constructor( 

2188 add_namespace_maps=add_namespace_maps, 

2189 strict=strict, 

2190 ns_hints=ns_hints, 

2191 ns_ignore=ns_ignore) 

2192 

2193 parser.StartElementHandler = handler.start_element 

2194 parser.EndElementHandler = handler.end_element 

2195 parser.CharacterDataHandler = handler.characters 

2196 parser.StartNamespaceDeclHandler = handler.start_namespace 

2197 parser.EndNamespaceDeclHandler = handler.end_namespace 

2198 

2199 try: 

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

2201 except Exception: 

2202 nbytes = None 

2203 

2204 ibytes = 0 

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

2206 try: 

2207 while True: 

2208 data = stream.read(bufsize) 

2209 ibytes += len(data) 

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

2211 for element in handler.get_queued_elements(): 

2212 yield element 

2213 

2214 task.update(ibytes) 

2215 

2216 if not data: 

2217 break 

2218 

2219 except Exception: 

2220 task.fail() 

2221 raise 

2222 

2223 finally: 

2224 task.done() 

2225 

2226 

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

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

2229 

2230 

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

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

2233 return next(g) 

2234 

2235 

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

2237 

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

2239 enc = encode_utf8 

2240 else: 

2241 enc = no_encode 

2242 

2243 _dump_xml_header(stream, header) 

2244 

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

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

2247 

2248 stream.write(enc(beg)) 

2249 

2250 for ob in objects: 

2251 _dump_xml(ob, stream=stream) 

2252 

2253 stream.write(enc(end)) 

2254 

2255 

2256def _dump_xml_header(stream, banner=None): 

2257 

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

2259 enc = encode_utf8 

2260 else: 

2261 enc = no_encode 

2262 

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

2264 if isinstance(banner, str): 

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

2266 

2267 

2268def _dump_xml( 

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

2270 ns_ignore=False): 

2271 

2272 from xml.sax.saxutils import escape, quoteattr 

2273 

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

2275 enc = encode_utf8 

2276 else: 

2277 enc = no_encode 

2278 

2279 if depth == 0 and header: 

2280 _dump_xml_header(stream, header) 

2281 

2282 indent = ' '*depth*2 

2283 if ns_name is None: 

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

2285 

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

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

2288 else: 

2289 ns, name = '', ns_name 

2290 

2291 if isinstance(obj, Object): 

2292 obj.validate(depth=1) 

2293 attrs = [] 

2294 elems = [] 

2295 

2296 added_ns = False 

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

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

2299 ns_map.append(ns) 

2300 added_ns = True 

2301 

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

2303 if prop.xmlstyle == 'attribute': 

2304 assert not prop.multivalued 

2305 assert not isinstance(v, Object) 

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

2307 

2308 elif prop.xmlstyle == 'content': 

2309 assert not prop.multivalued 

2310 assert not isinstance(v, Object) 

2311 elems.append((None, v)) 

2312 

2313 else: 

2314 prop.extend_xmlelements(elems, v) 

2315 

2316 attr_str = '' 

2317 if attrs: 

2318 attr_str = ' ' + ' '.join( 

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

2320 for (k, v) in attrs) 

2321 

2322 if not elems: 

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

2324 else: 

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

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

2327 indent, 

2328 name, 

2329 attr_str, 

2330 '' if oneline else '\n'))) 

2331 

2332 for (k, v) in elems: 

2333 if k is None: 

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

2335 else: 

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

2337 

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

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

2340 

2341 if added_ns: 

2342 ns_map.pop() 

2343 

2344 else: 

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

2346 indent, 

2347 name, 

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

2349 name))) 

2350 

2351 

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

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

2354 yield path, x 

2355 

2356 if isinstance(x, Object): 

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

2358 if prop.multivalued: 

2359 if val is not None: 

2360 for iele, ele in enumerate(val): 

2361 for y in walk(ele, typ, 

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

2363 yield y 

2364 else: 

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

2366 yield y 

2367 

2368 

2369def clone(x, pool=None): 

2370 ''' 

2371 Clone guts object tree. 

2372 

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

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

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

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

2377 referenced also in the destination tree. 

2378 

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

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

2381 property. 

2382 ''' 

2383 

2384 if pool is None: 

2385 pool = {} 

2386 

2387 if id(x) in pool: 

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

2389 

2390 else: 

2391 if isinstance(x, SObject): 

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

2393 elif isinstance(x, Object): 

2394 d = {} 

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

2396 if y is not None: 

2397 if not prop.multivalued: 

2398 y_copy = clone(y, pool) 

2399 elif prop.multivalued is dict: 

2400 y_copy = dict( 

2401 (clone(zk, pool), clone(zv, pool)) 

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

2403 else: 

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

2405 else: 

2406 y_copy = y 

2407 

2408 d[prop.name] = y_copy 

2409 

2410 x_copy = x.__class__(**d) 

2411 

2412 else: 

2413 x_copy = copy.deepcopy(x) 

2414 

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

2416 return x_copy 

2417 

2418 

2419class YPathError(Exception): 

2420 ''' 

2421 This exception is raised for invalid ypath specifications. 

2422 ''' 

2423 pass 

2424 

2425 

2426def _parse_yname(yname): 

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

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

2429 m = re.match( 

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

2431 % (ident, rint, rint, rint), yname) 

2432 

2433 if not m: 

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

2435 

2436 d = dict( 

2437 name=m.group(1)) 

2438 

2439 if m.group(2): 

2440 if m.group(5): 

2441 istart = iend = None 

2442 if m.group(4): 

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

2444 if m.group(6): 

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

2446 

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

2448 else: 

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

2450 

2451 return d 

2452 

2453 

2454def _decend(obj, ynames): 

2455 if ynames: 

2456 for sobj in iter_elements(obj, ynames): 

2457 yield sobj 

2458 else: 

2459 yield obj 

2460 

2461 

2462def iter_elements(obj, ypath): 

2463 ''' 

2464 Generator yielding elements matching a given ypath specification. 

2465 

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

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

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

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

2470 

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

2472 ''' 

2473 

2474 try: 

2475 if isinstance(ypath, str): 

2476 ynames = ypath.split('.') 

2477 else: 

2478 ynames = ypath 

2479 

2480 yname = ynames[0] 

2481 ynames = ynames[1:] 

2482 d = _parse_yname(yname) 

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

2484 raise AttributeError(d['name']) 

2485 

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

2487 

2488 if 'index' in d: 

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

2490 for ssobj in _decend(sobj, ynames): 

2491 yield ssobj 

2492 

2493 elif 'slice' in d: 

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

2495 sobj = obj[i] 

2496 for ssobj in _decend(sobj, ynames): 

2497 yield ssobj 

2498 else: 

2499 for sobj in _decend(obj, ynames): 

2500 yield sobj 

2501 

2502 except (AttributeError, IndexError) as e: 

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

2504 

2505 

2506def get_elements(obj, ypath): 

2507 ''' 

2508 Get all elements matching a given ypath specification. 

2509 

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

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

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

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

2514 

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

2516 ''' 

2517 return list(iter_elements(obj, ypath)) 

2518 

2519 

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

2521 ''' 

2522 Set elements matching a given ypath specification. 

2523 

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

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

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

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

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

2529 :param validate: Whether to validate affected subtrees. 

2530 :param regularize: Whether to regularize affected subtrees. 

2531 

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

2533 ''' 

2534 

2535 ynames = ypath.split('.') 

2536 try: 

2537 d = _parse_yname(ynames[-1]) 

2538 if ynames[:-1]: 

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

2540 else: 

2541 it = [obj] 

2542 

2543 for sobj in it: 

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

2545 raise AttributeError(d['name']) 

2546 

2547 if 'index' in d: 

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

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

2550 elif 'slice' in d: 

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

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

2553 ssobj[i] = value 

2554 else: 

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

2556 if regularize: 

2557 sobj.regularize() 

2558 if validate: 

2559 sobj.validate() 

2560 

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

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

2563 

2564 

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

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

2567 yield path, stack + (x,) 

2568 

2569 if isinstance(x, Object): 

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

2571 if prop.multivalued: 

2572 if val is not None: 

2573 for iele, ele in enumerate(val): 

2574 for y in zip_walk( 

2575 ele, typ, 

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

2577 stack=stack + (x,)): 

2578 

2579 yield y 

2580 else: 

2581 for y in zip_walk(val, typ, 

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

2583 stack=stack + (x,)): 

2584 yield y 

2585 

2586 

2587def path_element(x): 

2588 if isinstance(x, tuple): 

2589 if len(x) == 2: 

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

2591 elif len(x) == 3: 

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

2593 

2594 else: 

2595 return x 

2596 

2597 

2598def path_to_str(path): 

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

2600 

2601 

2602@expand_stream_args('w') 

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

2604 ''' 

2605 Serialize to YAML. 

2606 

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

2608 serialized data is returned. 

2609 

2610 :param obj: 

2611 Object to be serialized. 

2612 :type obj: 

2613 :py:class:`Object` 

2614 

2615 :param stream: 

2616 Output to stream. 

2617 

2618 :param filename: 

2619 Output to file of given name. 

2620 :type filename: 

2621 str 

2622 

2623 :param header: 

2624 File header to prepend to the output. 

2625 :type header: 

2626 str 

2627 ''' 

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

2629 

2630 

2631@expand_stream_args('r') 

2632def load(stream, **kwargs): 

2633 return _load(stream, **kwargs) 

2634 

2635 

2636def load_string(s, **kwargs): 

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

2638 

2639 

2640@expand_stream_args('w') 

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

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

2643 

2644 

2645@expand_stream_args('r') 

2646def load_all(stream, **kwargs): 

2647 return _load_all(stream, **kwargs) 

2648 

2649 

2650@expand_stream_args('r') 

2651def iload_all(stream, **kwargs): 

2652 return _iload_all(stream, **kwargs) 

2653 

2654 

2655@expand_stream_args('w') 

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

2657 ''' 

2658 Serialize to XML. 

2659 

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

2661 serialized data is returned. 

2662 

2663 :param obj: 

2664 Object to be serialized. 

2665 :type obj: 

2666 :py:class:`Object` 

2667 

2668 :param stream: 

2669 Output to stream. 

2670 

2671 :param filename: 

2672 Output to file of given name. 

2673 :type filename: 

2674 str 

2675 

2676 :param header: 

2677 File header to prepend to the output. 

2678 :type header: 

2679 str 

2680 

2681 :param ns_ignore: 

2682 Whether to ignore the XML namespace. 

2683 :type ns_ignore: 

2684 bool 

2685 ''' 

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

2687 

2688 

2689@expand_stream_args('r') 

2690def load_xml(stream, **kwargs): 

2691 kwargs.pop('filename', None) 

2692 return _load_xml(stream, **kwargs) 

2693 

2694 

2695def load_xml_string(s, **kwargs): 

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

2697 

2698 

2699@expand_stream_args('w') 

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

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

2702 

2703 

2704@expand_stream_args('r') 

2705def load_all_xml(stream, **kwargs): 

2706 kwargs.pop('filename', None) 

2707 return _load_all_xml(stream, **kwargs) 

2708 

2709 

2710@expand_stream_args('r') 

2711def iload_all_xml(stream, **kwargs): 

2712 kwargs.pop('filename', None) 

2713 return _iload_all_xml(stream, **kwargs) 

2714 

2715 

2716def _dump_all_spickle(objects, stream): 

2717 import pickle 

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

2719 stream.write(header, ) 

2720 for obj in objects: 

2721 pickle.dump(obj, stream) 

2722 

2723 

2724def _iload_all_spickle(stream): 

2725 for obj, _ in _iload_all_spickle_internal(stream): 

2726 yield obj 

2727 

2728 

2729def _load_one_spickle_internal(stream): 

2730 import pickle 

2731 fpos = stream.tell() 

2732 return pickle.load(stream), fpos 

2733 

2734 

2735def _iload_all_spickle_internal(stream, offset=None): 

2736 if offset is not None: 

2737 stream.seek(offset, 0) 

2738 else: 

2739 header = stream.read(512) 

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

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

2742 

2743 while True: 

2744 try: 

2745 yield _load_one_spickle_internal(stream) 

2746 except EOFError: 

2747 break 

2748 

2749 

2750def _load_all_spickle(stream): 

2751 return list(_iload_all_spickle(stream)) 

2752 

2753 

2754@expand_stream_args('w') 

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

2756 return _dump_all_spickle(objects, stream) 

2757 

2758 

2759@expand_stream_args('r') 

2760def iload_all_spickle(stream, **kwargs): 

2761 _iload_all_spickle(stream) 

2762 

2763 

2764@expand_stream_args('r') 

2765def load_all_spickle(stream, **kwargs): 

2766 kwargs.pop('filename', None) 

2767 return _load_all_spickle(stream, **kwargs) 

2768 

2769 

2770__all__ = guts_types + [ 

2771 'guts_types', 'TBase', 'ValidationError', 

2772 'ArgumentError', 'Defer', 

2773 'DefaultMaker', 'ObjectDefaultMaker', 

2774 'clone', 

2775 'dump', 'load', 

2776 'dump_all', 'load_all', 'iload_all', 

2777 'dump_xml', 'load_xml', 

2778 'dump_all_xml', 'load_all_xml', 'iload_all_xml', 

2779 'load_string', 

2780 'load_xml_string', 

2781 'make_typed_list_class', 'walk', 'zip_walk', 'path_to_str' 

2782]