Coverage for pygeodesy/errors.py: 91%

332 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-03-25 15:01 -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__ = '26.02.12' 

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 _ConvergenceError(ValueError): # in .ellipses, .elliptic 

96 '''(INTERNAL) Format a C{ConvergenceError}. 

97 ''' 

98 def __init__(self, maxit, d, tol, **thresh): # PYCHOK cause=None 

99 t = _streprs.Fmt.no_convergence(d, tol, **thresh) 

100 _error_init(ValueError, self, (), maxit=maxit, txt=t) 

101 

102 

103class _ImportError(ImportError): 

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

105 ''' 

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

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

108 

109 

110class _IndexError(IndexError): 

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

112 ''' 

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

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

115 

116 

117class _KeyError(KeyError): 

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

119 ''' 

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

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

122 

123 

124class _NameError(NameError): 

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

126 ''' 

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

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

129 

130 

131class _NotImplementedError(NotImplementedError): 

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

133 ''' 

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

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

136 

137 

138class _OverflowError(OverflowError): 

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

140 ''' 

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

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

143 

144 

145class _TypeError(TypeError): 

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

147 ''' 

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

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

150 

151 

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

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

154 ''' 

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

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

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

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

159 

160 

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

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

163 ''' 

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

165 n = len(kwds) 

166 if args: 

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

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

169 else: 

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

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

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

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

174 

175 

176class _ValueError(ValueError): 

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

178 ''' 

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

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

181 

182 

183class _ZeroDivisionError(ZeroDivisionError): 

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

185 ''' 

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

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

188 

189 

190class AuxError(_ValueError): 

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

192 ''' 

193 pass 

194 

195 

196class ClipError(_ValueError): 

197 '''Clip box or clip region issue. 

198 ''' 

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

200 '''New L{ClipError}. 

201 

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

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

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

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

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

207 for exception chaining. 

208 ''' 

209 if len(name_n_corners) == 3: 

210 t, n, v = name_n_corners 

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

212 name_n_corners = n, v 

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

214 

215 

216class CrossError(_ValueError): 

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

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

219 ''' 

220 pass 

221 

222 

223class GeodesicError(_ValueError): 

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

225 L{geodesicw<pygeodesy.geodesicw>}, L{geodsolve<pygeodesy.geodsolve>} or 

226 L{karney<pygeodesy.karney>}. 

227 ''' 

228 pass 

229 

230 

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

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

233 ''' 

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

235 '''New L{IntersectionError}. 

236 ''' 

237 if args: 

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

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

240 else: 

241 _ValueError.__init__(self, **kwds) 

242 

243 

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

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

246 ''' 

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

248 '''New L{LenError}. 

249 

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

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

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

253 (C{keyword arguments}). 

254 ''' 

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

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

257 return ns, vs, txt, cause 

258 

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

260 ns = _COMMASPACE_.join(ns) 

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

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

263 t = _SPACE_(t, _len_, vs) 

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

265 

266 

267class LimitError(_ValueError): 

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

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

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

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

272 

273 @see: Subclass L{UnitError}. 

274 ''' 

275 pass 

276 

277 

278class MGRSError(_ValueError): 

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

280 ''' 

281 pass 

282 

283 

284class NumPyError(_ValueError): 

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

286 ''' 

287 pass 

288 

289 

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

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

292 ''' 

293 pass 

294 

295 

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

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

298 ''' 

299 pass 

300 

301 

302class RangeError(_ValueError): 

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

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

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

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

307 

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

309 ''' 

310 pass 

311 

312 

313class RhumbError(_ValueError): 

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

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

316 ''' 

317 pass 

318 

319 

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

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

322 ''' 

323 pass 

324 

325 

326class SciPyError(PointsError): 

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

328 ''' 

329 pass 

330 

331 

332class SciPyWarning(PointsError): 

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

334 

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

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

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

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

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

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

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

342 ''' 

343 pass 

344 

345 

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

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

348 conversion issue. 

349 ''' 

350 pass 

351 

352 

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

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

355 or C{high} limit. 

356 ''' 

357 pass 

358 

359 

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

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

362 ''' 

363 pass 

364 

365 

366def _an(noun): 

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

368 on the pronounciation of the first letter. 

369 ''' 

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

371 return _SPACE_(a, noun) 

372 

373 

374def _and(*words): 

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

376 ''' 

377 return _and_or(_and_, *words) 

378 

379 

380def _and_or(last, *words): 

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

382 ''' 

383 t, w = NN, list(words) 

384 if w: 

385 t = w.pop() 

386 if w: 

387 w = _COMMASPACE_.join(w) 

388 t = _SPACE_(w, last, t) 

389 return t 

390 

391 

392def crosserrors(raiser=None): 

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

394 

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

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

397 leave the setting unchanged. 

398 

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

400 

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

402 ''' 

403 V = _MODS.vector3dBase.Vector3dBase 

404 t = V._crosserrors # XXX class attr! 

405 if raiser is not None: 

406 V._crosserrors = bool(raiser) 

407 return t 

408 

409 

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

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

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

413 

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

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

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

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

418 particular for name conflicts with keyword 

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

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

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

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

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

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

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

426 exception chaining (supported in Python 3+). 

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

428 ''' 

429 def _fmtuple(pairs): 

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

431 

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

433 if n > 2: 

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

435 s = _basics.isodd(n) 

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

437 t += args[-1:] 

438 elif n == 2: 

439 t = (fmt_name_value % args), 

440 elif n: # == 1 

441 t = str(args[0]), 

442 if kwds: 

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

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

445 

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

447 else typename(txt__)) 

448 if x is not None: 

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

450 C = _COMMASPACE_ if _COLON_ in t else _COLONSPACE_ 

451 t = C(t, x) 

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

453# x = NN # XXX or t? 

454 Error.__init__(inst, t) 

455# inst.__x_txt__ = x # hold explanation 

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

457 _error_under(inst) 

458 

459 

460def _error_under(inst): 

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

462 ''' 

463 t = type(inst) 

464 n = typename(t) # _tailof? 

465 if n.startswith(_UNDER_): 

466 t.__name__ = n.lstrip(_UNDER_) 

467 return inst 

468 

469 

470def exception_chaining(exc=None): 

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

472 

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

474 

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

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

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

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

479 or C{None} if there is none. 

480 

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

482 set env var C{PYGEODESY_EXCEPTION_CHAINING} to any 

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

484 ''' 

485 return _exception_chaining if exc is None else \ 

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

487 

488 

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

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

491 ''' 

492 n = typename(inst) 

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

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

495 _EQUALSPACED_(n, repr(value)) 

496 return Error(_immutable_, txt=t) 

497 

498 

499def _incompatible(this): 

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

501 ''' 

502 return _SPACE_(_incompatible_, _with_, this) 

503 

504 

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

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

507 

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

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

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

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

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

513 

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

515 ''' 

516 return _XError(Error, **txt_name_values_cause) 

517 

518 

519def isError(exc): 

520 '''Check a (caught) exception. 

521 

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

523 

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

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

526 of C{None} if neither. 

527 ''' 

528 def _X(exc): 

529 X = type(exc) 

530 m = X.__module__ 

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

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

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

534 

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

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

537 

538 

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

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

541 

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

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

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

545 for exception chaining. 

546 

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

548 ''' 

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

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

551 n, v = _xkwds_item2(kwds) 

552 

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

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

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

556 

557 

558def limiterrors(raiser=None): 

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

560 

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

562 ignore L{LimitError} exceptions or 

563 C{None} to leave the setting unchanged. 

564 

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

566 ''' 

567 global _limiterrors 

568 t = _limiterrors 

569 if raiser is not None: 

570 _limiterrors = bool(raiser) 

571 return t 

572 

573 

574def _or(*words): 

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

576 ''' 

577 return _and_or(_or_, *words) 

578 

579 

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

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

582 

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

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

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

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

587 

588 @return: Parser result. 

589 

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

591 ''' 

592 try: 

593 return parser(*args) 

594 except Exception as x: 

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

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

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

598 

599 

600def rangerrors(raiser=None): 

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

602 

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

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

605 the setting unchanged. 

606 

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

608 ''' 

609 global _rangerrors 

610 t = _rangerrors 

611 if raiser is not None: 

612 _rangerrors = bool(raiser) 

613 return t 

614 

615 

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

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

618 E = SciPyWarning 

619 else: 

620 E = SciPyError # PYCHOK not really 

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

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

623 

624 

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

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

627 ''' 

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

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

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

631 

632 

633def _xattr(obj, **name_default): 

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

635 ''' 

636 if len(name_default) == 1: 

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

638 try: # obj is tuple 

639 return getattr(obj, n, d) 

640 except ValueError: 

641 return d 

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

643 

644 

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

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

647 

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

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

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

651 

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

653 

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

655 ''' 

656 def _getattr(o, a): 

657 if hasattr(o, a): 

658 return getattr(o, a) 

659 try: 

660 n = o._DOT_(a) 

661 except AttributeError: 

662 n = _streprs.Fmt.DOT(a) 

663 raise _AttributeError(o, name=n) 

664 

665 for a in attrs: 

666 s = _getattr(other, a) 

667 g = _getattr(inst, a) 

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

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

670 return inst 

671 

672 

673def _xcallable(**names_callables): 

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

675 ''' 

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

677 if not callable(c): 

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

679 

680 

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

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

683 ''' 

684 if Error: 

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

686 if e1 != e2: 

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

688 elif datum1 != datum2: 

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

690 _not_, repr(datum2.name)) 

691 raise _AssertionError(t) 

692 

693 

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

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

696 ''' 

697 if len(name_value) == 1: 

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

699 try: 

700 if v.isEllipsoidal: 

701 return v 

702 except AttributeError: 

703 pass 

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

705 raise _xAssertionError(_xellipsoidal, name_value) 

706 

707 

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

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

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

711 ''' 

712 m = _MODS.ellipsoidalBase 

713 ll = isinstance(point, m.LatLonEllipsoidalBase) 

714 if not ll: 

715 _basics._xinstanceof(m.CartesianEllipsoidalBase, 

716 m.LatLonEllipsoidalBase, point=point) 

717 return ll 

718 

719 

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

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

722 ''' 

723 if E2 != E1: 

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

725 return E1 

726 

727 

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

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

730 ''' 

731 try: # C{_Error} style 

732 return Error(*args, **kwds) 

733 except TypeError: # no keyword arguments 

734 pass 

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

736 E = Error(str(e)) 

737 if _exception_chaining: 

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

739 return E 

740 

741 

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

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

744 

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

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

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

748 ''' 

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

750 

751 

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

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

754 

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

756 ''' 

757 x = isError(exc) 

758 if x: 

759 E = type(exc) 

760 elif x is None: 

761 E = _AssertionError 

762 else: # get _Error from Error 

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

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

765 x = E is not _NotImplementedError 

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

767 

768 

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

770 _TypeError, _ValueError, _ZeroDivisionError) 

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

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

773# ZeroDivisionError: _ZeroDivisionError} 

774 

775 

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

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

778 ''' 

779 if G1.ellipsoid != G2.ellipsoid: 

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

781 return G1 

782 

783 

784try: 

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

786 

787 def _xkwds(kwds, **dflts): 

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

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

790 ''' 

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

792 

793except AttributeError: 

794 

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

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

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

798 ''' 

799 d = dflts 

800 if kwds: 

801 d = _copy(d) 

802 d.update(kwds) 

803 return d 

804 

805 

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

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

808# ''' 

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

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

811# if b is None: # invalid bool attr 

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

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

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

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

816 

817 

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

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

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

821# ''' 

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

823# for n, m in items: 

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

825# t = orig[n] 

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

827# yield n, t 

828# 

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

830 

831 

832def _xkwds_get(kwds, **name_default): 

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

834 C{default} if not present. 

835 ''' 

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

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

838 return kwds.get(n, v) 

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

840 

841 

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

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

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

845# ''' 

846# if not isinstance(kwds, dict): 

847# raise _xAssertionError(_xkwds_get_, kwds) 

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

849# yield kwds.get(n, v) 

850 

851 

852def _xkwds_get1(kwds, **name_default): 

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

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

855 with any remaining keyword arguments. 

856 ''' 

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

858 if kwds: 

859 raise _UnexpectedError(**kwds) 

860 return v 

861 

862 

863def _xkwds_item2(kwds): 

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

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

866 ''' 

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

868 for item in kwds.items(): 

869 return item 

870 raise _xAssertionError(_xkwds_item2, kwds) 

871 

872 

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

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

875 ''' 

876 if not isinstance(kwds, dict): 

877 raise _xAssertionError(_xkwds_kwds, kwds) 

878 _g = kwds.get 

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

880 

881 

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

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

884 ''' 

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

886 

887 

888def _xkwds_pop(kwds, **name_default): 

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

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

891 ''' 

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

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

894 return kwds.pop(n, v) 

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

896 

897 

898def _xkwds_pop2(kwds, **name_default): 

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

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

901 ''' 

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

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

904 if n in kwds: 

905 kwds = _copy(kwds) 

906 v = kwds.pop(n, v) 

907 return v, kwds 

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

909 

910 

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

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

913 ''' 

914 X, m = _xkwds_item2(Xorder) 

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

916 return m 

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

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

919 

920 

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

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

923 ''' 

924 def _xs(*xs): 

925 if len(xs) > 4: 

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

927 return xs 

928 

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

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

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

932 

933 

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

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

936 ''' 

937 s = typename(_MODS.unitsBase.Str) 

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

939 return _IsnotError(*t, **name_value_Error) 

940 

941# **) MIT License 

942# 

943# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved. 

944# 

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

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

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

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

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

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

951# 

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

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

954# 

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

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

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

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

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

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

961# OTHER DEALINGS IN THE SOFTWARE.