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_, _name_, _no_, _not_, _or_, _SPACE_, \

19 _specified_, _UNDER_, _value_, _vs_, _with_

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

21 _pairs, _PYTHON_X_DEV

22

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

24__version__ = '23.12.14'

25

26_box_ = 'box'

27_default_ = 'default'

28_kwargs_ = 'kwargs' # XXX _kwds_?

29_limiterrors = True # in .formy

30_multiple_ = 'multiple'

31_name_value_ = repr('name=value')

32_rangerrors = True # in .dms

33_region_ = 'region'

34_vs__ = _SPACE_(NN, _vs_, NN)

35

36 try:

37 _exception_chaining = None # not available

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

39

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

41 _exception_chaining = True # turned on, std

42 raise AttributeError # allow exception chaining

43

44 _exception_chaining = False # turned off

45

46 def _error_cause(inst, cause=None):

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

48

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

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

51

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

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

54 or C{None} to avoid exception chaining.

55

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

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

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

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

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

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

62 '''

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

64 return inst

65

66 except AttributeError: # Python 2+

67

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

69 return inst # no-op

70

71

72 class _AssertionError(AssertionError):

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

74 '''

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

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

77

78

79 class _AttributeError(AttributeError):

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

81 '''

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

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

84

85

86 class _ImportError(ImportError):

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

88 '''

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

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

91

92

93 class _IndexError(IndexError):

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

95 '''

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

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

98

99

100 class _KeyError(KeyError):

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

102 '''

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

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

105

106

107 class _NameError(NameError):

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

109 '''

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

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

112

113

114 class _NotImplementedError(NotImplementedError):

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

116 '''

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

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

119

120

121 class _OverflowError(OverflowError):

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

123 '''

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

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

126

127

128 class _TypeError(TypeError):

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

130 '''

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

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

133

134

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

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

137 '''

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

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

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

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

142

143

144 class _ValueError(ValueError):

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

146 '''

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

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

149

150

151 class _ZeroDivisionError(ZeroDivisionError):

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

153 '''

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

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

156

157

158 class AuxError(_ValueError):

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

160 '''

161 pass

162

163

164 class ClipError(_ValueError):

165 '''Clip box or clip region issue.

166 '''

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

168 '''New L{ClipError}.

169

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

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

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

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

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

175 for exception chaining.

176 '''

177 if len(name_n_corners) == 3:

178 t, n, v = name_n_corners

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

180 name_n_corners = n, v

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

182

183

184 class CrossError(_ValueError):

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

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

187 '''

188 pass

189

190

191 class GeodesicError(_ValueError):

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

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

194 '''

195 pass

196

197

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

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

200 '''

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

202 '''New L{IntersectionError}.

203 '''

204 if args:

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

206 else:

207 _ValueError.__init__(self, **kwds)

208

209

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

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

212 '''

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

214 '''New L{LenError}.

215

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

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

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

219 (C{keyword arguments}).

220 '''

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

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

223 return ns, vs, txt, cause

224

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

226 ns = _COMMASPACE_.join(ns)

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

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

229 t = _SPACE_(t, _len_, vs)

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

231

232

233 class LimitError(_ValueError):

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

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

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

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

238

239 @see: Subclass L{UnitError}.

240 '''

241 pass

242

243

244 class MGRSError(_ValueError):

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

246 '''

247 pass

248

249

250 class NumPyError(_ValueError):

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

252 '''

253 pass

254

255

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

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

258 '''

259 pass

260

261

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

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

264 '''

265 pass

266

267

268 class RangeError(_ValueError):

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

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

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

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

273 L{pygeodesy.clipRadians}.

274

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

276 '''

277 pass

278

279

280 class RhumbError(_ValueError):

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

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

283 '''

284 pass

285

286

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

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

289 '''

290 pass

291

292

293 class SciPyError(PointsError):

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

295 '''

296 pass

297

298

299 class SciPyWarning(PointsError):

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

301

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

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

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

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

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

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

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

309 '''

310 pass

311

312

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

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

315 or L{RefFrame} conversion issue.

316 '''

317 pass

318

319

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

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

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

323 '''

324 pass

325

326

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

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

329 '''

330 pass

331

332

333 def _an(noun):

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

335 on the pronounciation of the first letter.

336 '''

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

338 return _SPACE_(a, noun)

339

340

341 def _and(*words):

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

343 '''

344 return _and_or(_and_, *words)

345

346

347 def _and_or(last, *words):

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

349 '''

350 t, w = NN, list(words)

351 if w:

352 t = w.pop()

353 if w:

354 w = _COMMASPACE_.join(w)

355 t = _SPACE_(w, last, t)

356 return t

357

358

359 def crosserrors(raiser=None):

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

361

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

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

364 leave the setting unchanged.

365

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

367

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

369 '''

370 B = _MODS.vector3dBase.Vector3dBase

371 t = B._crosserrors # XXX class attr!

372 if raiser in (True, False):

373 B._crosserrors = raiser

374 return t

375

376

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

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

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

380

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

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

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

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

385 particular for name conflicts with keyword

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

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

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

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

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

391 exception chaining (supported in Python 3+).

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

393 '''

394 def _fmtuple(pairs):

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

396

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

398 if n > 2:

399 s = _MODS.basics.isodd(n)

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

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

402 t += args[-1:]

403 elif n == 2:

404 t = (fmt_name_value % args),

405 elif n: # == 1

406 t = str(args[0]),

407

408 if kwds:

409 t += _fmtuple(itemsorted(kwds))

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

411

412 if txt is not None:

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

414 C = _COMMASPACE_ if _COLON_ in t else _COLONSPACE_

415 t = C(t, x)

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

417# x = NN # XXX or t?

418 Error.__init__(inst, t)

419# inst.__x_txt__ = x # hold explanation

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

421 _error_under(inst)

422

423

424 def _error_under(inst):

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

426 '''

427 n = inst.__class__.__name__

428 if n.startswith(_UNDER_):

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

430 return inst

431

432

433 def exception_chaining(error=None):

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

435

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

437

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

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

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

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

442 I{cause} or C{None}.

443

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

445 set env var C{PYGEODESY_EXCEPTION_CHAINING} to any

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

447 '''

448 return _exception_chaining if error is None else \

449 getattr(error, '__cause__', None)

450

451

452 def _incompatible(this):

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

454 '''

455 return _SPACE_(_incompatible_, _with_, this)

456

457

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

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

460

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

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

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

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

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

466

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

468 '''

469 return _XError(Error, **txt_name_values_cause)

470

471

472 def isError(obj):

473 '''Check a (caught) exception.

474

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

476

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

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

479 of C{None} if neither.

480 '''

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

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

483

484

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

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

487

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

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

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

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

492

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

494 '''

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

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

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

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

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

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

501

502

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

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

505 and in I{ascending} order.

506

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

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

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

510 results in C{descending} order.

511 '''

512 def _un(item):

513 return item[0].lower()

514

515 # see .rhumb.Rhumb and ._RhumbLine

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

517 if asorted_reverse else (True, False)

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

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

520

521

522 def limiterrors(raiser=None):

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

524

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

526 ignore L{LimitError} exceptions. Use

527 C{None} to leave the setting unchanged.

528

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

530 '''

531 global _limiterrors

532 t = _limiterrors

533 if raiser in (True, False):

534 _limiterrors = raiser

535 return t

536

537

538 def _or(*words):

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

540 '''

541 return _and_or(_or_, *words)

542

543

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

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

546

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

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

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

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

551 argument to override the default.

552

553 @return: Parser result.

554

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

556 '''

557 try:

558 return parser(*args)

559 except Exception as x:

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

561 ParseError)

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

563

564

565 def rangerrors(raiser=None):

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

567

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

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

570 the setting unchanged.

571

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

573 '''

574 global _rangerrors

575 t = _rangerrors

576 if raiser in (True, False):

577 _rangerrors = raiser

578 return t

579

580

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

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

583 Error = SciPyWarning

584 else:

585 Error = SciPyError # PYCHOK not really

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

587 return Error(t, cause=x)

588

589

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

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

592 '''

593 if len(name_default) == 1:

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

595 return getattr(obj, n, d)

596 raise _xkwds_Error(_xattr, {}, name_default)

597

598

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

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

601 '''

602 if Error:

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

604 if E1 != E2:

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

606 elif datum1 != datum2:

607 t = _SPACE_(_datum_, repr(datum1.name), _not_, repr(datum2.name))

608 raise _AssertionError(t)

609

610

611 def _xellipsoidal(**name_value):

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

613

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

615

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

617 '''

618 try:

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

620 if v.isEllipsoidal:

621 return v

622 break

623 else:

624 n = v = MISSING

625 except AttributeError:

626 pass

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

628

629

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

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

632 '''

633 try: # C{_Error} style

634 return Error(*args, **kwds)

635 except TypeError: # no keyword arguments

636 pass

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

638 E = Error(str(e))

639 if _exception_chaining:

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

641 return E

642

643

644_XErrors = _TypeError, _ValueError

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

646_X2Error = {AssertionError: _AssertionError,

647 AttributeError: _AttributeError,

648 ImportError: _ImportError,

649 IndexError: _IndexError,

650 KeyError: _KeyError,

651 NameError: _NameError,

652 NotImplementedError: _NotImplementedError,

653 OverflowError: _OverflowError,

654 TypeError: _TypeError,

655 ValueError: _ValueError,

656 ZeroDivisionError: _ZeroDivisionError}

657

658

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

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

661

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

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

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

665 '''

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

667

668

669 def _xError2(x): # in .fsums

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

671

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

673 '''

674 X = type(x)

675 E = _X2Error.get(X, X)

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

677 E = _NotImplementedError

678 t = repr(x)

679 else:

680 t = str(x)

681 return E, t

682

683

684 try:

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

686

687 def _xkwds(kwds, **dflts):

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

689 '''

690 return (dflts | kwds) if kwds else dflts

691

692 except AttributeError:

693 from copy import copy as _copy

694

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

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

697 '''

698 d = dflts

699 if kwds:

700 d = _copy(d)

701 d.update(kwds)

702 return d

703

704

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

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

707 '''

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

709 b = getattr(inst, n, None)

710 if b is None: # invalid bool attr

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

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

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

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

715

716

717 def _xkwds_Error(where, kwds, name_txt, txt=_default_):

718 # Helper for _xkwds_get, _xkwds_pop and _xkwds_popitem below

719 f = _COMMASPACE_.join(_pairs(kwds) + _pairs(name_txt))

720 f = _MODS.streprs.Fmt.PAREN(where.__name__, f)

721 t = _multiple_ if name_txt else _no_

722 t = _SPACE_(t, _EQUAL_(_name_, txt), _kwargs_)

723 return _AssertionError(f, txt=t)

724

725

726 def _xkwds_get(kwds, **name_default):

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

728 '''

729 if len(name_default) == 1:

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

731 return kwds.get(n, d)

732 raise _xkwds_Error(_xkwds_get, kwds, name_default)

733

734

735 def _xkwds_get_(kwds, **names_defaults):

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

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

738 '''

739 for n, d in itemsorted(names_defaults):

740 yield kwds.get(n, d)

741

742

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

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

745 '''

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

747

748

749 def _xkwds_pop(kwds, **name_default):

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

751 '''

752 if len(name_default) == 1:

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

754 return kwds.pop(n, d)

755 raise _xkwds_Error(_xkwds_pop, kwds, name_default)

756

757

758 def _xkwds_pop_(kwds, **names_defaults):

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

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

761 '''

762 for n, d in itemsorted(names_defaults):

763 yield kwds.pop(n, d)

764

765

766 def _xkwds_popitem(name_value):

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

768 '''

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

770 return name_value.popitem() # XXX AttributeError

771 raise _xkwds_Error(_xkwds_popitem, (), name_value, txt=_value_)

772

773

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

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

776 '''

777 X, m = Xorder.popitem()

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

779 return m

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

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

782

783

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

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

786 '''

787 s = _xkwds_get(strict, strict=True)

788 if s:

789 _zip = _MODS.basics._zip

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

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

792 raise _NotImplementedError(t, txt=None)

793 return _zip(*args)

794 return zip(*args)

795

796# **) MIT License

797#

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

799#

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

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

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

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

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

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

806#

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

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

809#

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

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

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

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

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

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

816# OTHER DEALINGS IN THE SOFTWARE.