Coverage for pygeodesy/props.py: 98%

241 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-10 16:55 -0500

1 

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

3 

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

5deprecation decorators. 

6 

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

12 

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 

25 

26from functools import wraps as _wraps 

27 

28__all__ = _ALL_LAZY.props 

29__version__ = '24.11.06' 

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 

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 

59 

60 

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 

69 

70 

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) 

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 

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. 

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 

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

157class _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 

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

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 

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

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 

324class 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 

360class _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 

383class 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 

396class 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 

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__ 

425 

426 

427def 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 

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

479def 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 

491def 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 

503def 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 

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

518 

519 

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) 

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 

565else: # PYCHOK no cover 

566 class deprecated_property(property): # PYCHOK expected 

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

568 ''' 

569 pass 

570 

571deprecated_Property = deprecated_property 

572 

573 

574def 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 

584def 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 

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

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:] 

626 

627 

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 

635 

636 

637class 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 

699DeprecationWarnings = DeprecationWarnings() # PYCHOK singleton 

700_throwarning = DeprecationWarnings.throw 

701 

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.