Coverage for pygeodesy/props.py: 97%
243 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-10 16:16 -0500
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-10 16:16 -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
16from pygeodesy.internals import _tailof, typename
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
24# from pygeodesy.streprs import Fmt # _MODS
26from functools import wraps as _wraps
28__all__ = _ALL_LAZY.props
29__version__ = '25.12.31'
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 = type(Clas_or_inst).__mro__[:-1] # not object
49 except AttributeError:
50 S = () # not an inst
51 B = Bases or _PropertyBase
52 P = _property_RO___
53 for C in S:
54 for n, p in C.__dict__.items():
55 if isinstance(p, B) and p.name == n and not (
56 isinstance(p, P) or n in excls):
57 yield p
60def _allPropertiesOf_n(n, Clas_or_inst, *Bases, **excls):
61 '''(INTERNAL) Assert the number of C{R/property/_RO}s at C{Clas_or_inst}.
62 '''
63 t = tuple(p.name for p in _allPropertiesOf(Clas_or_inst, *Bases, **excls))
64 if len(t) != n:
65 raise _AssertionError(_COMMASPACE_.join(t), Clas_or_inst,
66 txt=_COMMASPACE_(len(t), _not_(n)))
67 return t
70def _hasProperty(inst, name, *Classes): # in .named._NamedBase._update
71 '''(INTERNAL) Check whether C{inst} has a C{P/property/_RO} by this C{name}.
72 '''
73 p = getattr(inst.__class__, name, None) # walks __class__.__mro__
74 return bool(p and isinstance(p, Classes or _PropertyBase)
75 and p.name == name)
78# def _isclass(obj):
79# '''(INTERNAL) Get and overwrite C{_isclass}.
80# '''
81# _MODS.getmodule(__name__)._isclass = f = _MODS.basics.isclass
82# return f(obj)
85def _update_all(inst, *attrs, **Base_needed):
86 '''(INTERNAL) Zap all I{cached} L{property_RO}s, L{Property}s,
87 L{Property_RO}s and the named C{attrs} of an instance.
89 @return: The number of updates (C{int}), if any.
90 '''
91 if _isclass(inst):
92 raise _AssertionError(inst, txt=_not_an_inst_)
93 try:
94 d = inst.__dict__
95 except AttributeError:
96 return 0
97 u = len(d)
98 if u > _xkwds_get(Base_needed, needed=0):
99 B = _xkwds_get(Base_needed, Base=_PropertyBase)
100 for p in _allPropertiesOf(inst, B):
101 p._update(inst) # d.pop(p.name, None)
103 if attrs:
104 _update_attrs(inst, *attrs) # remove attributes from inst.__dict__
105 u -= len(d)
106 return u # updates
109# def _update_all_from(inst, other, **Base):
110# '''(INTERNAL) Update all I{cached} L{Property}s and
111# L{Property_RO}s of instance C{inst} from C{other}.
112#
113# @return: The number of updates (C{int}), if any.
114# '''
115# if _isclass(inst):
116# raise _AssertionError(inst, txt=_not_an_inst_)
117# try:
118# d = inst.__dict__
119# f = other.__dict__
120# except AttributeError:
121# return 0
122# u = len(f)
123# if u:
124# u = len(d)
125# B = _xkwds_get(Base, Base=_PropertyBase)
126# for p in _allPropertiesOf(inst, B):
127# p._update_from(inst, other)
128# u -= len(d)
129# return u # number of updates
132def _update_attrs(inst, *attrs):
133 '''(INTERNAL) Zap all named C{attrs} of an instance.
135 @return: The number of updates (C{int}), if any.
136 '''
137 try:
138 d = inst.__dict__
139 except AttributeError:
140 return 0
141 u = len(d)
142 if u: # zap attrs from inst.__dict__
143 _p = d.pop
144 for a in attrs:
145 _ = _p(a, MISSING)
146# if _ is MISSING and not hasattr(inst, a):
147# n = _MODS.named.classname(inst, prefixed=True)
148# a = _DOT_(n, _SPACE_(a, _invalid_))
149# raise _AssertionError(a, txt=repr(inst))
150# _ = _p(a, None) # redo: hasattr side effect
151 u -= len(d)
152 # assert u >= 0
153 return u # number of named C{attrs} zapped
156class _PropertyBase(property):
157 '''(INTERNAL) Base class for C{P/property/_RO}.
158 '''
159 def __init__(self, method, fget, fset, doc=NN):
161 _xcallable(getter=method, fget=fget)
163 self.method = method
164 self.name = typename(method)
165 d = doc or method.__doc__
166 if _FOR_DOCS and d:
167 self.__doc__ = d # PYCHOK no cover
169 property.__init__(self, fget, fset, self._fdel, d or _N_A_)
171 def _Error(self, kind, nameter, farg):
172 '''(INTERNAL) Return an C{AttributeError} instance.
173 '''
174 if farg:
175 n = _DOT_(self.name, typename(nameter))
176 n = _SPACE_(n, typename(farg))
177 else:
178 n = nameter
179 e = _SPACE_(kind, _MODS.named.classname(self))
180 return _AttributeError(e, txt=n)
182 def _fdel(self, inst):
183 '''Zap the I{cached/memoized} C{property} value.
184 '''
185 self._update(inst, None) # PYCHOK no cover
187 def _fget(self, inst):
188 '''Get and I{cache/memoize} the C{property} value.
189 '''
190 try: # to get the value cached in instance' __dict__
191 return inst.__dict__[self.name]
192 except KeyError:
193 # cache the value in the instance' __dict__
194 inst.__dict__[self.name] = val = self.method(inst)
195 return val
197 def _fset_error(self, inst, val):
198 '''Throws an C{AttributeError}, always.
199 '''
200 n = _MODS.named.classname(inst)
201 n = _DOT_(n, self.name)
202 n = _EQUALSPACED_(n, repr(val))
203 raise self._Error(_immutable_, n, None)
205 def _update(self, inst, *unused):
206 '''(INTERNAL) Zap the I{cached/memoized} C{inst.__dict__[name]} item.
207 '''
208 inst.__dict__.pop(self.name, None) # name, NOT _name
210 def _update_from(self, inst, other):
211 '''(INTERNAL) Copy a I{cached/memoized} C{inst.__dict__[name]} item
212 from C{other.__dict__[name]} if present, otherwise zap it.
213 '''
214 n = self.name # name, NOT _name
215 v = other.__dict__.get(n, MISSING)
216 if v is MISSING:
217 inst.__dict__.pop(n, None)
218 else:
219 inst.__dict__[n] = v
221 def deleter(self, fdel):
222 '''Throws an C{AttributeError}, always.
223 '''
224 raise self._Error(_invalid_, self.deleter, fdel)
226 def getter(self, fget):
227 '''Throws an C{AttributeError}, always.
228 '''
229 raise self._Error(_invalid_, self.getter, fget)
231 def setter(self, fset):
232 '''Throws an C{AttributeError}, always.
233 '''
234 raise self._Error(_immutable_, self.setter, fset)
237class Property_RO(_PropertyBase):
238 # No __doc__ on purpose
239 def __init__(self, method, doc=NN): # PYCHOK expected
240 '''New I{immutable}, I{caching}, I{memoizing} C{property} I{Factory}
241 to be used as C{decorator}.
243 @arg method: The callable being decorated as this C{property}'s C{getter},
244 to be invoked only once.
245 @kwarg doc: Optional property documentation (C{str}).
247 @note: Like standard Python C{property} without a C{setter}, but with
248 a more descriptive error message when set.
250 @see: Python 3's U{functools.cached_property<https://docs.Python.org/3/
251 library/functools.html#functools.cached_property>} and U{-.cache
252 <https://Docs.Python.org/3/library/functools.html#functools.cache>}
253 to I{cache} or I{memoize} the property value.
255 @see: Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 636
256 Example 19-24 or 2022 p. 870 Example 22-28 and U{class
257 Property<https://docs.Python.org/3/howto/descriptor.html>}.
258 '''
259 _fget = method if _FOR_DOCS else self._fget # XXX force method.__doc__ to epydoc
260 _PropertyBase.__init__(self, method, _fget, self._fset_error)
262 def __get__(self, inst, *unused): # PYCHOK 2 vs 3 args
263 if inst is None:
264 return self
265 try: # to get the cached value immediately
266 return inst.__dict__[self.name]
267 except (AttributeError, KeyError):
268 return self._fget(inst)
271class Property(Property_RO):
272 # No __doc__ on purpose
273 __init__ = Property_RO.__init__
274 '''New I{mutable}, I{caching}, I{memoizing} C{property} I{Factory}
275 to be used as C{decorator}.
277 @see: L{Property_RO} for more details.
279 @note: Unless and until the C{setter} is defined, this L{Property} behaves
280 like an I{immutable}, I{caching}, I{memoizing} L{Property_RO}.
281 '''
283 def setter(self, method): # PYLINT W0237
284 '''Make this C{Property} I{mutable}.
286 @arg method: The callable being decorated as this C{Property}'s C{setter}.
288 @note: Setting a new property value always clears the previously I{cached}
289 or I{memoized} value I{after} invoking the B{C{method}}.
290 '''
291 def _fset(inst, val):
292 '''Set and I{cache}, I{memoize} the C{property} value.
293 '''
294 _ = method(inst, val)
295 self._update(inst) # un-cache this item
297 return self._setters(method, _fset)
299 def setter_(self, method):
300 '''Make this C{Property} I{mutable}.
302 @arg method: The callable being decorated as this C{Property}'s C{setter}
303 and returning the new property value to be I{cached} or
304 I{memoized}.
305 '''
306 def _fset(inst, val):
307 '''Set and I{cache}, I{memoize} the C{property} value.
308 '''
309 val = method(inst, val)
310 inst.__dict__[self.name] = val
312 return self._setters(method, _fset)
314 def _setters(self, method, _fset):
315 _xcallable(setter=method, fset=_fset)
316 if _FOR_DOCS: # XXX force method.__doc__ into epydoc
317 _PropertyBase.__init__(self, self.method, self.method, method)
318 else: # class Property <https://docs.Python.org/3/howto/descriptor.html>
319 _PropertyBase.__init__(self, self.method, self._fget, _fset)
320 return self
323class property_RO(_PropertyBase):
324 # No __doc__ on purpose
325 _uname = NN
327 def __init__(self, method, doc=NN): # PYCHOK expected
328 '''New I{immutable}, standard C{property} to be used as C{decorator}.
330 @arg method: The callable being decorated as C{property}'s C{getter}.
331 @kwarg doc: Optional property documentation (C{str}).
333 @note: Like standard Python C{property} without a setter, but with
334 a more descriptive error message when set.
336 @see: L{Property_RO}.
337 '''
338 _PropertyBase.__init__(self, method, method, self._fset_error, doc=doc)
339 self._uname = NN(_UNDER_, self.name) # actual attr UNDER<name>
341 def _update(self, inst, *Clas): # PYCHOK signature
342 '''(INTERNAL) Zap the I{cached} C{B{inst}.__dict__[_name]} item.
343 '''
344 uname = self._uname
345 if uname in inst.__dict__:
346 if Clas: # overrides inst.__class__
347 d = Clas[0].__dict__.get(uname, MISSING)
348 else:
349 d = getattr(type(inst), uname, MISSING)
350# if d is MISSING: # XXX superfluous
351# for c in type(inst).__mro__[:-1]:
352# if uname in c.__dict__:
353# d = c.__dict__[uname]
354# break
355 if d is None: # remove inst value
356 inst.__dict__.pop(uname)
359class _property_RO___(_PropertyBase):
360 # No __doc__ on purpose
362 def __init__(self, method, doc=NN): # PYCHOK expected
363 '''New C{property_ROnce} or C{property_ROver}, holding a singleton value as
364 class attribute for all instances of that class and overwriting C{self},
365 the C{property_ROver} instance in the 1st invokation.
367 @see: L{property_RO} for further details.
368 '''
369 _PropertyBase.__init__(self, method, self._fget, self._fset_error, doc=doc)
371 def _fdel(self, *unused): # PYCHOK no cover
372 '''Silently ignored, always.
373 '''
374 pass # PYLINT W0107
376 def _update(self, *unused): # PYCHOK signature
377 '''(INTERNAL) No-op, ignore updates.
378 '''
379 pass # PYLINT W0107
382class property_ROnce(_property_RO___):
383 # No __doc__ on purpose
385 def _fget(self, inst):
386 '''Get the C{property} value, only I{once} and memoize/cache it.
387 '''
388 try:
389 v = self._val
390 except AttributeError:
391 v = self._val = self.method(inst) # PYLINT W0201
392 return v
395class property_ROver(_property_RO___):
396 # No __doc__ on purpose
398 def _fget(self, inst):
399 '''Get the C{property} value I{once} and overwrite C{self},
400 this C{property} instance in the (super-)class of C{self}
401 where this property is define as a L{property_ROver}.
402 '''
403 v = self.method(inst)
404 n = self.name
405 C = type(inst)
406 for c in C.__mro__: # [:-1]
407 if getattr(c, n, None) is self:
408 setattr(c, n, v) # overwrite property_ROver
409 break
410 else:
411 n = _DOT_(typename(C), n)
412 raise _AssertionError(_EQUALSPACED_(n, v))
413 return v
416class _NamedProperty(property): # in .named
417 '''Class C{property} with a C{.name} attribute.
418 '''
419 @Property_RO
420 def name(self):
421 '''Get the name of this C{property} (C{str}).
422 '''
423 return typename(self.fget)
426def property_doc_(doc):
427 '''Decorator for a standard C{property} with basic documentation.
429 @arg doc: The property documentation (C{str}).
431 @example:
433 >>>class Clas(object):
434 >>>
435 >>> @property_doc_("documentation text.")
436 >>> def name(self):
437 >>> ...
438 >>>
439 >>> @name.setter
440 >>> def name(self, value):
441 >>> ...
442 '''
443 # See Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 212+
444 # Example 7-23 or 2022 p. 331+ Example 9-22 and <https://
445 # Python-3-Patterns-Idioms-Test.ReadTheDocs.io/en/latest/PythonDecorators.html>
447 def _documented_property(method):
448 '''(INTERNAL) Return the documented C{property}.
449 '''
450 t = 'get and set' if doc.startswith(_SPACE_) else NN
451 return _NamedProperty(method, None, None, NN('Property to ', t, doc))
453 return _documented_property
456def _deprecated(call, kind, qual_d):
457 '''(INTERNAL) Decorator for DEPRECATED functions, methods, etc.
459 @see: Brett Slatkin, "Effective Python", 2019 page 105, 2nd
460 ed, Addison-Wesley.
461 '''
462 doc = _DEPRECATEDof(call)
464 @_wraps(call) # PYCHOK self?
465 def _deprecated_call(*args, **kwds):
466 if qual_d: # function
467 q = qual_d
468 elif args: # method
469 q = _qualified(args[0], typename(call))
470 else: # PYCHOK no cover
471 q = typename(call)
472 _throwarning(kind, q, doc) # PYLINT E0606
473 return call(*args, **kwds)
475 return _deprecated_call
478def deprecated_class(cls_or_class):
479 '''Use inside __new__ or __init__ of a DEPRECATED class.
481 @arg cls_or_class: The class (C{cls} or C{Class}).
483 @note: NOT a decorator!
484 '''
485 if _WARNINGS_X_DEV:
486 q = _DOT_(cls_or_class.__module__, typename(cls_or_class))
487 _throwarning(_class_, q, cls_or_class.__doc__) # _DDOC_
490def deprecated_function(call):
491 '''Decorator for a DEPRECATED function.
493 @arg call: The deprecated function (C{callable}).
495 @return: The B{C{call}} DEPRECATED.
496 '''
497 return _deprecated(call, _function_, _DOT_(
498 call.__module__, typename(call))) if \
499 _WARNINGS_X_DEV else call
502def deprecated_method(call):
503 '''Decorator for a DEPRECATED method.
505 @arg call: The deprecated method (C{callable}).
507 @return: The B{C{call}} DEPRECATED.
508 '''
509 return _deprecated(call, _method_, NN) if _WARNINGS_X_DEV else call
512def _deprecated_module(name): # PYCHOK no cover
513 '''(INTERNAL) Callable within a DEPRECATED module.
514 '''
515 if _WARNINGS_X_DEV:
516 _throwarning(_module_, name, _dont_use_)
519def _DEPRECATEDof(obj):
520 '''(INTERNAL) Get uniform DEPRECATED __doc__ string.
521 '''
522 try:
523 d = obj.__doc__.strip()
524 i = d.find(_DEPRECATED_)
525 except AttributeError:
526 i = -1
527 return _DOT_(_DEPRECATED_, NN) if i < 0 else d[i:]
530if _WARNINGS_X_DEV:
531 class deprecated_property(_PropertyBase):
532 '''Decorator for a DEPRECATED C{property} or C{Property}.
533 '''
534 def __init__(self, method):
535 '''Decorator for a DEPRECATED C{property} or C{Property} getter.
536 '''
537 doc = _DEPRECATEDof(method)
539 def _fget(inst): # PYCHOK no cover
540 '''Get the C{property} or C{Property} value.
541 '''
542 q = _qualified(inst, self.name)
543 _throwarning(typename(property), q, doc)
544 return self.method(inst) # == method
546 _PropertyBase.__init__(self, method, _fget, None, doc=doc)
548 def setter(self, method): # PYLINT W0237
549 '''Decorator for a DEPRECATED C{property} or C{Property} setter.
551 @arg method: The callable being decorated as this C{Property}'s C{setter}.
553 @note: Setting a new property value always clears the previously I{cached}
554 or I{memoized} value I{after} invoking the B{C{method}}.
555 '''
556 if not callable(method):
557 _PropertyBase.setter(self, method) # PYCHOK no cover
559 if _FOR_DOCS: # XXX force method.__doc__ into epydoc
560 _PropertyBase.__init__(self, self.method, self.method, method)
561 else:
563 def _fset(inst, val):
564 '''Set the C{property} or C{Property} value.
565 '''
566 q = _qualified(inst, self.name)
567 _throwarning(typename(property), q, _DEPRECATEDof(method))
568 method(inst, val)
569 # self._update(inst) # un-cache this item
571 # class Property <https://docs.Python.org/3/howto/descriptor.html>
572 _PropertyBase.__init__(self, self.method, self._fget, _fset)
573 return self
575else: # PYCHOK no cover
576 class deprecated_property(property): # PYCHOK expected
577 '''Decorator for a DEPRECATED C{property} or C{Property}.
578 '''
579 pass # PYLINT W0107
581deprecated_Property = deprecated_property
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_property_RO(method):
595 '''Decorator for a DEPRECATED L{property_RO}.
597 @arg method: The C{property_RO.fget} method (C{callable}).
599 @return: The B{C{method}} DEPRECATED.
600 '''
601 return _deprecated_RO(method, property_RO)
604def _deprecated_RO(method, _RO):
605 '''(INTERNAL) Create a DEPRECATED C{property_RO} or C{Property_RO}.
606 '''
607 doc = _DEPRECATEDof(method)
609 if _WARNINGS_X_DEV:
611 class _Deprecated_RO(_PropertyBase):
612 __doc__ = doc
614 def __init__(self, method):
615 _PropertyBase.__init__(self, method, self._fget, self._fset_error, doc=doc)
617 def _fget(self, inst): # PYCHOK no cover
618 q = _qualified(inst, self.name)
619 _throwarning(typename(_RO), q, doc)
620 return self.method(inst)
622 return _Deprecated_RO(method)
623 else: # PYCHOK no cover
624 return _RO(method, doc=doc)
627def _qualified(inst, name):
628 '''(INTERNAL) Fully qualify a name.
629 '''
630 # _DOT_(inst.classname, name), not _DOT_(inst.named4, name)
631 t = type(inst)
632 q = _DOT_(t.__module__, typename(t), name) # _DMODULE_
633 return q
636class DeprecationWarnings(object):
637 '''(INTERNAL) Handle C{DeprecationWaring}s.
638 '''
639 _Warnings = 0
641 def __call__(self): # for backward compatibility
642 '''Have any C{DeprecationWarning}s been reported or raised?
644 @return: The number of C{DeprecationWarning}s (C{int}) so
645 far or C{None} if not enabled.
647 @note: To get C{DeprecationWarning}s if any, run C{python}
648 with env var C{PYGEODESY_WARNINGS} set to a non-empty
649 string I{AND} use C{python[3]} command line option
650 C{-X dev}, C{-W always} or C{-W error}, etc.
651 '''
652 return self.Warnings
654 @property_ROver
655 def _Fmt(self):
656 '''Get C{streprs.Fmt}, I{once}.
657 '''
658 return _MODS.streprs.Fmt # PYLINT E1101
660 @property_ROver
661 def _stacklevel3(self):
662 '''Get C{dict(stacklevel=3)}, I{once}.
663 '''
664 return dict(stacklevel=3)
666 def throw(self, kind, name, doc, **stacklevel): # stacklevel=3
667 '''Report or raise a C{DeprecationWarning}.
669 @arg kind: Warning kind (C{str}), C{"method"}, C{"funtion"}, ...
670 @arg name: Qualified name (C{str}) of B{C{kind}}.
671 @arg doc: The __doc__ (C{str}) of B{C{kind}}, C{"DEPRECATED ...}.
672 '''
673 link = _tailof(name) or name
674 if link is not name: # make "link<name>"
675 link = self._Fmt.ANGLE(link, name)
676 link = self._Fmt.CURLY(L=link) # "L{link}"
677 text = doc.split(_DNL_, 1)[0].strip()
678 text = _SPACE_(kind, link, _has_been_, *text.split())
679 kwds = stacklevel if stacklevel else self._stacklevel3
680 # XXX invoke warn or raise DeprecationWarning(text)
681 self._warn(text, category=DeprecationWarning, **kwds)
682 self._Warnings += 1
684 @property_ROver
685 def _warn(self):
686 '''Get Python's C{warnings.warn} function, I{once}.
687 '''
688 from warnings import warn as w
689 return w
691 @property_RO
692 def Warnings(self):
693 '''Get the number of C{DeprecationWarning}s (C{int}) so
694 far or C{None} if not enabled.
695 '''
696 return self._Warnings if _WARNINGS_X_DEV else None
698if not _FOR_DOCS: # PYCHOK force epydoc
699 DeprecationWarnings = DeprecationWarnings() # singleton
700 _throwarning = DeprecationWarnings.throw
701# del _FOR_DOCS
703# **) MIT License
704#
705# Copyright (C) 2016-2027 -- mrJean1 at Gmail -- All Rights Reserved.
706#
707# Permission is hereby granted, free of charge, to any person obtaining a
708# copy of this software and associated documentation files (the "Software"),
709# to deal in the Software without restriction, including without limitation
710# the rights to use, copy, modify, merge, publish, distribute, sublicense,
711# and/or sell copies of the Software, and to permit persons to whom the
712# Software is furnished to do so, subject to the following conditions:
713#
714# The above copyright notice and this permission notice shall be included
715# in all copies or substantial portions of the Software.
716#
717# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
718# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
719# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
720# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
721# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
722# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
723# OTHER DEALINGS IN THE SOFTWARE.