Coverage for pygeodesy/props.py: 98%
241 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-10 16:55 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-10 16:55 -0500
2# -*- coding: utf-8 -*-
4u'''Mutable, immutable and caching/memoizing properties and
5deprecation decorators.
7To enable C{DeprecationWarning}s from C{PyGeodesy}, set env var
8C{PYGEODESY_WARNINGS} to a non-empty string I{AND} run C{python}
9with command line option C{-X dev} or with one of the C{-W}
10choices, see callable L{DeprecationWarnings} below.
11'''
13from pygeodesy.basics import isclass as _isclass
14from pygeodesy.errors import _AssertionError, _AttributeError, \
15 _xcallable, _xkwds_get
16# from pygeodesy.internals import _tailof # from .lazily
17from pygeodesy.interns import MISSING, NN, _an_, _COMMASPACE_, \
18 _DEPRECATED_, _DOT_, _EQUALSPACED_, \
19 _immutable_, _invalid_, _module_, \
20 _N_A_, _NL_, _not_, _SPACE_, _UNDER_
21# from pygeodesy.named import callname # _MODS, avoid circular
22from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, \
23 _FOR_DOCS, _WARNINGS_X_DEV, _tailof
24# from pygeodesy.streprs import Fmt # _MODS
26from functools import wraps as _wraps
28__all__ = _ALL_LAZY.props
29__version__ = '24.11.06'
31_class_ = 'class'
32_DNL_ = _NL_ * 2 # PYCHOK used!
33_dont_use_ = _DEPRECATED_ + ", don't use."
34_function_ = 'function'
35_has_been_ = 'has been' # PYCHOK used!
36_method_ = 'method'
37_not_an_inst_ = _not_(_an_, 'instance')
40def _allPropertiesOf(Clas_or_inst, *Bases, **excls):
41 '''(INTERNAL) Yield all C{R/property/_RO}s at C{Clas_or_inst}
42 as specified in the C{Bases} arguments, except C{excls}.
43 '''
44 if _isclass(Clas_or_inst):
45 S = Clas_or_inst, # just this Clas
46 else: # class and super-classes of inst
47 try:
48 S = Clas_or_inst.__class__.__mro__[:-1] # not object
49 except AttributeError:
50 raise
51 S = () # not an inst
52 B = Bases or _PropertyBase
53 P = _property_RO___
54 for C in S:
55 for n, p in C.__dict__.items():
56 if isinstance(p, B) and p.name == n and not (
57 isinstance(p, P) or n in excls):
58 yield p
61def _allPropertiesOf_n(n, Clas_or_inst, *Bases, **excls):
62 '''(INTERNAL) Assert the number of C{R/property/_RO}s at C{Clas_or_inst}.
63 '''
64 t = tuple(p.name for p in _allPropertiesOf(Clas_or_inst, *Bases, **excls))
65 if len(t) != n:
66 raise _AssertionError(_COMMASPACE_.join(t), Clas_or_inst,
67 txt=_COMMASPACE_(len(t), _not_(n)))
68 return t
71def _hasProperty(inst, name, *Classes): # in .named._NamedBase._update
72 '''(INTERNAL) Check whether C{inst} has a C{P/property/_RO} by this C{name}.
73 '''
74 p = getattr(inst.__class__, name, None) # walks __class__.__mro__
75 return bool(p and isinstance(p, Classes or _PropertyBase)
76 and p.name == name)
79# def _isclass(obj):
80# '''(INTERNAL) Get and overwrite C{_isclass}.
81# '''
82# _MODS.getmodule(__name__)._isclass = f = _MODS.basics.isclass
83# return f(obj)
86def _update_all(inst, *attrs, **Base_needed):
87 '''(INTERNAL) Zap all I{cached} L{property_RO}s, L{Property}s,
88 L{Property_RO}s and the named C{attrs} of an instance.
90 @return: The number of updates (C{int}), if any.
91 '''
92 if _isclass(inst):
93 raise _AssertionError(inst, txt=_not_an_inst_)
94 try:
95 d = inst.__dict__
96 except AttributeError:
97 return 0
98 u = len(d)
99 if u > _xkwds_get(Base_needed, needed=0):
100 B = _xkwds_get(Base_needed, Base=_PropertyBase)
101 for p in _allPropertiesOf(inst, B):
102 p._update(inst) # d.pop(p.name, None)
104 if attrs:
105 _update_attrs(inst, *attrs) # remove attributes from inst.__dict__
106 u -= len(d)
107 return u # updates
110# def _update_all_from(inst, other, **Base):
111# '''(INTERNAL) Update all I{cached} L{Property}s and
112# L{Property_RO}s of instance C{inst} from C{other}.
113#
114# @return: The number of updates (C{int}), if any.
115# '''
116# if _isclass(inst):
117# raise _AssertionError(inst, txt=_not_an_inst_)
118# try:
119# d = inst.__dict__
120# f = other.__dict__
121# except AttributeError:
122# return 0
123# u = len(f)
124# if u:
125# u = len(d)
126# B = _xkwds_get(Base, Base=_PropertyBase)
127# for p in _allPropertiesOf(inst, B):
128# p._update_from(inst, other)
129# u -= len(d)
130# return u # number of updates
133def _update_attrs(inst, *attrs):
134 '''(INTERNAL) Zap all named C{attrs} of an instance.
136 @return: The number of updates (C{int}), if any.
137 '''
138 try:
139 d = inst.__dict__
140 except AttributeError:
141 return 0
142 u = len(d)
143 if u: # zap attrs from inst.__dict__
144 _p = d.pop
145 for a in attrs:
146 _ = _p(a, MISSING)
147# if _ is MISSING and not hasattr(inst, a):
148# n = _MODS.named.classname(inst, prefixed=True)
149# a = _DOT_(n, _SPACE_(a, _invalid_))
150# raise _AssertionError(a, txt=repr(inst))
151# _ = _p(a, None) # redo: hasattr side effect
152 u -= len(d)
153 # assert u >= 0
154 return u # number of named C{attrs} zapped
157class _PropertyBase(property):
158 '''(INTERNAL) Base class for C{P/property/_RO}.
159 '''
160 def __init__(self, method, fget, fset, doc=NN):
162 _xcallable(getter=method, fget=fget)
164 self.method = method
165 self.name = method.__name__
166 d = doc or method.__doc__
167 if _FOR_DOCS and d:
168 self.__doc__ = d # PYCHOK no cover
170 property.__init__(self, fget, fset, self._fdel, d or _N_A_)
172 def _Error(self, kind, nameter, farg):
173 '''(INTERNAL) Return an C{AttributeError} instance.
174 '''
175 if farg:
176 n = _DOT_(self.name, nameter.__name__)
177 n = _SPACE_(n, farg.__name__)
178 else:
179 n = nameter
180 e = _SPACE_(kind, _MODS.named.classname(self))
181 return _AttributeError(e, txt=n)
183 def _fdel(self, inst):
184 '''Zap the I{cached/memoized} C{property} value.
185 '''
186 self._update(inst, None) # PYCHOK no cover
188 def _fget(self, inst):
189 '''Get and I{cache/memoize} the C{property} value.
190 '''
191 try: # to get the value cached in instance' __dict__
192 return inst.__dict__[self.name]
193 except KeyError:
194 # cache the value in the instance' __dict__
195 inst.__dict__[self.name] = val = self.method(inst)
196 return val
198 def _fset_error(self, inst, val):
199 '''Throws an C{AttributeError}, always.
200 '''
201 n = _MODS.named.classname(inst)
202 n = _DOT_(n, self.name)
203 n = _EQUALSPACED_(n, repr(val))
204 raise self._Error(_immutable_, n, None)
206 def _update(self, inst, *unused):
207 '''(INTERNAL) Zap the I{cached/memoized} C{inst.__dict__[name]} item.
208 '''
209 inst.__dict__.pop(self.name, None) # name, NOT _name
211 def _update_from(self, inst, other):
212 '''(INTERNAL) Copy a I{cached/memoized} C{inst.__dict__[name]} item
213 from C{other.__dict__[name]} if present, otherwise zap it.
214 '''
215 n = self.name # name, NOT _name
216 v = other.__dict__.get(n, MISSING)
217 if v is MISSING:
218 inst.__dict__.pop(n, None)
219 else:
220 inst.__dict__[n] = v
222 def deleter(self, fdel):
223 '''Throws an C{AttributeError}, always.
224 '''
225 raise self._Error(_invalid_, self.deleter, fdel)
227 def getter(self, fget):
228 '''Throws an C{AttributeError}, always.
229 '''
230 raise self._Error(_invalid_, self.getter, fget)
232 def setter(self, fset):
233 '''Throws an C{AttributeError}, always.
234 '''
235 raise self._Error(_immutable_, self.setter, fset)
238class Property_RO(_PropertyBase):
239 # No __doc__ on purpose
240 def __init__(self, method, doc=NN): # PYCHOK expected
241 '''New I{immutable}, I{caching}, I{memoizing} C{property} I{Factory}
242 to be used as C{decorator}.
244 @arg method: The callable being decorated as this C{property}'s C{getter},
245 to be invoked only once.
246 @kwarg doc: Optional property documentation (C{str}).
248 @note: Like standard Python C{property} without a C{setter}, but with
249 a more descriptive error message when set.
251 @see: Python 3's U{functools.cached_property<https://docs.Python.org/3/
252 library/functools.html#functools.cached_property>} and U{-.cache
253 <https://Docs.Python.org/3/library/functools.html#functools.cache>}
254 to I{cache} or I{memoize} the property value.
256 @see: Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 636
257 Example 19-24 or 2022 p. 870 Example 22-28 and U{class
258 Property<https://docs.Python.org/3/howto/descriptor.html>}.
259 '''
260 _fget = method if _FOR_DOCS else self._fget # XXX force method.__doc__ to epydoc
261 _PropertyBase.__init__(self, method, _fget, self._fset_error)
263 def __get__(self, inst, *unused): # PYCHOK 2 vs 3 args
264 if inst is None:
265 return self
266 try: # to get the cached value immediately
267 return inst.__dict__[self.name]
268 except (AttributeError, KeyError):
269 return self._fget(inst)
272class Property(Property_RO):
273 # No __doc__ on purpose
274 __init__ = Property_RO.__init__
275 '''New I{mutable}, I{caching}, I{memoizing} C{property} I{Factory}
276 to be used as C{decorator}.
278 @see: L{Property_RO} for more details.
280 @note: Unless and until the C{setter} is defined, this L{Property} behaves
281 like an I{immutable}, I{caching}, I{memoizing} L{Property_RO}.
282 '''
284 def setter(self, method):
285 '''Make this C{Property} I{mutable}.
287 @arg method: The callable being decorated as this C{Property}'s C{setter}.
289 @note: Setting a new property value always clears the previously I{cached}
290 or I{memoized} value I{after} invoking the B{C{method}}.
291 '''
292 def _fset(inst, val):
293 '''Set and I{cache}, I{memoize} the C{property} value.
294 '''
295 _ = method(inst, val)
296 self._update(inst) # un-cache this item
298 return self._setters(method, _fset)
300 def setter_(self, method):
301 '''Make this C{Property} I{mutable}.
303 @arg method: The callable being decorated as this C{Property}'s C{setter}
304 and returning the new property value to be I{cached} or
305 I{memoized}.
306 '''
307 def _fset(inst, val):
308 '''Set and I{cache}, I{memoize} the C{property} value.
309 '''
310 val = method(inst, val)
311 inst.__dict__[self.name] = val
313 return self._setters(method, _fset)
315 def _setters(self, method, _fset):
316 _xcallable(setter=method, fset=_fset)
317 if _FOR_DOCS: # XXX force method.__doc__ into epydoc
318 _PropertyBase.__init__(self, self.method, self.method, method)
319 else: # class Property <https://docs.Python.org/3/howto/descriptor.html>
320 _PropertyBase.__init__(self, self.method, self._fget, _fset)
321 return self
324class property_RO(_PropertyBase):
325 # No __doc__ on purpose
326 _uname = NN
328 def __init__(self, method, doc=NN): # PYCHOK expected
329 '''New I{immutable}, standard C{property} to be used as C{decorator}.
331 @arg method: The callable being decorated as C{property}'s C{getter}.
332 @kwarg doc: Optional property documentation (C{str}).
334 @note: Like standard Python C{property} without a setter, but with
335 a more descriptive error message when set.
337 @see: L{Property_RO}.
338 '''
339 _PropertyBase.__init__(self, method, method, self._fset_error, doc=doc)
340 self._uname = NN(_UNDER_, self.name) # actual attr UNDER<name>
342 def _update(self, inst, *Clas): # PYCHOK signature
343 '''(INTERNAL) Zap the I{cached} C{B{inst}.__dict__[_name]} item.
344 '''
345 uname = self._uname
346 if uname in inst.__dict__:
347 if Clas: # overrides inst.__class__
348 d = Clas[0].__dict__.get(uname, MISSING)
349 else:
350 d = getattr(inst.__class__, uname, MISSING)
351# if d is MISSING: # XXX superfluous
352# for c in inst.__class__.__mro__[:-1]:
353# if uname in c.__dict__:
354# d = c.__dict__[uname]
355# break
356 if d is None: # remove inst value
357 inst.__dict__.pop(uname)
360class _property_RO___(_PropertyBase):
361 # No __doc__ on purpose
363 def __init__(self, method, doc=NN): # PYCHOK expected
364 '''New C{property_ROnce} or C{property_ROver}, holding a singleton value as
365 class attribute for all instances of that class and overwriting C{self},
366 the C{property_ROver} instance in the 1st invokation.
368 @see: L{property_RO} for further details.
369 '''
370 _PropertyBase.__init__(self, method, self._fget, self._fset_error, doc=doc)
372 def _fdel(self, *unused): # PYCHOK no cover
373 '''Silently ignored, always.
374 '''
375 pass
377 def _update(self, *unused): # PYCHOK signature
378 '''(INTERNAL) No-op, ignore updates.
379 '''
380 pass
383class property_ROnce(_property_RO___):
384 # No __doc__ on purpose
386 def _fget(self, inst):
387 '''Get the C{property} value, only I{once} and memoize/cache it.
388 '''
389 try:
390 v = self._val
391 except AttributeError:
392 v = self._val = self.method(inst)
393 return v
396class property_ROver(_property_RO___):
397 # No __doc__ on purpose
399 def _fget(self, inst):
400 '''Get the C{property} value I{once} and overwrite C{self},
401 this C{property} instance in the (super-)class of C{self}
402 where this property is define as a L{property_ROver}.
403 '''
404 v = self.method(inst)
405 n = self.name
406 C = inst.__class__
407 for c in C.__mro__: # [:-1]
408 if getattr(c, n, None) is self:
409 setattr(c, n, v) # overwrite property_ROver
410 break
411 else:
412 n = _DOT_(C.__name__, n)
413 raise _AssertionError(_EQUALSPACED_(n, v))
414 return v
417class _NamedProperty(property): # in .named
418 '''Class C{property} with a C{.name} attribute.
419 '''
420 @Property_RO
421 def name(self):
422 '''Get the name of this C{property} (C{str}).
423 '''
424 return self.fget.__name__
427def property_doc_(doc):
428 '''Decorator for a standard C{property} with basic documentation.
430 @arg doc: The property documentation (C{str}).
432 @example:
434 >>>class Clas(object):
435 >>>
436 >>> @property_doc_("documentation text.")
437 >>> def name(self):
438 >>> ...
439 >>>
440 >>> @name.setter
441 >>> def name(self, value):
442 >>> ...
443 '''
444 # See Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 212+
445 # Example 7-23 or 2022 p. 331+ Example 9-22 and <https://
446 # Python-3-Patterns-Idioms-Test.ReadTheDocs.io/en/latest/PythonDecorators.html>
448 def _documented_property(method):
449 '''(INTERNAL) Return the documented C{property}.
450 '''
451 t = 'get and set' if doc.startswith(_SPACE_) else NN
452 return _NamedProperty(method, None, None, NN('Property to ', t, doc))
454 return _documented_property
457def _deprecated(call, kind, qual_d):
458 '''(INTERNAL) Decorator for DEPRECATED functions, methods, etc.
460 @see: Brett Slatkin, "Effective Python", 2019 page 105, 2nd
461 ed, Addison-Wesley.
462 '''
463 doc = _docof(call)
465 @_wraps(call) # PYCHOK self?
466 def _deprecated_call(*args, **kwds):
467 if qual_d: # function
468 q = qual_d
469 elif args: # method
470 q = _qualified(args[0], call.__name__)
471 else: # PYCHOK no cover
472 q = call.__name__
473 _throwarning(kind, q, doc)
474 return call(*args, **kwds)
476 return _deprecated_call
479def deprecated_class(cls_or_class):
480 '''Use inside __new__ or __init__ of a DEPRECATED class.
482 @arg cls_or_class: The class (C{cls} or C{Class}).
484 @note: NOT a decorator!
485 '''
486 if _WARNINGS_X_DEV:
487 q = _DOT_(cls_or_class.__module__, cls_or_class.__name__)
488 _throwarning(_class_, q, cls_or_class.__doc__)
491def deprecated_function(call):
492 '''Decorator for a DEPRECATED function.
494 @arg call: The deprecated function (C{callable}).
496 @return: The B{C{call}} DEPRECATED.
497 '''
498 return _deprecated(call, _function_, _DOT_(
499 call.__module__, call.__name__)) if \
500 _WARNINGS_X_DEV else call
503def deprecated_method(call):
504 '''Decorator for a DEPRECATED method.
506 @arg call: The deprecated method (C{callable}).
508 @return: The B{C{call}} DEPRECATED.
509 '''
510 return _deprecated(call, _method_, NN) if _WARNINGS_X_DEV else call
513def _deprecated_module(name): # PYCHOK no cover
514 '''(INTERNAL) Callable within a DEPRECATED module.
515 '''
516 if _WARNINGS_X_DEV:
517 _throwarning(_module_, name, _dont_use_)
520if _WARNINGS_X_DEV:
521 class deprecated_property(_PropertyBase):
522 '''Decorator for a DEPRECATED C{property} or C{Property}.
523 '''
524 def __init__(self, method):
525 '''Decorator for a DEPRECATED C{property} or C{Property} getter.
526 '''
527 doc = _docof(method)
529 def _fget(inst): # PYCHOK no cover
530 '''Get the C{property} or C{Property} value.
531 '''
532 q = _qualified(inst, self.name)
533 _throwarning(property.__name__, q, doc)
534 return self.method(inst) # == method
536 _PropertyBase.__init__(self, method, _fget, None, doc=doc)
538 def setter(self, method):
539 '''Decorator for a DEPRECATED C{property} or C{Property} setter.
541 @arg method: The callable being decorated as this C{Property}'s C{setter}.
543 @note: Setting a new property value always clears the previously I{cached}
544 or I{memoized} value I{after} invoking the B{C{method}}.
545 '''
546 if not callable(method):
547 _PropertyBase.setter(self, method) # PYCHOK no cover
549 if _FOR_DOCS: # XXX force method.__doc__ into epydoc
550 _PropertyBase.__init__(self, self.method, self.method, method)
551 else:
553 def _fset(inst, val):
554 '''Set the C{property} or C{Property} value.
555 '''
556 q = _qualified(inst, self.name)
557 _throwarning(property.__name__, q, _docof(method))
558 method(inst, val)
559 # self._update(inst) # un-cache this item
561 # class Property <https://docs.Python.org/3/howto/descriptor.html>
562 _PropertyBase.__init__(self, self.method, self._fget, _fset)
563 return self
565else: # PYCHOK no cover
566 class deprecated_property(property): # PYCHOK expected
567 '''Decorator for a DEPRECATED C{property} or C{Property}.
568 '''
569 pass
571deprecated_Property = deprecated_property
574def deprecated_Property_RO(method):
575 '''Decorator for a DEPRECATED L{Property_RO}.
577 @arg method: The C{Property_RO.fget} method (C{callable}).
579 @return: The B{C{method}} DEPRECATED.
580 '''
581 return _deprecated_RO(method, Property_RO)
584def deprecated_property_RO(method):
585 '''Decorator for a DEPRECATED L{property_RO}.
587 @arg method: The C{property_RO.fget} method (C{callable}).
589 @return: The B{C{method}} DEPRECATED.
590 '''
591 return _deprecated_RO(method, property_RO)
594def _deprecated_RO(method, _RO):
595 '''(INTERNAL) Create a DEPRECATED C{property_RO} or C{Property_RO}.
596 '''
597 doc = _docof(method)
599 if _WARNINGS_X_DEV:
601 class _Deprecated_RO(_PropertyBase):
602 __doc__ = doc
604 def __init__(self, method):
605 _PropertyBase.__init__(self, method, self._fget, self._fset_error, doc=doc)
607 def _fget(self, inst): # PYCHOK no cover
608 q = _qualified(inst, self.name)
609 _throwarning(_RO.__name__, q, doc)
610 return self.method(inst)
612 return _Deprecated_RO(method)
613 else: # PYCHOK no cover
614 return _RO(method, doc=doc)
617def _docof(obj):
618 '''(INTERNAL) Get uniform DEPRECATED __doc__ string.
619 '''
620 try:
621 d = obj.__doc__.strip()
622 i = d.find(_DEPRECATED_)
623 except AttributeError:
624 i = -1
625 return _DOT_(_DEPRECATED_, NN) if i < 0 else d[i:]
628def _qualified(inst, name):
629 '''(INTERNAL) Fully qualify a name.
630 '''
631 # _DOT_(inst.classname, name), not _DOT_(inst.named4, name)
632 c = inst.__class__
633 q = _DOT_(c.__module__, c.__name__, name)
634 return q
637class DeprecationWarnings(object):
638 '''(INTERNAL) Handle C{DeprecationWaring}s.
639 '''
640 _Warnings = 0
642 def __call__(self): # for backward compatibility
643 '''Have any C{DeprecationWarning}s been reported or raised?
645 @return: The number of C{DeprecationWarning}s (C{int}) so
646 far or C{None} if not enabled.
648 @note: To get C{DeprecationWarning}s if any, run C{python}
649 with env var C{PYGEODESY_WARNINGS} set to a non-empty
650 string I{AND} use C{python[3]} command line option
651 C{-X dev}, C{-W always} or C{-W error}, etc.
652 '''
653 return self.Warnings
655 @property_ROver
656 def _Fmt(self):
657 '''Get C{streprs.Fmt}, I{once}.
658 '''
659 return _MODS.streprs.Fmt
661 @property_ROver
662 def _stacklevel3(self):
663 '''Get C{dict(stacklevel=3)}, I{once}.
664 '''
665 return dict(stacklevel=3)
667 def throw(self, kind, name, doc, **stacklevel): # stacklevel=3
668 '''Report or raise a C{DeprecationWarning}.
670 @arg kind: Warning kind (C{str}), C{"method"}, C{"funtion"}, ...
671 @arg name: Qualified name (C{str}) of B{C{kind}}.
672 @arg doc: The __doc__ (C{str}) of B{C{kind}}, C{"DEPRECATED ...}.
673 '''
674 link = _tailof(name) or name
675 if link is not name: # make "link<name>"
676 link = self._Fmt.ANGLE(link, name)
677 link = self._Fmt.CURLY(L=link) # "L{link}"
678 text = doc.split(_DNL_, 1)[0].strip()
679 text = _SPACE_(kind, link, _has_been_, *text.split())
680 kwds = stacklevel if stacklevel else self._stacklevel3
681 # XXX invoke warn or raise DeprecationWarning(text)
682 self._warn(text, category=DeprecationWarning, **kwds)
683 self._Warnings += 1
685 @property_ROver
686 def _warn(self):
687 '''Get Python's C{warnings.warn} function, I{once}.
688 '''
689 from warnings import warn as w
690 return w
692 @property_RO
693 def Warnings(self):
694 '''Get the number of C{DeprecationWarning}s (C{int}) so
695 far or C{None} if not enabled.
696 '''
697 return self._Warnings if _WARNINGS_X_DEV else None
699DeprecationWarnings = DeprecationWarnings() # PYCHOK singleton
700_throwarning = DeprecationWarnings.throw
702# **) MIT License
703#
704# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
705#
706# Permission is hereby granted, free of charge, to any person obtaining a
707# copy of this software and associated documentation files (the "Software"),
708# to deal in the Software without restriction, including without limitation
709# the rights to use, copy, modify, merge, publish, distribute, sublicense,
710# and/or sell copies of the Software, and to permit persons to whom the
711# Software is furnished to do so, subject to the following conditions:
712#
713# The above copyright notice and this permission notice shall be included
714# in all copies or substantial portions of the Software.
715#
716# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
717# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
718# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
719# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
720# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
721# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
722# OTHER DEALINGS IN THE SOFTWARE.