1

2# -*- coding: utf-8 -*-

3

4 u'''Mutable, immutable and caching/memoizing properties and

5 deprecation decorators.

6

7 To enable C{DeprecationWarning}s from C{PyGeodesy}, set env var

8 C{PYGEODESY_WARNINGS} to a non-empty string I{AND} run C{python}

9 with command line option C{-X dev} or with one of the C{-W}

10 choices, see callable L{DeprecationWarnings} below.

11'''

12

13 from pygeodesy.basics import isclass as _isclass

14 from pygeodesy.errors import _AssertionError, _AttributeError, \

15 _xcallable, _xkwds, _xkwds_get

16 from pygeodesy.interns import MISSING, NN, _an_, _COMMASPACE_, \

17 _DEPRECATED_, _DOT_, _EQUALSPACED_, \

18 _immutable_, _invalid_, _module_, _N_A_, \

19 _not_, _SPACE_, _UNDER_, _DNL_ # PYCHOK used!

20# from pygeodesy.named import callname # _MODS, avoid circular

21 from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, \

22 _FOR_DOCS, _WARNINGS_X_DEV

23# from pygeodesy.streprs import Fmt # _MODS

24

25 from functools import wraps as _wraps

26

27__all__ = _ALL_LAZY.props

28__version__ = '24.09.02'

29

30_class_ = 'class'

31_dont_use_ = _DEPRECATED_ + ", don't use."

32_function_ = 'function'

33_has_been_ = 'has been' # PYCHOK used!

34_method_ = 'method'

35_not_an_inst_ = _not_(_an_, 'instance')

36

37

38 def _allPropertiesOf(Clas_or_inst, *Bases, **excls):

39 '''(INTERNAL) Yield all C{R/property/_RO}s at C{Clas_or_inst}

40 as specified in the C{Bases} arguments, except C{excls}.

41 '''

42 if _isclass(Clas_or_inst):

43 S = Clas_or_inst, # just this Clas

44 else: # class and super-classes of inst

45 try:

46 S = Clas_or_inst.__class__.__mro__[:-1] # not object

47 except AttributeError:

48 raise

49 S = () # not an inst

50 B = Bases or _PropertyBase

51 P = _property_RO___

52 for C in S:

53 for n, p in C.__dict__.items():

54 if isinstance(p, B) and p.name == n and not (

55 isinstance(p, P) or n in excls):

56 yield p

57

58

59 def _allPropertiesOf_n(n, Clas_or_inst, *Bases, **excls):

60 '''(INTERNAL) Assert the number of C{R/property/_RO}s at C{Clas_or_inst}.

61 '''

62 t = tuple(p.name for p in _allPropertiesOf(Clas_or_inst, *Bases, **excls))

63 if len(t) != n:

64 raise _AssertionError(_COMMASPACE_.join(t), Clas_or_inst,

65 txt=_COMMASPACE_(len(t), _not_(n)))

66 return t

67

68

69 def _hasProperty(inst, name, *Classes): # in .named._NamedBase._update

70 '''(INTERNAL) Check whether C{inst} has a C{P/property/_RO} by this C{name}.

71 '''

72 p = getattr(inst.__class__, name, None) # walks __class__.__mro__

73 return bool(p and isinstance(p, Classes or _PropertyBase)

74 and p.name == name)

75

76

77# def _isclass(obj):

78# '''(INTERNAL) Get and overwrite C{_isclass}.

79# '''

80# _MODS.getmodule(__name__)._isclass = f = _MODS.basics.isclass

81# return f(obj)

82

83

84 def _update_all(inst, *attrs, **Base_needed):

85 '''(INTERNAL) Zap all I{cached} L{property_RO}s, L{Property}s,

86 L{Property_RO}s and the named C{attrs} of an instance.

87

88 @return: The number of updates (C{int}), if any.

89 '''

90 if _isclass(inst):

91 raise _AssertionError(inst, txt=_not_an_inst_)

92 try:

93 d = inst.__dict__

94 except AttributeError:

95 return 0

96 u = len(d)

97 if u > _xkwds_get(Base_needed, needed=0):

98 B = _xkwds_get(Base_needed, Base=_PropertyBase)

99 for p in _allPropertiesOf(inst, B):

100 p._update(inst) # d.pop(p.name, None)

101

102 if attrs:

103 _update_attrs(inst, *attrs) # remove attributes from inst.__dict__

104 u -= len(d)

105 return u # updates

106

107

108# def _update_all_from(inst, other, **Base):

109# '''(INTERNAL) Update all I{cached} L{Property}s and

110# L{Property_RO}s of instance C{inst} from C{other}.

111#

112# @return: The number of updates (C{int}), if any.

113# '''

114# if _isclass(inst):

115# raise _AssertionError(inst, txt=_not_an_inst_)

116# try:

117# d = inst.__dict__

118# f = other.__dict__

119# except AttributeError:

120# return 0

121# u = len(f)

122# if u:

123# u = len(d)

124# B = _xkwds_get(Base, Base=_PropertyBase)

125# for p in _allPropertiesOf(inst, B):

126# p._update_from(inst, other)

127# u -= len(d)

128# return u # number of updates

129

130

131 def _update_attrs(inst, *attrs):

132 '''(INTERNAL) Zap all named C{attrs} of an instance.

133

134 @return: The number of updates (C{int}), if any.

135 '''

136 try:

137 d = inst.__dict__

138 except AttributeError:

139 return 0

140 u = len(d)

141 if u: # zap attrs from inst.__dict__

142 _p = d.pop

143 for a in attrs:

144 _ = _p(a, MISSING)

145# if _ is MISSING and not hasattr(inst, a):

146# n = _MODS.named.classname(inst, prefixed=True)

147# a = _DOT_(n, _SPACE_(a, _invalid_))

148# raise _AssertionError(a, txt=repr(inst))

149# _ = _p(a, None) # redo: hasattr side effect

150 u -= len(d)

151 # assert u >= 0

152 return u # number of named C{attrs} zapped

153

154

155 class _PropertyBase(property):

156 '''(INTERNAL) Base class for C{P/property/_RO}.

157 '''

158 def __init__(self, method, fget, fset, doc=NN):

159

160 _xcallable(getter=method, fget=fget)

161

162 self.method = method

163 self.name = method.__name__

164 d = doc or method.__doc__

165 if _FOR_DOCS and d:

166 self.__doc__ = d # PYCHOK no cover

167

168 property.__init__(self, fget, fset, self._fdel, d or _N_A_)

169

170 def _Error(self, kind, nameter, farg):

171 '''(INTERNAL) Return an C{AttributeError} instance.

172 '''

173 if farg:

174 n = _DOT_(self.name, nameter.__name__)

175 n = _SPACE_(n, farg.__name__)

176 else:

177 n = nameter

178 e = _SPACE_(kind, _MODS.named.classname(self))

179 return _AttributeError(e, txt=n)

180

181 def _fdel(self, inst):

182 '''Zap the I{cached/memoized} C{property} value.

183 '''

184 self._update(inst, None) # PYCHOK no cover

185

186 def _fget(self, inst):

187 '''Get and I{cache/memoize} the C{property} value.

188 '''

189 try: # to get the value cached in instance' __dict__

190 return inst.__dict__[self.name]

191 except KeyError:

192 # cache the value in the instance' __dict__

193 inst.__dict__[self.name] = val = self.method(inst)

194 return val

195

196 def _fset_error(self, inst, val):

197 '''Throws an C{AttributeError}, always.

198 '''

199 n = _MODS.named.classname(inst)

200 n = _DOT_(n, self.name)

201 n = _EQUALSPACED_(n, repr(val))

202 raise self._Error(_immutable_, n, None)

203

204 def _update(self, inst, *unused):

205 '''(INTERNAL) Zap the I{cached/memoized} C{inst.__dict__[name]} item.

206 '''

207 inst.__dict__.pop(self.name, None) # name, NOT _name

208

209 def _update_from(self, inst, other):

210 '''(INTERNAL) Copy a I{cached/memoized} C{inst.__dict__[name]} item

211 from C{other.__dict__[name]} if present, otherwise zap it.

212 '''

213 n = self.name # name, NOT _name

214 v = other.__dict__.get(n, MISSING)

215 if v is MISSING:

216 inst.__dict__.pop(n, None)

217 else:

218 inst.__dict__[n] = v

219

220 def deleter(self, fdel):

221 '''Throws an C{AttributeError}, always.

222 '''

223 raise self._Error(_invalid_, self.deleter, fdel)

224

225 def getter(self, fget):

226 '''Throws an C{AttributeError}, always.

227 '''

228 raise self._Error(_invalid_, self.getter, fget)

229

230 def setter(self, fset):

231 '''Throws an C{AttributeError}, always.

232 '''

233 raise self._Error(_immutable_, self.setter, fset)

234

235

236 class Property_RO(_PropertyBase):

237 # No __doc__ on purpose

238 def __init__(self, method, doc=NN): # PYCHOK expected

239 '''New I{immutable}, I{caching}, I{memoizing} C{property} I{Factory}

240 to be used as C{decorator}.

241

242 @arg method: The callable being decorated as this C{property}'s C{getter},

243 to be invoked only once.

244 @kwarg doc: Optional property documentation (C{str}).

245

246 @note: Like standard Python C{property} without a C{setter}, but with

247 a more descriptive error message when set.

248

249 @see: Python 3's U{functools.cached_property<https://docs.Python.org/3/

250 library/functools.html#functools.cached_property>} and U{-.cache

251 <https://Docs.Python.org/3/library/functools.html#functools.cache>}

252 to I{cache} or I{memoize} the property value.

253

254 @see: Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 636

255 Example 19-24 or 2022 p. 870 Example 22-28 and U{class

256 Property<https://docs.Python.org/3/howto/descriptor.html>}.

257 '''

258 _fget = method if _FOR_DOCS else self._fget # XXX force method.__doc__ to epydoc

259 _PropertyBase.__init__(self, method, _fget, self._fset_error)

260

261 def __get__(self, inst, *unused): # PYCHOK 2 vs 3 args

262 if inst is None:

263 return self

264 try: # to get the cached value immediately

265 return inst.__dict__[self.name]

266 except (AttributeError, KeyError):

267 return self._fget(inst)

268

269

270 class Property(Property_RO):

271 # No __doc__ on purpose

272 __init__ = Property_RO.__init__

273 '''New I{mutable}, I{caching}, I{memoizing} C{property} I{Factory}

274 to be used as C{decorator}.

275

276 @see: L{Property_RO} for more details.

277

278 @note: Unless and until the C{setter} is defined, this L{Property} behaves

279 like an I{immutable}, I{caching}, I{memoizing} L{Property_RO}.

280 '''

281

282 def setter(self, method):

283 '''Make this C{Property} I{mutable}.

284

285 @arg method: The callable being decorated as this C{Property}'s C{setter}.

286

287 @note: Setting a new property value always clears the previously I{cached}

288 or I{memoized} value I{after} invoking the B{C{method}}.

289 '''

290 def _fset(inst, val):

291 '''Set and I{cache}, I{memoize} the C{property} value.

292 '''

293 _ = method(inst, val)

294 self._update(inst) # un-cache this item

295

296 return self._setters(method, _fset)

297

298 def setter_(self, method):

299 '''Make this C{Property} I{mutable}.

300

301 @arg method: The callable being decorated as this C{Property}'s C{setter}

302 and returning the new property value to be I{cached} or

303 I{memoized}.

304 '''

305 def _fset(inst, val):

306 '''Set and I{cache}, I{memoize} the C{property} value.

307 '''

308 val = method(inst, val)

309 inst.__dict__[self.name] = val

310

311 return self._setters(method, _fset)

312

313 def _setters(self, method, _fset):

314 _xcallable(setter=method, fset=_fset)

315 if _FOR_DOCS: # XXX force method.__doc__ into epydoc

316 _PropertyBase.__init__(self, self.method, self.method, method)

317 else: # class Property <https://docs.Python.org/3/howto/descriptor.html>

318 _PropertyBase.__init__(self, self.method, self._fget, _fset)

319 return self

320

321

322 class property_RO(_PropertyBase):

323 # No __doc__ on purpose

324 _uname = NN

325

326 def __init__(self, method, doc=NN): # PYCHOK expected

327 '''New I{immutable}, standard C{property} to be used as C{decorator}.

328

329 @arg method: The callable being decorated as C{property}'s C{getter}.

330 @kwarg doc: Optional property documentation (C{str}).

331

332 @note: Like standard Python C{property} without a setter, but with

333 a more descriptive error message when set.

334

335 @see: L{Property_RO}.

336 '''

337 _PropertyBase.__init__(self, method, method, self._fset_error, doc=doc)

338 self._uname = NN(_UNDER_, self.name) # actual attr UNDER<name>

339

340 def _update(self, inst, *Clas): # PYCHOK signature

341 '''(INTERNAL) Zap the I{cached} C{B{inst}.__dict__[_name]} item.

342 '''

343 uname = self._uname

344 if uname in inst.__dict__:

345 if Clas: # overrides inst.__class__

346 d = Clas[0].__dict__.get(uname, MISSING)

347 else:

348 d = getattr(inst.__class__, uname, MISSING)

349# if d is MISSING: # XXX superfluous

350# for c in inst.__class__.__mro__[:-1]:

351# if uname in c.__dict__:

352# d = c.__dict__[uname]

353# break

354 if d is None: # remove inst value

355 inst.__dict__.pop(uname)

356

357

358 class _property_RO___(_PropertyBase):

359 # No __doc__ on purpose

360

361 def __init__(self, method, doc=NN): # PYCHOK expected

362 '''New C{property_ROnce} or C{property_ROver}, holding a singleton value as

363 class attribute for all instances of that class and overwriting C{self},

364 the C{property_ROver} instance in the 1st invokation.

365

366 @see: L{property_RO} for further details.

367 '''

368 _PropertyBase.__init__(self, method, self._fget, self._fset_error, doc=doc)

369

370 def _fdel(self, unused): # PYCHOK no cover

371 '''Silently ignored, always.

372 '''

373 pass

374

375 def _update(self, *unused): # PYCHOK signature

376 '''(INTERNAL) No-op, ignore updates.

377 '''

378 pass

379

380

381 class property_ROnce(_property_RO___):

382 # No __doc__ on purpose

383

384 def _fget(self, inst):

385 '''Get the C{property} value, only I{once} and memoize/cache it.

386 '''

387 try:

388 v = self._val

389 except AttributeError:

390 v = self._val = self.method(inst)

391 return v

392

393

394 class property_ROver(_property_RO___):

395 # No __doc__ on purpose

396

397 def _fget(self, inst):

398 '''Get the C{property} value I{once} and overwrite C{self},

399 this C{property} instance in the (super-)class of C{self}

400 where this property is define as a L{property_ROver}.

401 '''

402 v = self.method(inst)

403 n = self.name

404 C = inst.__class__

405 for c in C.__mro__: # [:-1]

406 if getattr(c, n, None) is self:

407 setattr(c, n, v) # overwrite property_ROver

408 break

409 else:

410 n = _DOT_(C.__name__, n)

411 raise _AssertionError(_EQUALSPACED_(n, v))

412 return v

413

414

415 class _NamedProperty(property): # in .named

416 '''Class C{property} with a C{.name} attribute.

417 '''

418 @Property_RO

419 def name(self):

420 '''Get the name of this C{property} (C{str}).

421 '''

422 return self.fget.__name__

423

424

425 def property_doc_(doc):

426 '''Decorator for a standard C{property} with basic documentation.

427

428 @arg doc: The property documentation (C{str}).

429

430 @example:

431

432 >>>class Clas(object):

433 >>>

434 >>> @property_doc_("documentation text.")

435 >>> def name(self):

436 >>> ...

437 >>>

438 >>> @name.setter

439 >>> def name(self, value):

440 >>> ...

441 '''

442 # See Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 212+

443 # Example 7-23 or 2022 p. 331+ Example 9-22 and <https://

444 # Python-3-Patterns-Idioms-Test.ReadTheDocs.io/en/latest/PythonDecorators.html>

445

446 def _documented_property(method):

447 '''(INTERNAL) Return the documented C{property}.

448 '''

449 t = 'get and set' if doc.startswith(_SPACE_) else NN

450 return _NamedProperty(method, None, None, NN('Property to ', t, doc))

451

452 return _documented_property

453

454

455 def _deprecated(call, kind, qual_d):

456 '''(INTERNAL) Decorator for DEPRECATED functions, methods, etc.

457

458 @see: Brett Slatkin, "Effective Python", 2019 page 105, 2nd

459 ed, Addison-Wesley.

460 '''

461 doc = _docof(call)

462

463 @_wraps(call) # PYCHOK self?

464 def _deprecated_call(*args, **kwds):

465 if qual_d: # function

466 q = qual_d

467 elif args: # method

468 q = _qualified(args[0], call.__name__)

469 else: # PYCHOK no cover

470 q = call.__name__

471 _throwarning(kind, q, doc)

472 return call(*args, **kwds)

473

474 return _deprecated_call

475

476

477 def deprecated_class(cls_or_class):

478 '''Use inside __new__ or __init__ of a DEPRECATED class.

479

480 @arg cls_or_class: The class (C{cls} or C{Class}).

481

482 @note: NOT a decorator!

483 '''

484 if _WARNINGS_X_DEV:

485 q = _DOT_(cls_or_class.__module__, cls_or_class.__name__)

486 _throwarning(_class_, q, cls_or_class.__doc__)

487

488

489 def deprecated_function(call):

490 '''Decorator for a DEPRECATED function.

491

492 @arg call: The deprecated function (C{callable}).

493

494 @return: The B{C{call}} DEPRECATED.

495 '''

496 return _deprecated(call, _function_, _DOT_(

497 call.__module__, call.__name__)) if \

498 _WARNINGS_X_DEV else call

499

500

501 def deprecated_method(call):

502 '''Decorator for a DEPRECATED method.

503

504 @arg call: The deprecated method (C{callable}).

505

506 @return: The B{C{call}} DEPRECATED.

507 '''

508 return _deprecated(call, _method_, NN) if _WARNINGS_X_DEV else call

509

510

511 def _deprecated_module(name): # PYCHOK no cover

512 '''(INTERNAL) Callable within a DEPRECATED module.

513 '''

514 if _WARNINGS_X_DEV:

515 _throwarning(_module_, name, _dont_use_)

516

517

518 if _WARNINGS_X_DEV:

519 class deprecated_property(_PropertyBase):

520 '''Decorator for a DEPRECATED C{property} or C{Property}.

521 '''

522 def __init__(self, method):

523 '''Decorator for a DEPRECATED C{property} or C{Property} getter.

524 '''

525 doc = _docof(method)

526

527 def _fget(inst): # PYCHOK no cover

528 '''Get the C{property} or C{Property} value.

529 '''

530 q = _qualified(inst, self.name)

531 _throwarning(property.__name__, q, doc)

532 return self.method(inst) # == method

533

534 _PropertyBase.__init__(self, method, _fget, None, doc=doc)

535

536 def setter(self, method):

537 '''Decorator for a DEPRECATED C{property} or C{Property} setter.

538

539 @arg method: The callable being decorated as this C{Property}'s C{setter}.

540

541 @note: Setting a new property value always clears the previously I{cached}

542 or I{memoized} value I{after} invoking the B{C{method}}.

543 '''

544 if not callable(method):

545 _PropertyBase.setter(self, method) # PYCHOK no cover

546

547 if _FOR_DOCS: # XXX force method.__doc__ into epydoc

548 _PropertyBase.__init__(self, self.method, self.method, method)

549 else:

550

551 def _fset(inst, val):

552 '''Set the C{property} or C{Property} value.

553 '''

554 q = _qualified(inst, self.name)

555 _throwarning(property.__name__, q, _docof(method))

556 method(inst, val)

557 # self._update(inst) # un-cache this item

558

559 # class Property <https://docs.Python.org/3/howto/descriptor.html>

560 _PropertyBase.__init__(self, self.method, self._fget, _fset)

561 return self

562

563 else: # PYCHOK no cover

564 class deprecated_property(property): # PYCHOK expected

565 '''Decorator for a DEPRECATED C{property} or C{Property}.

566 '''

567 pass

568

569 deprecated_Property = deprecated_property

570

571

572 def deprecated_Property_RO(method):

573 '''Decorator for a DEPRECATED L{Property_RO}.

574

575 @arg method: The C{Property_RO.fget} method (C{callable}).

576

577 @return: The B{C{method}} DEPRECATED.

578 '''

579 return _deprecated_RO(method, Property_RO)

580

581

582 def deprecated_property_RO(method):

583 '''Decorator for a DEPRECATED L{property_RO}.

584

585 @arg method: The C{property_RO.fget} method (C{callable}).

586

587 @return: The B{C{method}} DEPRECATED.

588 '''

589 return _deprecated_RO(method, property_RO)

590

591

592 def _deprecated_RO(method, _RO):

593 '''(INTERNAL) Create a DEPRECATED C{property_RO} or C{Property_RO}.

594 '''

595 doc = _docof(method)

596

597 if _WARNINGS_X_DEV:

598

599 class _Deprecated_RO(_PropertyBase):

600 __doc__ = doc

601

602 def __init__(self, method):

603 _PropertyBase.__init__(self, method, self._fget, self._fset_error, doc=doc)

604

605 def _fget(self, inst): # PYCHOK no cover

606 q = _qualified(inst, self.name)

607 _throwarning(_RO.__name__, q, doc)

608 return self.method(inst)

609

610 return _Deprecated_RO(method)

611 else: # PYCHOK no cover

612 return _RO(method, doc=doc)

613

614

615 def _docof(obj):

616 '''(INTERNAL) Get uniform DEPRECATED __doc__ string.

617 '''

618 try:

619 d = obj.__doc__.strip()

620 i = d.find(_DEPRECATED_)

621 except AttributeError:

622 i = -1

623 return _DOT_(_DEPRECATED_, NN) if i < 0 else d[i:]

624

625

626 def _qualified(inst, name):

627 '''(INTERNAL) Fully qualify a name.

628 '''

629 # _DOT_(inst.classname, name), not _DOT_(inst.named4, name)

630 c = inst.__class__

631 q = _DOT_(c.__module__, c.__name__, name)

632 return q

633

634

635 class DeprecationWarnings(object):

636 '''(INTERNAL) Handle C{DeprecationWaring}s.

637 '''

638 _Warnings = 0

639

640 def __call__(self): # for backward compatibility

641 '''Have any C{DeprecationWarning}s been reported or raised?

642

643 @return: The number of C{DeprecationWarning}s (C{int}) so

644 far or C{None} if not enabled.

645

646 @note: To get C{DeprecationWarning}s if any, run C{python}

647 with env var C{PYGEODESY_WARNINGS} set to a non-empty

648 string I{AND} use C{python[3]} command line option

649 C{-X dev}, C{-W always} or C{-W error}, etc.

650 '''

651 return self.Warnings

652

653 def throw(self, kind, name, doc, **stacklevel): # stacklevel=3

654 '''Report or raise a C{DeprecationWarning}.

655 '''

656 line = doc.split(_DNL_, 1)[0].strip()

657 name = _MODS.streprs.Fmt.CURLY(L=name)

658 text = _SPACE_(kind, name, _has_been_, *line.split())

659 kwds = _xkwds(stacklevel, stacklevel=3)

660 # XXX invoke warn or raise DeprecationWarning(text)

661 self._warn(text, category=DeprecationWarning, **kwds)

662 self._Warnings += 1

663

664 @Property_RO

665 def _warn(self):

666 '''Get Python's C{warnings.warn}.

667 '''

668 from warnings import warn

669 return warn

670

671 @property_RO

672 def Warnings(self):

673 '''Get the number of C{DeprecationWarning}s (C{int}) so

674 far or C{None} if not enabled.

675 '''

676 return self._Warnings if _WARNINGS_X_DEV else None

677

678 DeprecationWarnings = DeprecationWarnings() # PYCHOK singleton

679_throwarning = DeprecationWarnings.throw

680

681# **) MIT License

682#

683# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.

684#

685# Permission is hereby granted, free of charge, to any person obtaining a

686# copy of this software and associated documentation files (the "Software"),

687# to deal in the Software without restriction, including without limitation

688# the rights to use, copy, modify, merge, publish, distribute, sublicense,

689# and/or sell copies of the Software, and to permit persons to whom the

690# Software is furnished to do so, subject to the following conditions:

691#

692# The above copyright notice and this permission notice shall be included

693# in all copies or substantial portions of the Software.

694#

695# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS

696# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

697# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL

698# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR

699# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,

700# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

701# OTHER DEALINGS IN THE SOFTWARE.