Coverage for pygeodesy/props.py: 97%

231 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-10-09 12:50 -0400

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, _xkwds_get 

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

17 _DEPRECATED_, _DOT_, _EQUALSPACED_, \ 

18 _immutable_, _invalid_, _module_, _N_A_, \ 

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

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

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

22 _FOR_DOCS, _WARNINGS_X_DEV 

23# from pygeodesy.streprs import Fmt # _MODS 

24 

25from functools import wraps as _wraps 

26 

27__all__ = _ALL_LAZY.props 

28__version__ = '24.09.02' 

29 

30_class_ = 'class' 

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

32_function_ = 'function' 

33_has_been_ = 'has been' # PYCHOK used! 

34_method_ = 'method' 

35_not_an_inst_ = _not_(_an_, 'instance') 

36 

37 

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

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

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

41 ''' 

42 if _isclass(Clas_or_inst): 

43 S = Clas_or_inst, # just this Clas 

44 else: # class and super-classes of inst 

45 try: 

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

47 except AttributeError: 

48 raise 

49 S = () # not an inst 

50 B = Bases or _PropertyBase 

51 P = _property_RO___ 

52 for C in S: 

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

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

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

56 yield p 

57 

58 

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

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

61 ''' 

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

63 if len(t) != n: 

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

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

66 return t 

67 

68 

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

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

71 ''' 

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

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

74 and p.name == name) 

75 

76 

77# def _isclass(obj): 

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

79# ''' 

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

81# return f(obj) 

82 

83 

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

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

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

87 

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

89 ''' 

90 if _isclass(inst): 

91 raise _AssertionError(inst, txt=_not_an_inst_) 

92 try: 

93 d = inst.__dict__ 

94 except AttributeError: 

95 return 0 

96 u = len(d) 

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

98 B = _xkwds_get(Base_needed, Base=_PropertyBase) 

99 for p in _allPropertiesOf(inst, B): 

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

101 

102 if attrs: 

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

104 u -= len(d) 

105 return u # updates 

106 

107 

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

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

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

111# 

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

113# ''' 

114# if _isclass(inst): 

115# raise _AssertionError(inst, txt=_not_an_inst_) 

116# try: 

117# d = inst.__dict__ 

118# f = other.__dict__ 

119# except AttributeError: 

120# return 0 

121# u = len(f) 

122# if u: 

123# u = len(d) 

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

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

126# p._update_from(inst, other) 

127# u -= len(d) 

128# return u # number of updates 

129 

130 

131def _update_attrs(inst, *attrs): 

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

133 

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

135 ''' 

136 try: 

137 d = inst.__dict__ 

138 except AttributeError: 

139 return 0 

140 u = len(d) 

141 if u: # zap attrs from inst.__dict__ 

142 _p = d.pop 

143 for a in attrs: 

144 _ = _p(a, MISSING) 

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

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

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

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

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

150 u -= len(d) 

151 # assert u >= 0 

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

153 

154 

155class _PropertyBase(property): 

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

157 ''' 

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

159 

160 _xcallable(getter=method, fget=fget) 

161 

162 self.method = method 

163 self.name = method.__name__ 

164 d = doc or method.__doc__ 

165 if _FOR_DOCS and d: 

166 self.__doc__ = d # PYCHOK no cover 

167 

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

169 

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

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

172 ''' 

173 if farg: 

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

175 n = _SPACE_(n, farg.__name__) 

176 else: 

177 n = nameter 

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

179 return _AttributeError(e, txt=n) 

180 

181 def _fdel(self, inst): 

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

183 ''' 

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

185 

186 def _fget(self, inst): 

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

188 ''' 

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

190 return inst.__dict__[self.name] 

191 except KeyError: 

192 # cache the value in the instance' __dict__ 

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

194 return val 

195 

196 def _fset_error(self, inst, val): 

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

198 ''' 

199 n = _MODS.named.classname(inst) 

200 n = _DOT_(n, self.name) 

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

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

203 

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

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

206 ''' 

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

208 

209 def _update_from(self, inst, other): 

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

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

212 ''' 

213 n = self.name # name, NOT _name 

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

215 if v is MISSING: 

216 inst.__dict__.pop(n, None) 

217 else: 

218 inst.__dict__[n] = v 

219 

220 def deleter(self, fdel): 

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

222 ''' 

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

224 

225 def getter(self, fget): 

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

227 ''' 

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

229 

230 def setter(self, fset): 

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

232 ''' 

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

234 

235 

236class Property_RO(_PropertyBase): 

237 # No __doc__ on purpose 

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

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

240 to be used as C{decorator}. 

241 

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

243 to be invoked only once. 

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

245 

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

247 a more descriptive error message when set. 

248 

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

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

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

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

253 

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

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

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

257 ''' 

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

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

260 

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

262 if inst is None: 

263 return self 

264 try: # to get the cached value immediately 

265 return inst.__dict__[self.name] 

266 except (AttributeError, KeyError): 

267 return self._fget(inst) 

268 

269 

270class Property(Property_RO): 

271 # No __doc__ on purpose 

272 __init__ = Property_RO.__init__ 

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

274 to be used as C{decorator}. 

275 

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

277 

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

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

280 ''' 

281 

282 def setter(self, method): 

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

284 

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

286 

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

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

289 ''' 

290 def _fset(inst, val): 

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

292 ''' 

293 _ = method(inst, val) 

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

295 

296 return self._setters(method, _fset) 

297 

298 def setter_(self, method): 

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

300 

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

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

303 I{memoized}. 

304 ''' 

305 def _fset(inst, val): 

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

307 ''' 

308 val = method(inst, val) 

309 inst.__dict__[self.name] = val 

310 

311 return self._setters(method, _fset) 

312 

313 def _setters(self, method, _fset): 

314 _xcallable(setter=method, fset=_fset) 

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

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

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

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

319 return self 

320 

321 

322class property_RO(_PropertyBase): 

323 # No __doc__ on purpose 

324 _uname = NN 

325 

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

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

328 

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

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

331 

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

333 a more descriptive error message when set. 

334 

335 @see: L{Property_RO}. 

336 ''' 

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

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

339 

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

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

342 ''' 

343 uname = self._uname 

344 if uname in inst.__dict__: 

345 if Clas: # overrides inst.__class__ 

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

347 else: 

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

349# if d is MISSING: # XXX superfluous 

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

351# if uname in c.__dict__: 

352# d = c.__dict__[uname] 

353# break 

354 if d is None: # remove inst value 

355 inst.__dict__.pop(uname) 

356 

357 

358class _property_RO___(_PropertyBase): 

359 # No __doc__ on purpose 

360 

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

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

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

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

365 

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

367 ''' 

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

369 

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

371 '''Silently ignored, always. 

372 ''' 

373 pass 

374 

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

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

377 ''' 

378 pass 

379 

380 

381class property_ROnce(_property_RO___): 

382 # No __doc__ on purpose 

383 

384 def _fget(self, inst): 

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

386 ''' 

387 try: 

388 v = self._val 

389 except AttributeError: 

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

391 return v 

392 

393 

394class property_ROver(_property_RO___): 

395 # No __doc__ on purpose 

396 

397 def _fget(self, inst): 

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

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

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

401 ''' 

402 v = self.method(inst) 

403 n = self.name 

404 C = inst.__class__ 

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

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

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

408 break 

409 else: 

410 n = _DOT_(C.__name__, n) 

411 raise _AssertionError(_EQUALSPACED_(n, v)) 

412 return v 

413 

414 

415class _NamedProperty(property): # in .named 

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

417 ''' 

418 @Property_RO 

419 def name(self): 

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

421 ''' 

422 return self.fget.__name__ 

423 

424 

425def property_doc_(doc): 

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

427 

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

429 

430 @example: 

431 

432 >>>class Clas(object): 

433 >>> 

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

435 >>> def name(self): 

436 >>> ... 

437 >>> 

438 >>> @name.setter 

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

440 >>> ... 

441 ''' 

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

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

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

445 

446 def _documented_property(method): 

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

448 ''' 

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

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

451 

452 return _documented_property 

453 

454 

455def _deprecated(call, kind, qual_d): 

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

457 

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

459 ed, Addison-Wesley. 

460 ''' 

461 doc = _docof(call) 

462 

463 @_wraps(call) # PYCHOK self? 

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

465 if qual_d: # function 

466 q = qual_d 

467 elif args: # method 

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

469 else: # PYCHOK no cover 

470 q = call.__name__ 

471 _throwarning(kind, q, doc) 

472 return call(*args, **kwds) 

473 

474 return _deprecated_call 

475 

476 

477def deprecated_class(cls_or_class): 

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

479 

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

481 

482 @note: NOT a decorator! 

483 ''' 

484 if _WARNINGS_X_DEV: 

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

486 _throwarning(_class_, q, cls_or_class.__doc__) 

487 

488 

489def deprecated_function(call): 

490 '''Decorator for a DEPRECATED function. 

491 

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

493 

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

495 ''' 

496 return _deprecated(call, _function_, _DOT_( 

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

498 _WARNINGS_X_DEV else call 

499 

500 

501def deprecated_method(call): 

502 '''Decorator for a DEPRECATED method. 

503 

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

505 

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

507 ''' 

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

509 

510 

511def _deprecated_module(name): # PYCHOK no cover 

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

513 ''' 

514 if _WARNINGS_X_DEV: 

515 _throwarning(_module_, name, _dont_use_) 

516 

517 

518if _WARNINGS_X_DEV: 

519 class deprecated_property(_PropertyBase): 

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

521 ''' 

522 def __init__(self, method): 

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

524 ''' 

525 doc = _docof(method) 

526 

527 def _fget(inst): # PYCHOK no cover 

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

529 ''' 

530 q = _qualified(inst, self.name) 

531 _throwarning(property.__name__, q, doc) 

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

533 

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

535 

536 def setter(self, method): 

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

538 

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

540 

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

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

543 ''' 

544 if not callable(method): 

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

546 

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

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

549 else: 

550 

551 def _fset(inst, val): 

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

553 ''' 

554 q = _qualified(inst, self.name) 

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

556 method(inst, val) 

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

558 

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

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

561 return self 

562 

563else: # PYCHOK no cover 

564 class deprecated_property(property): # PYCHOK expected 

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

566 ''' 

567 pass 

568 

569deprecated_Property = deprecated_property 

570 

571 

572def deprecated_Property_RO(method): 

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

574 

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

576 

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

578 ''' 

579 return _deprecated_RO(method, Property_RO) 

580 

581 

582def deprecated_property_RO(method): 

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

584 

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

586 

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

588 ''' 

589 return _deprecated_RO(method, property_RO) 

590 

591 

592def _deprecated_RO(method, _RO): 

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

594 ''' 

595 doc = _docof(method) 

596 

597 if _WARNINGS_X_DEV: 

598 

599 class _Deprecated_RO(_PropertyBase): 

600 __doc__ = doc 

601 

602 def __init__(self, method): 

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

604 

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

606 q = _qualified(inst, self.name) 

607 _throwarning(_RO.__name__, q, doc) 

608 return self.method(inst) 

609 

610 return _Deprecated_RO(method) 

611 else: # PYCHOK no cover 

612 return _RO(method, doc=doc) 

613 

614 

615def _docof(obj): 

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

617 ''' 

618 try: 

619 d = obj.__doc__.strip() 

620 i = d.find(_DEPRECATED_) 

621 except AttributeError: 

622 i = -1 

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

624 

625 

626def _qualified(inst, name): 

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

628 ''' 

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

630 c = inst.__class__ 

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

632 return q 

633 

634 

635class DeprecationWarnings(object): 

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

637 ''' 

638 _Warnings = 0 

639 

640 def __call__(self): # for backward compatibility 

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

642 

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

644 far or C{None} if not enabled. 

645 

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

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

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

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

650 ''' 

651 return self.Warnings 

652 

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

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

655 ''' 

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

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

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

659 kwds = _xkwds(stacklevel, stacklevel=3) 

660 # XXX invoke warn or raise DeprecationWarning(text) 

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

662 self._Warnings += 1 

663 

664 @Property_RO 

665 def _warn(self): 

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

667 ''' 

668 from warnings import warn 

669 return warn 

670 

671 @property_RO 

672 def Warnings(self): 

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

674 far or C{None} if not enabled. 

675 ''' 

676 return self._Warnings if _WARNINGS_X_DEV else None 

677 

678DeprecationWarnings = DeprecationWarnings() # PYCHOK singleton 

679_throwarning = DeprecationWarnings.throw 

680 

681# **) MIT License 

682# 

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

684# 

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

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

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

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

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

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

691# 

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

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

694# 

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

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

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

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

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

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

701# OTHER DEALINGS IN THE SOFTWARE.