Coverage for pygeodesy/errors.py: 91%

327 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-09-09 13:03 -0400

1 

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

3 

4u'''Errors, exceptions, exception formatting and exception chaining. 

5 

6Error, exception classes and functions to format PyGeodesy errors, including 

7the setting of I{exception chaining} for Python 3.9+. 

8 

9By default, I{exception chaining} is turned I{off}. To enable I{exception 

10chaining}, use command line option C{python -X dev} I{OR} set env variable 

11C{PYTHONDEVMODE=1} or to any non-empty string I{OR} set env variable 

12C{PYGEODESY_EXCEPTION_CHAINING=std} or to any non-empty string. 

13''' 

14# from pygeodesy import basics as _basics # _MODS.into 

15# from pygeodesy.ellipsoidalBase import CartesianEllipsoidalBase, LatLonEllipsoidalBase # _MODS 

16# from pygeodesy import errors # _MODS, _MODS.getattr 

17from pygeodesy.internals import _envPYGEODESY, _plural, _tailof, typename 

18from pygeodesy.interns import MISSING, NN, _a_, _an_, _and_, _clip_, _COLON_, _COLONSPACE_, \ 

19 _COMMASPACE_, _datum_, _DOT_, _ELLIPSIS_, _ellipsoidal_, \ 

20 _EQUALSPACED_, _immutable_, _incompatible_, _invalid_, _keyword_, \ 

21 _LatLon_, _len_, _not_, _or_, _SPACE_, _specified_, _UNDER_, \ 

22 _vs_, _with_ 

23from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _PYTHON_X_DEV 

24# from pygeodesy import streprs as _streprs # _MODS.into 

25# from pygeodesy.unitsBase import Str # _MODS 

26# from pygeodesy.vector3dBase import Vector3dBase # _MODS 

27 

28from copy import copy as _copy 

29 

30__all__ = _ALL_LAZY.errors # _ALL_DOCS('_InvalidError', '_IsnotError') _under 

31__version__ = '25.09.04' 

32 

33_argument_ = 'argument' 

34_basics = _MODS.into(basics=__name__) 

35_box_ = 'box' 

36_del_ = 'del' 

37_expected_ = 'expected' 

38_limiterrors = True # in .formy 

39_name_value_ = repr('name=value') 

40_rangerrors = True # in .dms 

41_region_ = 'region' 

42_streprs = _MODS.into(streprs=__name__) 

43_vs__ = _SPACE_(NN, _vs_, NN) 

44 

45try: 

46 _exception_chaining = None # not available 

47 _ = Exception().__cause__ # Python 3.9+ exception chaining 

48 

49 if _PYTHON_X_DEV or _envPYGEODESY('EXCEPTION_CHAINING'): # == _std_ 

50 _exception_chaining = True # turned on, std 

51 raise AttributeError() # allow exception chaining 

52 

53 _exception_chaining = False # turned off 

54 

55 def _error_cause(inst, cause=None): 

56 '''(INTERNAL) Set or avoid Python 3+ exception chaining. 

57 

58 Setting C{inst.__cause__ = None} is equivalent to syntax 

59 C{raise Error(...) from None} to avoid exception chaining. 

60 

61 @arg inst: An error instance (I{caught} C{Exception}). 

62 @kwarg cause: A previous error instance (I{caught} C{Exception}) 

63 or C{None} to avoid exception chaining. 

64 

65 @see: Alex Martelli, et.al., "Python in a Nutshell", 3rd Ed., page 163, 

66 O'Reilly, 2017, U{PEP-3134<https://www.Python.org/dev/peps/pep-3134>}, 

67 U{here<https://StackOverflow.com/questions/17091520/how-can-i-more- 

68 easily-suppress-previous-exceptions-when-i-raise-my-own-exception>} 

69 and U{here<https://StackOverflow.com/questions/1350671/ 

70 inner-exception-with-traceback-in-python>}. 

71 ''' 

72 inst.__cause__ = cause # None, no exception chaining 

73 return inst 

74 

75except AttributeError: # Python 2+ 

76 

77 def _error_cause(inst, **unused): # PYCHOK expected 

78 return inst # no-op 

79 

80 

81class _AssertionError(AssertionError): 

82 '''(INTERNAL) Format an C{AssertionError} with/-out exception chaining. 

83 ''' 

84 def __init__(self, *args, **kwds): 

85 _error_init(AssertionError, self, args, **kwds) 

86 

87 

88class _AttributeError(AttributeError): 

89 '''(INTERNAL) Format an C{AttributeError} with/-out exception chaining. 

90 ''' 

91 def __init__(self, *args, **kwds): 

92 _error_init(AttributeError, self, args, **kwds) 

93 

94 

95class _ImportError(ImportError): 

96 '''(INTERNAL) Format an C{ImportError} with/-out exception chaining. 

97 ''' 

98 def __init__(self, *args, **kwds): 

99 _error_init(ImportError, self, args, **kwds) 

100 

101 

102class _IndexError(IndexError): 

103 '''(INTERNAL) Format an C{IndexError} with/-out exception chaining. 

104 ''' 

105 def __init__(self, *args, **kwds): 

106 _error_init(IndexError, self, args, **kwds) 

107 

108 

109class _KeyError(KeyError): 

110 '''(INTERNAL) Format a C{KeyError} with/-out exception chaining. 

111 ''' 

112 def __init__(self, *args, **kwds): # txt=_invalid_ 

113 _error_init(KeyError, self, args, **kwds) 

114 

115 

116class _NameError(NameError): 

117 '''(INTERNAL) Format a C{NameError} with/-out exception chaining. 

118 ''' 

119 def __init__(self, *args, **kwds): 

120 _error_init(NameError, self, args, **kwds) 

121 

122 

123class _NotImplementedError(NotImplementedError): 

124 '''(INTERNAL) Format a C{NotImplementedError} with/-out exception chaining. 

125 ''' 

126 def __init__(self, *args, **kwds): 

127 _error_init(NotImplementedError, self, args, **kwds) 

128 

129 

130class _OverflowError(OverflowError): 

131 '''(INTERNAL) Format an C{OverflowError} with/-out exception chaining. 

132 ''' 

133 def __init__(self, *args, **kwds): # txt=_invalid_ 

134 _error_init(OverflowError, self, args, **kwds) 

135 

136 

137class _TypeError(TypeError): 

138 '''(INTERNAL) Format a C{TypeError} with/-out exception chaining. 

139 ''' 

140 def __init__(self, *args, **kwds): 

141 _error_init(TypeError, self, args, fmt_name_value='type(%s) (%r)', **kwds) 

142 

143 

144def _TypesError(name, value, *Types, **kwds): 

145 '''(INTERNAL) Format a C{TypeError} with/-out exception chaining. 

146 ''' 

147 # no longer C{class _TypesError} to avoid missing value 

148 # argument errors in _XError line ...E = Error(str(e)) 

149 t = _an(_or(*map(typename, Types, Types))) 

150 return _TypeError(name, value, txt=_not_(t), **kwds) 

151 

152 

153class _UnexpectedError(TypeError): # note, a TypeError! 

154 '''(INTERNAL) Format a C{TypeError} I{without exception chaining}. 

155 ''' 

156 def __init__(self, *args, **kwds): 

157 n = len(kwds) 

158 if args: 

159 a = _plural(_argument_, len(args)) 

160 n = _and(a, _plural(_keyword_, n)) if n else a 

161 else: 

162 n = _plural(_SPACE_(_keyword_, _argument_), n) 

163 u = _streprs.unstr(_SPACE_(n, NN), *args, **kwds) 

164 # _error_init(TypeError, self, (u,), txt_not_=_expected_) 

165 TypeError.__init__(self, _SPACE_(u, _not_, _expected_)) 

166 

167 

168class _ValueError(ValueError): 

169 '''(INTERNAL) Format a C{ValueError} with/-out exception chaining. 

170 ''' 

171 def __init__(self, *args, **kwds): # ..., cause=None, txt=_invalid_, ... 

172 _error_init(ValueError, self, args, **kwds) 

173 

174 

175class _ZeroDivisionError(ZeroDivisionError): 

176 '''(INTERNAL) Format a C{ZeroDivisionError} with/-out exception chaining. 

177 ''' 

178 def __init__(self, *args, **kwds): 

179 _error_init(ZeroDivisionError, self, args, **kwds) 

180 

181 

182class AuxError(_ValueError): 

183 '''Error raised for a L{rhumb.aux_}, C{Aux}, C{AuxDLat} or C{AuxLat} issue. 

184 ''' 

185 pass 

186 

187 

188class ClipError(_ValueError): 

189 '''Clip box or clip region issue. 

190 ''' 

191 def __init__(self, *name_n_corners, **txt_cause): 

192 '''New L{ClipError}. 

193 

194 @arg name_n_corners: Either just a name (C{str}) or 

195 name, number, corners (C{str}, 

196 C{int}, C{tuple}). 

197 @kwarg txt_cause: Optional C{B{txt}=str} explanation 

198 of the error and C{B{cause}=None} 

199 for exception chaining. 

200 ''' 

201 if len(name_n_corners) == 3: 

202 t, n, v = name_n_corners 

203 n = _SPACE_(t, _clip_, (_box_ if n == 2 else _region_)) 

204 name_n_corners = n, v 

205 _ValueError.__init__(self, *name_n_corners, **txt_cause) 

206 

207 

208class CrossError(_ValueError): 

209 '''Error raised for zero or near-zero vectorial cross products, 

210 occurring for coincident or colinear points, lines or bearings. 

211 ''' 

212 pass 

213 

214 

215class GeodesicError(_ValueError): 

216 '''Error raised for convergence or other issues in L{geodesicx<pygeodesy.geodesicx>}, 

217 L{geodesicw<pygeodesy.geodesicw>} or L{karney<pygeodesy.karney>}. 

218 ''' 

219 pass 

220 

221 

222class IntersectionError(_ValueError): # in .ellipsoidalBaseDI, .formy, ... 

223 '''Error raised for line or circle intersection issues. 

224 ''' 

225 def __init__(self, *args, **kwds): 

226 '''New L{IntersectionError}. 

227 ''' 

228 if args: 

229 t = _COMMASPACE_(*map(repr, args)) 

230 _ValueError.__init__(self, t, **kwds) 

231 else: 

232 _ValueError.__init__(self, **kwds) 

233 

234 

235class LenError(_ValueError): # in .ecef, .fmath, .heights, .iters, .named 

236 '''Error raised for mis-matching C{len} values. 

237 ''' 

238 def __init__(self, where, **lens_txt): # txt=None 

239 '''New L{LenError}. 

240 

241 @arg where: Object with C{.__name__} attribute 

242 (C{class}, C{method}, or C{function}). 

243 @kwarg lens_txt: Two or more C{name=len(name)} pairs 

244 (C{keyword arguments}). 

245 ''' 

246 def _ns_vs_txt_x(cause=None, txt=_invalid_, **kwds): 

247 ns, vs = zip(*_basics.itemsorted(kwds)) # unzip 

248 return ns, vs, txt, cause 

249 

250 ns, vs, txt, x = _ns_vs_txt_x(**lens_txt) 

251 ns = _COMMASPACE_.join(ns) 

252 t = _streprs.Fmt.PAREN(typename(where), ns) 

253 vs = _vs__.join(map(str, vs)) 

254 t = _SPACE_(t, _len_, vs) 

255 _ValueError.__init__(self, t, txt=txt, cause=x) 

256 

257 

258class LimitError(_ValueError): 

259 '''Error raised for lat- or longitudinal values or deltas exceeding the given 

260 B{C{limit}} in functions L{equirectangular<pygeodesy.equirectangular>}, 

261 L{equirectangular4<pygeodesy.equirectangular4>}, C{nearestOn*} and 

262 C{simplify*} or methods with C{limit} or C{options} keyword arguments. 

263 

264 @see: Subclass L{UnitError}. 

265 ''' 

266 pass 

267 

268 

269class MGRSError(_ValueError): 

270 '''Military Grid Reference System (MGRS) parse or other L{Mgrs} issue. 

271 ''' 

272 pass 

273 

274 

275class NumPyError(_ValueError): 

276 '''Error raised for C{NumPy} issues. 

277 ''' 

278 pass 

279 

280 

281class ParseError(_ValueError): # in .dms, .elevations, .utmupsBase 

282 '''Error parsing degrees, radians or several other formats. 

283 ''' 

284 pass 

285 

286 

287class PointsError(_ValueError): # in .clipy, .frechet, ... 

288 '''Error for an insufficient number of points. 

289 ''' 

290 pass 

291 

292 

293class RangeError(_ValueError): 

294 '''Error raised for lat- or longitude values outside the B{C{clip}}, B{C{clipLat}}, 

295 B{C{clipLon}} in functions L{parse3llh<pygeodesy.dms.parse3llh>}, L{parseDMS<pygeodesy.dms.parseDMS>}, 

296 L{parseDMS2<pygeodesy.dms.parseDMS2>} and L{parseRad<pygeodesy.dms.parseRad>} or the B{C{limit}} set 

297 with functions L{clipDegrees<pygeodesy.dms.clipDegrees>} and L{clipRadians<pygeodesy.dms.clipRadians>}. 

298 

299 @see: Function L{rangerrors<pygeodesy.errors.rangerrors>}. 

300 ''' 

301 pass 

302 

303 

304class RhumbError(_ValueError): 

305 '''Error raised for a rhumb L{aux_<pygeodesy.rhumb.aux_>}, L{ekx<pygeodesy.rhumb.ekx>} or 

306 L{solve<pygeodesy.rhumb.solve>} issue. 

307 ''' 

308 pass 

309 

310 

311class TriangleError(_ValueError): # in .resections, .vector2d 

312 '''Error raised for triangle, intersection or resection issues. 

313 ''' 

314 pass 

315 

316 

317class SciPyError(PointsError): 

318 '''Error raised for C{SciPy} issues. 

319 ''' 

320 pass 

321 

322 

323class SciPyWarning(PointsError): 

324 '''Error thrown for C{SciPy} warnings. 

325 

326 To raise C{SciPy} warnings as L{SciPyWarning} exceptions, Python 

327 C{warnings} must be filtered as U{warnings.filterwarnings('error') 

328 <https://docs.Python.org/3/library/warnings.html#the-warnings-filter>} 

329 I{prior to} C{import scipy} OR by setting env var U{PYTHONWARNINGS 

330 <https://docs.Python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>} 

331 OR by invoking C{python} with command line option U{-W<https://docs. 

332 Python.org/3/using/cmdline.html#cmdoption-w>} set to C{-W error}. 

333 ''' 

334 pass 

335 

336 

337class TRFError(_ValueError): # in .ellipsoidalBase, .trf, .units 

338 '''Terrestrial Reference Frame (TRF), L{Epoch}, L{RefFrame} or L{RefFrame} 

339 conversion issue. 

340 ''' 

341 pass 

342 

343 

344class UnitError(LimitError): # in .named, .units 

345 '''Default exception for L{units} issues for a value exceeding the C{low} 

346 or C{high} limit. 

347 ''' 

348 pass 

349 

350 

351class VectorError(_ValueError): # in .nvectorBase, .vector3d, .vector3dBase 

352 '''L{Vector3d}, C{Cartesian*} or C{*Nvector} issues. 

353 ''' 

354 pass 

355 

356 

357def _an(noun): 

358 '''(INTERNAL) Prepend an article to a noun based 

359 on the pronounciation of the first letter. 

360 ''' 

361 a = _an_ if noun[:1].lower() in 'aeinoux' else _a_ 

362 return _SPACE_(a, noun) 

363 

364 

365def _and(*words): 

366 '''(INTERNAL) Join C{words} with C{", "} and C{" and "}. 

367 ''' 

368 return _and_or(_and_, *words) 

369 

370 

371def _and_or(last, *words): 

372 '''(INTERNAL) Join C{words} with C{", "} and C{B{last}}. 

373 ''' 

374 t, w = NN, list(words) 

375 if w: 

376 t = w.pop() 

377 if w: 

378 w = _COMMASPACE_.join(w) 

379 t = _SPACE_(w, last, t) 

380 return t 

381 

382 

383def crosserrors(raiser=None): 

384 '''Report or ignore vectorial cross product errors. 

385 

386 @kwarg raiser: Use C{True} to throw, C{False} to ignore 

387 L{CrossError} exceptions or C{None} to 

388 leave the setting unchanged. 

389 

390 @return: Previous setting (C{bool}). 

391 

392 @see: Property C{Vector3d[Base].crosserrors}. 

393 ''' 

394 V = _MODS.vector3dBase.Vector3dBase 

395 t = V._crosserrors # XXX class attr! 

396 if raiser is not None: 

397 V._crosserrors = bool(raiser) 

398 return t 

399 

400 

401def _error_init(Error, inst, args, fmt_name_value='%s (%r)', txt_not_=NN, 

402 txt__=None, txt=NN, cause=None, **kwds): 

403 '''(INTERNAL) Format an error text and initialize an C{Error} instance. 

404 

405 @arg Error: The error super-class (C{Exception}). 

406 @arg inst: Sub-class instance to be __init__-ed (C{_Exception}). 

407 @arg args: Either just a value or several name, value, ... 

408 positional arguments (C{str}, any C{type}), in 

409 particular for name conflicts with keyword 

410 arguments of C{error_init} or which can't be 

411 given as C{name=value} keyword arguments. 

412 @kwarg fmt_name_value: Format for (name, value) (C{str}). 

413 @kwarg txt: Optional explanation of the error (C{str}). 

414 @kwarg txt__: Alternate C{B{txt}=B{txt__}.__name__}. 

415 @kwarg txt_not_: Negative explanation C{B{txt}=_not_(B{txt_not_})}. 

416 @kwarg cause: Optional, caught error (L{Exception}), for 

417 exception chaining (supported in Python 3+). 

418 @kwarg kwds: Additional C{B{name}=value} pairs, if any. 

419 ''' 

420 def _fmtuple(pairs): 

421 return tuple(fmt_name_value % t for t in pairs) 

422 

423 t, n = (), len(args) 

424 if n > 2: 

425 t = _fmtuple(zip(args[0::2], args[1::2])) 

426 s = _basics.isodd(n) 

427 if s: # XXX _xzip(..., strict=s) 

428 t += args[-1:] 

429 elif n == 2: 

430 t = (fmt_name_value % args), 

431 elif n: # == 1 

432 t = str(args[0]), 

433 if kwds: 

434 t += _fmtuple(_basics.itemsorted(kwds)) 

435 t = _or(*t) if t else _SPACE_(_name_value_, MISSING) 

436 

437 x = _not_(txt_not_) if txt_not_ else (txt if txt__ is None 

438 else typename(txt__)) 

439 if x is not None: 

440 x = str(x) or (str(cause) if cause else _invalid_) 

441 C = _COMMASPACE_ if _COLON_ in t else _COLONSPACE_ 

442 t = C(t, x) 

443# else: # LenError, _xzip, .dms, .heights, .vector2d 

444# x = NN # XXX or t? 

445 Error.__init__(inst, t) 

446# inst.__x_txt__ = x # hold explanation 

447 _error_cause(inst, cause=cause if _exception_chaining else None) 

448 _error_under(inst) 

449 

450 

451def _error_under(inst): 

452 '''(INTERNAL) Remove leading underscore from instance' class name. 

453 ''' 

454 t = type(inst) 

455 n = typename(t) # _tailof? 

456 if n.startswith(_UNDER_): 

457 t.__name__ = n.lstrip(_UNDER_) 

458 return inst 

459 

460 

461def exception_chaining(exc=None): 

462 '''Get an error's I{cause} or the exception chaining setting. 

463 

464 @kwarg exc: An error instance (C{Exception}) or C{None}. 

465 

466 @return: If C{B{exc} is None}, return C{True} if exception 

467 chaining is enabled for PyGeodesy errors, C{False} 

468 if turned off and C{None} if not available. If 

469 C{B{exc} is not None}, return it's error I{cause} 

470 or C{None} if there is none. 

471 

472 @note: To enable exception chaining for C{pygeodesy} errors, 

473 set env var C{PYGEODESY_EXCEPTION_CHAINING} to any 

474 non-empty value prior to C{import pygeodesy}. 

475 ''' 

476 return _exception_chaining if exc is None else \ 

477 getattr(exc, '__cause__', None) # _DCAUSE_ 

478 

479 

480def _ImmutableError(inst, attr, value=_del_, Error=_TypeError): # PYCHOK self 

481 '''(INTERNAL) Format an C{immutable _TypeError}. 

482 ''' 

483 n = typename(inst) 

484 n = _DOT_(_xattr(inst, name=n), attr) 

485 t = _SPACE_(_del_, n) if value is _del_ else \ 

486 _EQUALSPACED_(n, repr(value)) 

487 return Error(_immutable_, txt=t) 

488 

489 

490def _incompatible(this): 

491 '''(INTERNAL) Format an C{"incompatible with ..."} text. 

492 ''' 

493 return _SPACE_(_incompatible_, _with_, this) 

494 

495 

496def _InvalidError(Error=_ValueError, **txt_name_values_cause): # txt=_invalid_, name=value [, ...] 

497 '''(INTERNAL) Create an C{Error} instance. 

498 

499 @kwarg Error: The error class or sub-class (C{Exception}). 

500 @kwarg txt_name_values: One or more C{B{name}=value} pairs 

501 and optionally, keyword argument C{B{txt}=str} 

502 to override the default C{B{txt}='invalid'} and 

503 C{B{cause}=None} for exception chaining. 

504 

505 @return: An B{C{Error}} instance. 

506 ''' 

507 return _XError(Error, **txt_name_values_cause) 

508 

509 

510def isError(exc): 

511 '''Check a (caught) exception. 

512 

513 @arg exc: The exception C({Exception}). 

514 

515 @return: C{True} if B{C{exc}} is a C{pygeodesy} error, 

516 C{False} if B{C{exc}} is a standard Python error 

517 of C{None} if neither. 

518 ''' 

519 def _X(exc): 

520 X = type(exc) 

521 m = X.__module__ 

522 return _basics.issubclassof(X, *_XErrors) or \ 

523 ((m is __name__ or m == __name__) and 

524 _tailof(typename(X)).startswith(_UNDER_)) 

525 

526 return True if isinstance(exc, _XErrors) else ( 

527 _X(exc) if isinstance(exc, Exception) else None) 

528 

529 

530def _IsnotError(*types__, **name_value_Error_cause): # name=value [, Error=TypeError, cause=None] 

531 '''Create a C{TypeError} for an invalid C{name=value} type. 

532 

533 @arg types__: One or more types or type names. 

534 @kwarg name_value_Error_cause: One C{B{name}=value} pair and optionally, 

535 keyword arguments C{B{Error}=TypeError} and C{B{cause}=None} 

536 for exception chaining. 

537 

538 @return: A C{TypeError} or an B{C{Error}} instance. 

539 ''' 

540 x, kwds = _xkwds_pop2(name_value_Error_cause, cause=None) 

541 E, kwds = _xkwds_pop2(kwds, Error=TypeError) 

542 n, v = _xkwds_item2(kwds) 

543 

544 n = _streprs.Fmt.PARENSPACED(n, repr(v)) 

545 t = _an(_or(*map(typename, types__, types__))) if types__ else _specified_ 

546 return _XError(E, n, txt=_not_(t), cause=x) 

547 

548 

549def limiterrors(raiser=None): 

550 '''Get/set the throwing of L{LimitError}s. 

551 

552 @kwarg raiser: Use C{True} to raise, C{False} to 

553 ignore L{LimitError} exceptions or 

554 C{None} to leave the setting unchanged. 

555 

556 @return: Previous setting (C{bool}). 

557 ''' 

558 global _limiterrors 

559 t = _limiterrors 

560 if raiser is not None: 

561 _limiterrors = bool(raiser) 

562 return t 

563 

564 

565def _or(*words): 

566 '''(INTERNAL) Join C{words} with C{", "} and C{" or "}. 

567 ''' 

568 return _and_or(_or_, *words) 

569 

570 

571def _parseX(parser, *args, **Error_name_values): # name=value[, ..., Error=ParseError] 

572 '''(INTERNAL) Invoke a parser and handle exceptions. 

573 

574 @arg parser: The parser (C{callable(*B{args}}). 

575 @arg args: Any B{C{parser}} arguments (any C{type}s). 

576 @kwarg Error_name_values: Optional C{B{Error}=ParseError} 

577 and number of C{B{name}=value} pairs. 

578 

579 @return: Parser result. 

580 

581 @raise ParseError: Or the specified C{B{Error}}. 

582 ''' 

583 try: 

584 return parser(*args) 

585 except Exception as x: 

586 E = type(x) if isError(x) else ParseError 

587 E, kwds = _xkwds_pop2(Error_name_values, Error=E) 

588 raise _XError(E, **_xkwds(kwds, cause=x)) 

589 

590 

591def rangerrors(raiser=None): 

592 '''Get/set the throwing of L{RangeError}s. 

593 

594 @kwarg raiser: Use C{True} to raise or C{False} to ignore 

595 L{RangeError} exceptions or C{None} to leave 

596 the setting unchanged. 

597 

598 @return: Previous setting (C{bool}). 

599 ''' 

600 global _rangerrors 

601 t = _rangerrors 

602 if raiser is not None: 

603 _rangerrors = bool(raiser) 

604 return t 

605 

606 

607def _SciPyIssue(exc, *extras): # PYCHOK no cover 

608 if isinstance(exc, (RuntimeWarning, UserWarning)): 

609 E = SciPyWarning 

610 else: 

611 E = SciPyError # PYCHOK not really 

612 t = _SPACE_(str(exc).strip(), *extras) 

613 return E(t, txt=None, cause=exc) 

614 

615 

616def _xAssertionError(where, *args, **kwds): 

617 '''(INTERNAL) Embellish an C{AssertionError} with/-out exception chaining. 

618 ''' 

619 x, kwds = _xkwds_pop2(kwds, cause=None) 

620 w = _streprs.unstr(where, *args, **kwds) 

621 return _AssertionError(w, txt=None, cause=x) 

622 

623 

624def _xattr(obj, **name_default): 

625 '''(INTERNAL) Get an C{obj}'s attribute by C{name}. 

626 ''' 

627 if len(name_default) == 1: 

628 for n, d in name_default.items(): 

629 return getattr(obj, n, d) 

630 raise _xAssertionError(_xattr, obj, **name_default) 

631 

632 

633def _xattrs(inst, other, *attrs): # see .errors._xattr 

634 '''(INTERNAL) Copy attribute values from B{C{other}} to B{C{inst}}. 

635 

636 @arg inst: Object to copy attribute values to (any C{type}). 

637 @arg other: Object to copy attribute values from (any C{type}). 

638 @arg attrs: One or more attribute names (C{str}s). 

639 

640 @return: Object B{C{inst}}, updated. 

641 

642 @raise AttributeError: An B{C{attrs}} doesn't exist or isn't settable. 

643 ''' 

644 def _getattr(o, a): 

645 if hasattr(o, a): 

646 return getattr(o, a) 

647 try: 

648 n = o._DOT_(a) 

649 except AttributeError: 

650 n = _streprs.Fmt.DOT(a) 

651 raise _AttributeError(o, name=n) 

652 

653 for a in attrs: 

654 s = _getattr(other, a) 

655 g = _getattr(inst, a) 

656 if (g is None and s is not None) or g != s: 

657 setattr(inst, a, s) # not settable? 

658 return inst 

659 

660 

661def _xcallable(**names_callables): 

662 '''(INTERNAL) Check one or more C{callable}s. 

663 ''' 

664 for n, c in names_callables.items(): 

665 if not callable(c): 

666 raise _TypeError(n, c, txt_not_=typename(callable)) # txt__ 

667 

668 

669def _xdatum(datum1, datum2, Error=None): 

670 '''(INTERNAL) Check for datum, ellipsoid or rhumb mis-match. 

671 ''' 

672 if Error: 

673 e1, e2 = datum1.ellipsoid, datum2.ellipsoid 

674 if e1 != e2: 

675 raise Error(e2.named2, txt=_incompatible(e1.named2)) 

676 elif datum1 != datum2: 

677 t = _SPACE_(_datum_, repr(datum1.name), 

678 _not_, repr(datum2.name)) 

679 raise _AssertionError(t) 

680 

681 

682def _xellipsoidal(**name_value): # see _xellipsoidall elel 

683 '''(INTERNAL) Check an I{ellipsoidal} item and return its value. 

684 ''' 

685 if len(name_value) == 1: 

686 for n, v in name_value.items(): 

687 try: 

688 if v.isEllipsoidal: 

689 return v 

690 except AttributeError: 

691 pass 

692 raise _TypeError(n, v, txt_not_=_ellipsoidal_) 

693 raise _xAssertionError(_xellipsoidal, name_value) 

694 

695 

696def _xellipsoidall(point): # ... elel, see _xellipsoidal 

697 '''(INTERNAL) Check an ellipsoidal C{point}, return C{True} 

698 if geodetic latlon, C{False} if cartesian or TypeError. 

699 ''' 

700 m = _MODS.ellipsoidalBase 

701 ll = isinstance(point, m.LatLonEllipsoidalBase) 

702 if not ll: 

703 _basics._xinstanceof(m.CartesianEllipsoidalBase, 

704 m.LatLonEllipsoidalBase, point=point) 

705 return ll 

706 

707 

708def _xellipsoids(E1, E2, Error=_ValueError): # see .ellipsoidalBase 

709 '''(INTERNAL) Check ellipsoid mis-match, E2 vs E1. 

710 ''' 

711 if E2 != E1: 

712 raise Error(E2.named2, txt=_incompatible(E1.named2)) 

713 return E1 

714 

715 

716def _XError(Error, *args, **kwds): 

717 '''(INTERNAL) Format an C{Error} or C{_Error}. 

718 ''' 

719 try: # C{_Error} style 

720 return Error(*args, **kwds) 

721 except TypeError: # no keyword arguments 

722 pass 

723 e = _ValueError(*args, **kwds) 

724 E = Error(str(e)) 

725 if _exception_chaining: 

726 _error_cause(E, cause=e.__cause__) # PYCHOK OK 

727 return E 

728 

729 

730def _xError(exc, *args, **kwds): 

731 '''(INTERNAL) Embellish a (caught) exception. 

732 

733 @arg exc: The exception (usually, C{_Error}). 

734 @arg args: Embelishments (C{any}). 

735 @kwarg kwds: Embelishments (C{any}). 

736 ''' 

737 return _XError(type(exc), *args, **_xkwds(kwds, cause=exc)) 

738 

739 

740def _xError2(exc): # in .constants, .fsums, .lazily, .vector2d 

741 '''(INTERNAL) Map an exception to 2-tuple (C{_Error} class, error C{txt}). 

742 

743 @arg exc: The exception instance (usually, C{Exception}). 

744 ''' 

745 x = isError(exc) 

746 if x: 

747 E = type(exc) 

748 elif x is None: 

749 E = _AssertionError 

750 else: # get _Error from Error 

751 n = NN(_UNDER_, _tailof(typename(type(exc)))) 

752 E = _MODS.getattr(__name__, n, _NotImplementedError) 

753 x = E is not _NotImplementedError 

754 return E, (str(exc) if x else repr(exc)) 

755 

756 

757_XErrors = (_AssertionError, _AttributeError, # some isError's 

758 _TypeError, _ValueError, _ZeroDivisionError) 

759# map certain C{Exception} classes to the C{_Error} 

760# _X2Error = {AssertionError: _AssertionError, ... 

761# ZeroDivisionError: _ZeroDivisionError} 

762 

763 

764def _xgeodesics(G1, G2, Error=_ValueError): # see .geodesici 

765 '''(INTERNAL) Check geodesics mis-match. 

766 ''' 

767 if G1.ellipsoid != G2.ellipsoid: 

768 raise Error(G1.named2, txt=_incompatible(G2.named2)) 

769 return G1 

770 

771 

772try: 

773 _ = {}.__or__ # {} | {} # Python 3.9+ 

774 

775 def _xkwds(kwds, **dflts): 

776 '''(INTERNAL) Update C{dflts} with specified C{kwds}, 

777 i.e. C{copy(kwds).update(dflts)}. 

778 ''' 

779 return ((dflts | kwds) if kwds else dflts) if dflts else kwds 

780 

781except AttributeError: 

782 

783 def _xkwds(kwds, **dflts): # PYCHOK expected 

784 '''(INTERNAL) Update C{dflts} with specified C{kwds}, 

785 i.e. C{copy(kwds).update(dflts)}. 

786 ''' 

787 d = dflts 

788 if kwds: 

789 d = _copy(d) 

790 d.update(kwds) 

791 return d 

792 

793 

794# def _xkwds_bool(inst, **kwds): # unused 

795# '''(INTERNAL) Set applicable C{bool} properties/attributes. 

796# ''' 

797# for n, v in kwds.items(): 

798# b = getattr(inst, n, None) 

799# if b is None: # invalid bool attr 

800# t = _SPACE_(_EQUAL_(n, repr(v)), 'for', typename(type(inst)) # XXX .classname 

801# raise _AttributeError(t, txt_not_='applicable') 

802# if _basics.isbool(v) and v != b: 

803# setattr(inst, NN(_UNDER_, n), v) 

804 

805 

806# def _xkwds_from(orig, *args, **kwds): # unused 

807# '''(INTERNAL) Return the items from C{orig} with the keys 

808# from C{kwds} and a value not in C{args} and C{kwds}. 

809# ''' 

810# def _items(orig, args, items): 

811# for n, m in items: 

812# if n in orig: # n in (orig.keys() & kwds.keys()) 

813# t = orig[n] 

814# if t is not m and t not in args: 

815# yield n, t 

816# 

817# return _items(orig, args, kwds.items()) 

818 

819 

820def _xkwds_get(kwds, **name_default): 

821 '''(INTERNAL) Get a C{kwds} value by C{name} or the 

822 C{default} if not present. 

823 ''' 

824 if isinstance(kwds, dict) and len(name_default) == 1: 

825 for n, v in name_default.items(): 

826 return kwds.get(n, v) 

827 raise _xAssertionError(_xkwds_get, kwds, **name_default) 

828 

829 

830# def _xkwds_get_(kwds, **names_defaults): # unused 

831# '''(INTERNAL) Yield each C{kwds} value or its C{default} 

832# in I{case-insensitive, alphabetical} C{name} order. 

833# ''' 

834# if not isinstance(kwds, dict): 

835# raise _xAssertionError(_xkwds_get_, kwds) 

836# for n, v in _basics.itemsorted(names_defaults): 

837# yield kwds.get(n, v) 

838 

839 

840def _xkwds_get1(kwds, **name_default): 

841 '''(INTERNAL) Get one C{kwds} value by C{name} or the 

842 C{default} if not present. Raise an C{_UnexpectedError} 

843 with any remaining keyword arguments. 

844 ''' 

845 v, kwds = _xkwds_pop2(kwds, **name_default) 

846 if kwds: 

847 raise _UnexpectedError(**kwds) 

848 return v 

849 

850 

851def _xkwds_item2(kwds): 

852 '''(INTERNAL) Return the 2-tuple C{item}, keeping the 

853 single-item C{kwds} I{unmodified}. 

854 ''' 

855 if isinstance(kwds, dict) and len(kwds) == 1: 

856 for item in kwds.items(): 

857 return item 

858 raise _xAssertionError(_xkwds_item2, kwds) 

859 

860 

861def _xkwds_kwds(kwds, **names_defaults): # in .geodesici # PYCHOK no cover 

862 '''(INTERNAL) Return a C{dict} of C{named_defaults} items replaced with C{kwds}. 

863 ''' 

864 if not isinstance(kwds, dict): 

865 raise _xAssertionError(_xkwds_kwds, kwds) 

866 _g = kwds.get 

867 return dict((n, _g(n, v)) for n, v in names_defaults.items()) 

868 

869 

870def _xkwds_not(*args, **kwds): 

871 '''(INTERNAL) Return C{kwds} with a value not in C{args}. 

872 ''' 

873 return dict((n, v) for n, v in kwds.items() if v not in args) 

874 

875 

876def _xkwds_pop(kwds, **name_default): 

877 '''(INTERNAL) Pop an item by C{name} from C{kwds} and 

878 return its value, otherwise return the C{default}. 

879 ''' 

880 if isinstance(kwds, dict) and len(name_default) == 1: 

881 for n, v in name_default.items(): 

882 return kwds.pop(n, v) 

883 raise _xAssertionError(_xkwds_pop, kwds, **name_default) 

884 

885 

886def _xkwds_pop2(kwds, **name_default): 

887 '''(INTERNAL) Pop a C{kwds} item by C{name} and return the value and 

888 reduced C{kwds} copy, otherwise the C{default} and original C{kwds}. 

889 ''' 

890 if isinstance(kwds, dict) and len(name_default) == 1: 

891 for n, v in name_default.items(): 

892 if n in kwds: 

893 kwds = _copy(kwds) 

894 v = kwds.pop(n, v) 

895 return v, kwds 

896 raise _xAssertionError(_xkwds_pop2, kwds, **name_default) 

897 

898 

899def _Xorder(_Coeffs, Error, **Xorder): # in .auxLat, .ktm, .rhumb.bases, .rhumb.ekx 

900 '''(INTERNAL) Validate C{RAorder} or C{TMorder}. 

901 ''' 

902 X, m = _xkwds_item2(Xorder) 

903 if m in _Coeffs and _basics.isint(m): 

904 return m 

905 t = sorted(map(str, _Coeffs.keys())) 

906 raise Error(X, m, txt_not_=_or(*t)) 

907 

908 

909def _xsError(X, xs, i, x, *n, **kwds): # in .fmath, ._fstats, .fsums 

910 '''(INTERNAL) Error for C{xs} or C{x}, item C{xs[i]}. 

911 ''' 

912 def _xs(*xs): 

913 if len(xs) > 4: 

914 xs = xs[:3] + (_ELLIPSIS_,) + xs[-1:] 

915 return xs 

916 

917 return ((_xError(X, n[0], _xs(*xs), **kwds) if n else 

918 _xError(X, xs=_xs(*xs), **kwds)) if x is xs else 

919 _xError(X, _streprs.Fmt.INDEX(xs=i), x, **kwds)) 

920 

921 

922def _xStrError(*Refs, **name_value_Error): # in .gars, .geohash, .wgrs 

923 '''(INTERNAL) Create a C{TypeError} for C{Garef}, C{Geohash}, C{Wgrs}. 

924 ''' 

925 s = typename(_MODS.unitsBase.Str) 

926 t = tuple(map(typename, Refs)) + (s, _LatLon_, 'LatLon*Tuple') 

927 return _IsnotError(*t, **name_value_Error) 

928 

929# **) MIT License 

930# 

931# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

932# 

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

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

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

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

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

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

939# 

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

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

942# 

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

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

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

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

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

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

949# OTHER DEALINGS IN THE SOFTWARE.