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_get

16# from pygeodesy.internals import _tailof # from .lazily

17 from 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

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

23 _FOR_DOCS, _WARNINGS_X_DEV, _tailof

24# from pygeodesy.streprs import Fmt # _MODS

25

26 from functools import wraps as _wraps

27

28__all__ = _ALL_LAZY.props

29__version__ = '24.10.19'

30

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')

38

39

40 def _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

59

60

61 def _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

69

70

71 def _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)

77

78

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)

84

85

86 def _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.

89

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)

103

104 if attrs:

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

106 u -= len(d)

107 return u # updates

108

109

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

131

132

133 def _update_attrs(inst, *attrs):

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

135

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

155

156

157 class _PropertyBase(property):

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

159 '''

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

161

162 _xcallable(getter=method, fget=fget)

163

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

169

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

171

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)

182

183 def _fdel(self, inst):

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

185 '''

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

187

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

197

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)

205

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

210

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

221

222 def deleter(self, fdel):

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

224 '''

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

226

227 def getter(self, fget):

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

229 '''

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

231

232 def setter(self, fset):

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

234 '''

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

236

237

238 class 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}.

243

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}).

247

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

249 a more descriptive error message when set.

250

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.

255

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)

262

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)

270

271

272 class 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}.

277

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

279

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 '''

283

284 def setter(self, method):

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

286

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

288

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

297

298 return self._setters(method, _fset)

299

300 def setter_(self, method):

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

302

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

312

313 return self._setters(method, _fset)

314

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

322

323

324 class property_RO(_PropertyBase):

325 # No __doc__ on purpose

326 _uname = NN

327

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

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

330

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

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

333

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

335 a more descriptive error message when set.

336

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>

341

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)

358

359

360 class _property_RO___(_PropertyBase):

361 # No __doc__ on purpose

362

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.

367

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

369 '''

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

371

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

373 '''Silently ignored, always.

374 '''

375 pass

376

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

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

379 '''

380 pass

381

382

383 class property_ROnce(_property_RO___):

384 # No __doc__ on purpose

385

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

394

395

396 class property_ROver(_property_RO___):

397 # No __doc__ on purpose

398

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

415

416

417 class _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__

425

426

427 def property_doc_(doc):

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

429

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

431

432 @example:

433

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>

447

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))

453

454 return _documented_property

455

456

457 def _deprecated(call, kind, qual_d):

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

459

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

461 ed, Addison-Wesley.

462 '''

463 doc = _docof(call)

464

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)

475

476 return _deprecated_call

477

478

479 def deprecated_class(cls_or_class):

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

481

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

483

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__)

489

490

491 def deprecated_function(call):

492 '''Decorator for a DEPRECATED function.

493

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

495

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

501

502

503 def deprecated_method(call):

504 '''Decorator for a DEPRECATED method.

505

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

507

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

509 '''

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

511

512

513 def _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_)

518

519

520 if _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)

528

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

535

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

537

538 def setter(self, method):

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

540

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

542

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

548

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

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

551 else:

552

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

560

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

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

563 return self

564

565 else: # PYCHOK no cover

566 class deprecated_property(property): # PYCHOK expected

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

568 '''

569 pass

570

571 deprecated_Property = deprecated_property

572

573

574 def deprecated_Property_RO(method):

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

576

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

578

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

580 '''

581 return _deprecated_RO(method, Property_RO)

582

583

584 def deprecated_property_RO(method):

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

586

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

588

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

590 '''

591 return _deprecated_RO(method, property_RO)

592

593

594 def _deprecated_RO(method, _RO):

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

596 '''

597 doc = _docof(method)

598

599 if _WARNINGS_X_DEV:

600

601 class _Deprecated_RO(_PropertyBase):

602 __doc__ = doc

603

604 def __init__(self, method):

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

606

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)

611

612 return _Deprecated_RO(method)

613 else: # PYCHOK no cover

614 return _RO(method, doc=doc)

615

616

617 def _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:]

626

627

628 def _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

635

636

637 class DeprecationWarnings(object):

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

639 '''

640 _Warnings = 0

641

642 def __call__(self): # for backward compatibility

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

644

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

646 far or C{None} if not enabled.

647

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

654

655 @property_ROver

656 def _Fmt(self):

657 '''Get C{streprs.Fmt}, I{once}.

658 '''

659 return _MODS.streprs.Fmt

660

661 @property_ROver

662 def _stacklevel3(self):

663 '''Get C{dict(stacklevel=3)}, I{once}.

664 '''

665 return dict(stacklevel=3)

666

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

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

669

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

684

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

691

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

698

699 DeprecationWarnings = DeprecationWarnings() # PYCHOK singleton

700_throwarning = DeprecationWarnings.throw

701

702# **) MIT License

703#

704# Copyright (C) 2016-2024 -- 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.