Coverage for pygeodesy / props.py: 97%

243 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-10-21 16:43 -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_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 

25 

26from functools import wraps as _wraps 

27 

28__all__ = _ALL_LAZY.props 

29__version__ = '25.12.31' 

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

58 

59 

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 

68 

69 

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) 

76 

77 

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) 

83 

84 

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. 

88 

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) 

102 

103 if attrs: 

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

105 u -= len(d) 

106 return u # updates 

107 

108 

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 

130 

131 

132def _update_attrs(inst, *attrs): 

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

134 

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 

154 

155 

156class _PropertyBase(property): 

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

158 ''' 

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

160 

161 _xcallable(getter=method, fget=fget) 

162 

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 

168 

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

170 

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) 

181 

182 def _fdel(self, inst): 

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

184 ''' 

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

186 

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 

196 

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) 

204 

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 

209 

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 

220 

221 def deleter(self, fdel): 

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

223 ''' 

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

225 

226 def getter(self, fget): 

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

228 ''' 

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

230 

231 def setter(self, fset): 

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

233 ''' 

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

235 

236 

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

242 

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

246 

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

248 a more descriptive error message when set. 

249 

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. 

254 

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) 

261 

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) 

269 

270 

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

276 

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

278 

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

282 

283 def setter(self, method): # PYLINT W0237 

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

285 

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

287 

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 

296 

297 return self._setters(method, _fset) 

298 

299 def setter_(self, method): 

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

301 

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 

311 

312 return self._setters(method, _fset) 

313 

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 

321 

322 

323class property_RO(_PropertyBase): 

324 # No __doc__ on purpose 

325 _uname = NN 

326 

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

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

329 

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

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

332 

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

334 a more descriptive error message when set. 

335 

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> 

340 

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) 

357 

358 

359class _property_RO___(_PropertyBase): 

360 # No __doc__ on purpose 

361 

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. 

366 

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

368 ''' 

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

370 

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

372 '''Silently ignored, always. 

373 ''' 

374 pass # PYLINT W0107 

375 

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

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

378 ''' 

379 pass # PYLINT W0107 

380 

381 

382class property_ROnce(_property_RO___): 

383 # No __doc__ on purpose 

384 

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 

393 

394 

395class property_ROver(_property_RO___): 

396 # No __doc__ on purpose 

397 

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 

414 

415 

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) 

424 

425 

426def property_doc_(doc): 

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

428 

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

430 

431 @example: 

432 

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> 

446 

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

452 

453 return _documented_property 

454 

455 

456def _deprecated(call, kind, qual_d): 

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

458 

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

460 ed, Addison-Wesley. 

461 ''' 

462 doc = _DEPRECATEDof(call) 

463 

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) 

474 

475 return _deprecated_call 

476 

477 

478def deprecated_class(cls_or_class): 

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

480 

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

482 

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_ 

488 

489 

490def deprecated_function(call): 

491 '''Decorator for a DEPRECATED function. 

492 

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

494 

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 

500 

501 

502def deprecated_method(call): 

503 '''Decorator for a DEPRECATED method. 

504 

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

506 

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

508 ''' 

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

510 

511 

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

517 

518 

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

528 

529 

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) 

538 

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 

545 

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

547 

548 def setter(self, method): # PYLINT W0237 

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

550 

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

552 

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 

558 

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

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

561 else: 

562 

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 

570 

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

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

573 return self 

574 

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 

580 

581deprecated_Property = deprecated_property 

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_property_RO(method): 

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

596 

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

598 

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

600 ''' 

601 return _deprecated_RO(method, property_RO) 

602 

603 

604def _deprecated_RO(method, _RO): 

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

606 ''' 

607 doc = _DEPRECATEDof(method) 

608 

609 if _WARNINGS_X_DEV: 

610 

611 class _Deprecated_RO(_PropertyBase): 

612 __doc__ = doc 

613 

614 def __init__(self, method): 

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

616 

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) 

621 

622 return _Deprecated_RO(method) 

623 else: # PYCHOK no cover 

624 return _RO(method, doc=doc) 

625 

626 

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 

634 

635 

636class DeprecationWarnings(object): 

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

638 ''' 

639 _Warnings = 0 

640 

641 def __call__(self): # for backward compatibility 

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

643 

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

645 far or C{None} if not enabled. 

646 

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 

653 

654 @property_ROver 

655 def _Fmt(self): 

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

657 ''' 

658 return _MODS.streprs.Fmt # PYLINT E1101 

659 

660 @property_ROver 

661 def _stacklevel3(self): 

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

663 ''' 

664 return dict(stacklevel=3) 

665 

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

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

668 

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 

683 

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 

690 

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 

697 

698if not _FOR_DOCS: # PYCHOK force epydoc 

699 DeprecationWarnings = DeprecationWarnings() # singleton 

700 _throwarning = DeprecationWarnings.throw 

701# del _FOR_DOCS 

702 

703# **) MIT License 

704# 

705# Copyright (C) 2016-2026 -- 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.