1

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

3

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

5

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

7 including the setting of I{exception chaining} in Python 3+.

8

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

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

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

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

13'''

14

15 from pygeodesy.interns import MISSING, NN, _a_, _an_, _and_, _clip_, \

16 _COLON_, _COLONSPACE_, _COMMASPACE_, _datum_, \

17 _ellipsoidal_, _EQUAL_, _incompatible_, _invalid_, \

18 _len_, _not_, _or_, _SPACE_, _specified_, _UNDER_, \

19 _vs_, _with_

20 from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _getenv, _PYTHON_X_DEV

21

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

23__version__ = '24.01.02'

24

25_box_ = 'box'

26_limiterrors = True # in .formy

27_name_value_ = repr('name=value')

28_rangerrors = True # in .dms

29_region_ = 'region'

30_vs__ = _SPACE_(NN, _vs_, NN)

31

32 try:

33 _exception_chaining = None # not available

34 _ = Exception().__cause__ # Python 3+ exception chaining

35

36 if _PYTHON_X_DEV or _getenv('PYGEODESY_EXCEPTION_CHAINING', NN): # == _std_

37 _exception_chaining = True # turned on, std

38 raise AttributeError # allow exception chaining

39

40 _exception_chaining = False # turned off

41

42 def _error_cause(inst, cause=None):

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

44

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

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

47

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

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

50 or C{None} to avoid exception chaining.

51

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

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

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

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

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

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

58 '''

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

60 return inst

61

62 except AttributeError: # Python 2+

63

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

65 return inst # no-op

66

67

68 class _AssertionError(AssertionError):

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

70 '''

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

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

73

74

75 class _AttributeError(AttributeError):

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

77 '''

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

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

80

81

82 class _ImportError(ImportError):

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

84 '''

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

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

87

88

89 class _IndexError(IndexError):

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

91 '''

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

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

94

95

96 class _KeyError(KeyError):

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

98 '''

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

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

101

102

103 class _NameError(NameError):

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

105 '''

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

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

108

109

110 class _NotImplementedError(NotImplementedError):

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

112 '''

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

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

115

116

117 class _OverflowError(OverflowError):

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

119 '''

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

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

122

123

124 class _TypeError(TypeError):

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

126 '''

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

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

129

130

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

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

133 '''

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

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

136 t = _not_(_an(_or(*(t.__name__ for t in Types))))

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

138

139

140 class _ValueError(ValueError):

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

142 '''

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

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

145

146

147 class _ZeroDivisionError(ZeroDivisionError):

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

149 '''

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

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

152

153

154 class AuxError(_ValueError):

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

156 '''

157 pass

158

159

160 class ClipError(_ValueError):

161 '''Clip box or clip region issue.

162 '''

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

164 '''New L{ClipError}.

165

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

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

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

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

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

171 for exception chaining.

172 '''

173 if len(name_n_corners) == 3:

174 t, n, v = name_n_corners

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

176 name_n_corners = n, v

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

178

179

180 class CrossError(_ValueError):

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

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

183 '''

184 pass

185

186

187 class GeodesicError(_ValueError):

188 '''Error raised for lack of convergence or other issues in L{pygeodesy.geodesicx},

189 L{pygeodesy.geodesicw} or L{pygeodesy.karney}.

190 '''

191 pass

192

193

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

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

196 '''

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

198 '''New L{IntersectionError}.

199 '''

200 if args:

201 _ValueError.__init__(self, _SPACE_(*args), **kwds)

202 else:

203 _ValueError.__init__(self, **kwds)

204

205

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

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

208 '''

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

210 '''New L{LenError}.

211

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

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

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

215 (C{keyword arguments}).

216 '''

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

218 ns, vs = zip(*itemsorted(kwds)) # unzip

219 return ns, vs, txt, cause

220

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

222 ns = _COMMASPACE_.join(ns)

223 t = _MODS.streprs.Fmt.PAREN(where.__name__, ns)

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

225 t = _SPACE_(t, _len_, vs)

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

227

228

229 class LimitError(_ValueError):

230 '''Error raised for lat- or longitudinal values or deltas exceeding

231 the given B{C{limit}} in functions L{pygeodesy.equirectangular},

232 L{pygeodesy.equirectangular_}, C{nearestOn*} and C{simplify*}

233 or methods with C{limit} or C{options} keyword arguments.

234

235 @see: Subclass L{UnitError}.

236 '''

237 pass

238

239

240 class MGRSError(_ValueError):

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

242 '''

243 pass

244

245

246 class NumPyError(_ValueError):

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

248 '''

249 pass

250

251

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

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

254 '''

255 pass

256

257

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

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

260 '''

261 pass

262

263

264 class RangeError(_ValueError):

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

266 B{C{clipLat}}, B{C{clipLon}} in functions L{pygeodesy.parse3llh},

267 L{pygeodesy.parseDMS}, L{pygeodesy.parseDMS2} and L{pygeodesy.parseRad}

268 or the given B{C{limit}} in functions L{pygeodesy.clipDegrees} and

269 L{pygeodesy.clipRadians}.

270

271 @see: Function L{pygeodesy.rangerrors}.

272 '''

273 pass

274

275

276 class RhumbError(_ValueError):

277 '''Error raised for a L{pygeodesy.rhumb.aux_}, L{pygeodesy.rhumb.ekx}

278 or L{pygeodesy.rhumb.solve} issue.

279 '''

280 pass

281

282

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

284 '''Error raised for triangle, inter- or resection issues.

285 '''

286 pass

287

288

289 class SciPyError(PointsError):

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

291 '''

292 pass

293

294

295 class SciPyWarning(PointsError):

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

297

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

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

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

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

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

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

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

305 '''

306 pass

307

308

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

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

311 or L{RefFrame} conversion issue.

312 '''

313 pass

314

315

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

317 '''Default exception for L{units} issues for a value exceeding the

318 C{low} or C{high} limit.

319 '''

320 pass

321

322

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

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

325 '''

326 pass

327

328

329 def _an(noun):

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

331 on the pronounciation of the first letter.

332 '''

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

334 return _SPACE_(a, noun)

335

336

337 def _and(*words):

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

339 '''

340 return _and_or(_and_, *words)

341

342

343 def _and_or(last, *words):

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

345 '''

346 t, w = NN, list(words)

347 if w:

348 t = w.pop()

349 if w:

350 w = _COMMASPACE_.join(w)

351 t = _SPACE_(w, last, t)

352 return t

353

354

355 def crosserrors(raiser=None):

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

357

358 @kwarg raiser: Use C{True} to throw or C{False} to ignore

359 L{CrossError} exceptions. Use C{None} to

360 leave the setting unchanged.

361

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

363

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

365 '''

366 B = _MODS.vector3dBase.Vector3dBase

367 t = B._crosserrors # XXX class attr!

368 if raiser in (True, False):

369 B._crosserrors = raiser

370 return t

371

372

373 def _error_init(Error, inst, args, fmt_name_value='%s (%r)', txt=NN,

374 cause=None, **kwds): # by .lazily

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

376

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

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

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

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

381 particular for name conflicts with keyword

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

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

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

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

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

387 exception chaining (supported in Python 3+).

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

389 '''

390 def _fmtuple(pairs):

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

392

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

394 if n > 2:

395 s = _MODS.basics.isodd(n)

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

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

398 t += args[-1:]

399 elif n == 2:

400 t = (fmt_name_value % args),

401 elif n: # == 1

402 t = str(args[0]),

403

404 if kwds:

405 t += _fmtuple(itemsorted(kwds))

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

407

408 if txt is not None:

409 x = str(txt) or (str(cause) if cause else _invalid_)

410 C = _COMMASPACE_ if _COLON_ in t else _COLONSPACE_

411 t = C(t, x)

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

413# x = NN # XXX or t?

414 Error.__init__(inst, t)

415# inst.__x_txt__ = x # hold explanation

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

417 _error_under(inst)

418

419

420 def _error_under(inst):

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

422 '''

423 n = inst.__class__.__name__

424 if n.startswith(_UNDER_):

425 inst.__class__.__name__ = n.lstrip(_UNDER_)

426 return inst

427

428

429 def exception_chaining(error=None):

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

431

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

433

434 @return: If C{B{error} is None}, return C{True} if exception

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

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

437 B{C{error}} is not C{None}, return it's error

438 I{cause} or C{None}.

439

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

441 set env var C{PYGEODESY_EXCEPTION_CHAINING} to any

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

443 '''

444 return _exception_chaining if error is None else \

445 getattr(error, '__cause__', None)

446

447

448 def _incompatible(this):

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

450 '''

451 return _SPACE_(_incompatible_, _with_, this)

452

453

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

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

456

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

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

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

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

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

462

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

464 '''

465 return _XError(Error, **txt_name_values_cause)

466

467

468 def isError(obj):

469 '''Check a (caught) exception.

470

471 @arg obj: The exception C({Exception}).

472

473 @return: C{True} if B{C{obj}} is a C{pygeodesy} error,

474 C{False} if B{C{obj}} is a standard Python error

475 of C{None} if neither.

476 '''

477 return True if isinstance(obj, _XErrors) else (

478 False if isinstance(obj, Exception) else None)

479

480

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

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

483

484 @arg nouns: One or more expected class or type names, usually nouns (C{str}).

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

486 keyword argument C{B{Error}=TypeError} to override the default

487 and C{B{cause}=None} for exception chaining.

488

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

490 '''

491 x, Error = _xkwds_pop_(name_value_Error_cause, cause=None, Error=TypeError)

492 n, v = _xkwds_popitem(name_value_Error_cause) if name_value_Error_cause \

493 else (_name_value_, MISSING) # XXX else tuple(...)

494 n = _MODS.streprs.Fmt.PARENSPACED(n, repr(v))

495 t = _not_(_an(_or(*nouns)) if nouns else _specified_)

496 return _XError(Error, n, txt=t, cause=x)

497

498

499 def itemsorted(adict, *args, **asorted_reverse):

500 '''Return the items of C{B{adict}} sorted I{alphabetically, case-insensitively}

501 and in I{ascending} order.

502

503 @arg args: Optional argument(s) for method C{B{adict}.items(B*{args})}.

504 @kwarg asorted_reverse: Use keyword argument C{B{asorted}=False} for

505 I{case-sensitive} sorting and C{B{reverse}=True} for

506 results in C{descending} order.

507 '''

508 def _un(item):

509 return item[0].lower()

510

511 # see .rhumb.Rhumb and ._RhumbLine

512 a, r = _xkwds_get_(asorted_reverse, asorted=True, reverse=False) \

513 if asorted_reverse else (True, False)

514 items = adict.items(*args) if args else adict.items()

515 return sorted(items, reverse=r, key=_un if a else None)

516

517

518 def limiterrors(raiser=None):

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

520

521 @kwarg raiser: Choose C{True} to raise or C{False} to

522 ignore L{LimitError} exceptions. Use

523 C{None} to leave the setting unchanged.

524

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

526 '''

527 global _limiterrors

528 t = _limiterrors

529 if raiser in (True, False):

530 _limiterrors = raiser

531 return t

532

533

534 def _or(*words):

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

536 '''

537 return _and_or(_or_, *words)

538

539

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

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

542

543 @arg parser: The parser (C{callable}).

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

545 @kwarg name_values_Error: Any C{B{name}=value} pairs and

546 optionally, C{B{Error}=ParseError} keyword

547 argument to override the default.

548

549 @return: Parser result.

550

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

552 '''

553 try:

554 return parser(*args)

555 except Exception as x:

556 E = _xkwds_pop(name_values_Error, Error=type(x) if isError(x) else

557 ParseError)

558 raise _XError(E, **_xkwds(name_values_Error, cause=x))

559

560

561 def rangerrors(raiser=None):

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

563

564 @kwarg raiser: Choose C{True} to raise or C{False} to ignore

565 L{RangeError} exceptions. Use C{None} to leave

566 the setting unchanged.

567

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

569 '''

570 global _rangerrors

571 t = _rangerrors

572 if raiser in (True, False):

573 _rangerrors = raiser

574 return t

575

576

577 def _SciPyIssue(x, *extras): # PYCHOK no cover

578 if isinstance(x, (RuntimeWarning, UserWarning)):

579 Error = SciPyWarning

580 else:

581 Error = SciPyError # PYCHOK not really

582 t = _SPACE_(str(x).strip(), *extras)

583 return Error(t, cause=x)

584

585

586 def _xAssertionError(where, *args, **kwds): # in .basics

587 '''(INTERNAL) Embellish an C{AssertionError}.

588 '''

589 t = _MODS.streprs.unstr(where, *args, **kwds)

590 return _AssertionError(t)

591

592

593 def _xattr(obj, **name_default): # see .strerprs._xattrs

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

595 '''

596 if len(name_default) == 1:

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

598 return getattr(obj, n, d)

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

600

601

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

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

604 '''

605 if Error:

606 E1, E2 = datum1.ellipsoid, datum2.ellipsoid

607 if E1 != E2:

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

609 elif datum1 != datum2:

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

611 _not_, repr(datum2.name))

612 raise _AssertionError(t)

613

614

615 def _xellipsoidal(**name_value):

616 '''(INTERNAL) Check an I{ellipsoidal} item.

617

618 @return: The B{C{value}} if ellipsoidal.

619

620 @raise TypeError: Not ellipsoidal B{C{value}}.

621 '''

622 try:

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

624 if v.isEllipsoidal:

625 return v

626 break

627 else:

628 n = v = MISSING

629 except AttributeError:

630 pass

631 raise _TypeError(n, v, txt=_not_(_ellipsoidal_))

632

633

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

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

636 '''

637 try: # C{_Error} style

638 return Error(*args, **kwds)

639 except TypeError: # no keyword arguments

640 pass

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

642 E = Error(str(e))

643 if _exception_chaining:

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

645 return E

646

647

648_XErrors = _TypeError, _ValueError

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

650_X2Error = {AssertionError: _AssertionError,

651 AttributeError: _AttributeError,

652 ImportError: _ImportError,

653 IndexError: _IndexError,

654 KeyError: _KeyError,

655 NameError: _NameError,

656 NotImplementedError: _NotImplementedError,

657 OverflowError: _OverflowError,

658 TypeError: _TypeError,

659 ValueError: _ValueError,

660 ZeroDivisionError: _ZeroDivisionError}

661

662

663 def _xError(x, *args, **kwds):

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

665

666 @arg x: The exception (usually, C{_Error}).

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

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

669 '''

670 return _XError(type(x), *args, **_xkwds(kwds, cause=x))

671

672

673 def _xError2(x): # in .fsums

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

675

676 @arg x: The exception instance (usually, C{Exception}).

677 '''

678 X = type(x)

679 E = _X2Error.get(X, X)

680 if E is X and not isError(x):

681 E = _NotImplementedError

682 t = repr(x)

683 else:

684 t = str(x)

685 return E, t

686

687

688 try:

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

690

691 def _xkwds(kwds, **dflts):

692 '''(INTERNAL) Override C{dflts} with specified C{kwds}.

693 '''

694 return (dflts | kwds) if kwds else dflts

695

696 except AttributeError:

697 from copy import copy as _copy

698

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

700 '''(INTERNAL) Override C{dflts} with specified C{kwds}.

701 '''

702 d = dflts

703 if kwds:

704 d = _copy(d)

705 d.update(kwds)

706 return d

707

708

709 def _xkwds_bool(inst, **kwds): # in .frechet, .hausdorff, .heights

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

711 '''

712 for n, v in kwds.items():

713 b = getattr(inst, n, None)

714 if b is None: # invalid bool attr

715 t = _SPACE_(_EQUAL_(n, repr(v)), 'for', inst.__class__.__name__) # XXX .classname

716 raise _AttributeError(t, txt=_not_('applicable'))

717 if v in (False, True) and v != b:

718 setattr(inst, NN(_UNDER_, n), v)

719

720

721 def _xkwds_get(kwds, **name_default):

722 '''(INTERNAL) Get a C{kwds} value by C{name}, or the C{default}.

723 '''

724 if len(name_default) == 1:

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

726 return kwds.get(n, d)

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

728

729

730 def _xkwds_get_(kwds, **names_defaults):

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

732 in I{case-insensitive, alphabetical} order.

733 '''

734 for n, d in itemsorted(names_defaults):

735 yield kwds.get(n, d)

736

737

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

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

740 '''

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

742

743

744 def _xkwds_pop(kwds, **name_default):

745 '''(INTERNAL) Pop a C{kwds} value by C{name}, or the C{default}.

746 '''

747 if len(name_default) == 1:

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

749 return kwds.pop(n, d)

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

751

752

753 def _xkwds_pop_(kwds, **names_defaults):

754 '''(INTERNAL) Pop and yield each C{kwds} value or its C{default}

755 in I{case-insensitive, alphabetical} order.

756 '''

757 for n, d in itemsorted(names_defaults):

758 yield kwds.pop(n, d)

759

760

761 def _xkwds_popitem(name_value):

762 '''(INTERNAL) Return exactly one C{(name, value)} item.

763 '''

764 if len(name_value) == 1: # XXX TypeError

765 return name_value.popitem() # XXX AttributeError

766 raise _xAssertionError(_xkwds_popitem, name_value)

767

768

769 def _Xorder(_Coeffs, Error, **Xorder): # in .ktm, .rhumbBase

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

771 '''

772 X, m = Xorder.popitem()

773 if m in _Coeffs and _MODS.basics.isint(m):

774 return m

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

776 raise Error(X, m, txt=_not_(_or(*t)))

777

778

779 def _xzip(*args, **strict): # PYCHOK no cover

780 '''(INTERNAL) Standard C{zip(..., strict=True)}.

781 '''

782 s = _xkwds_get(strict, strict=True)

783 if s:

784 _zip = _MODS.basics._zip

785 if _zip is zip: # < (3, 10)

786 t = _MODS.streprs.unstr(_xzip.__name__, *args, strict=s)

787 raise _NotImplementedError(t, txt=None)

788 return _zip(*args)

789 return zip(*args)

790

791# **) MIT License

792#

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

794#

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

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

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

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

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

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

801#

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

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

804#

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

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

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

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

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

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

811# OTHER DEALINGS IN THE SOFTWARE.