Coverage for pygeodesy/errors.py: 95%
267 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-03-03 11:31 -0500
« prev ^ index » next coverage.py v7.2.2, created at 2024-03-03 11:31 -0500
2# -*- coding: utf-8 -*-
4u'''Errors, exceptions, exception formatting and exception chaining.
6Error, exception classes and functions to format PyGeodesy errors, including
7the setting of I{exception chaining} for Python 3.9+.
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.basics import isint, isodd, itemsorted, _xinstanceof, _zip # _MODS
15# from pygeodesy.ellipsoidalBase import CartesianEllipsoidalBase, LatLonEllipsoidalBase # _MODS
16# from pygeodesy import errors # _MODS.getattr
17from pygeodesy.interns import MISSING, NN, _a_, _an_, _and_, _clip_, _COLON_, \
18 _COLONSPACE_, _COMMASPACE_, _datum_, _ellipsoidal_, \
19 _incompatible_, _invalid_, _len_, _not_, _or_, _SPACE_, \
20 _specified_, _UNDER_, _vs_, _with_, _tailof
21from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _getenv, _PYTHON_X_DEV
22# from pygeodesy.streprs import Fmt, unstr # _MODS
23# from pygeodesy.vector3dBase import Vector3dBase # _MODS
25from copy import copy as _copy
27__all__ = _ALL_LAZY.errors # _ALL_DOCS('_InvalidError', '_IsnotError') _under
28__version__ = '24.02.20'
30_box_ = 'box'
31_limiterrors = True # in .formy
32_name_value_ = repr('name=value')
33_rangerrors = True # in .dms
34_region_ = 'region'
35_vs__ = _SPACE_(NN, _vs_, NN)
37try:
38 _exception_chaining = None # not available
39 _ = Exception().__cause__ # Python 3.9+ exception chaining
41 if _PYTHON_X_DEV or _getenv('PYGEODESY_EXCEPTION_CHAINING', NN): # == _std_
42 _exception_chaining = True # turned on, std
43 raise AttributeError # allow exception chaining
45 _exception_chaining = False # turned off
47 def _error_cause(inst, cause=None):
48 '''(INTERNAL) Set or avoid Python 3+ exception chaining.
50 Setting C{inst.__cause__ = None} is equivalent to syntax
51 C{raise Error(...) from None} to avoid exception chaining.
53 @arg inst: An error instance (I{caught} C{Exception}).
54 @kwarg cause: A previous error instance (I{caught} C{Exception})
55 or C{None} to avoid exception chaining.
57 @see: Alex Martelli, et.al., "Python in a Nutshell", 3rd Ed., page 163,
58 O'Reilly, 2017, U{PEP-3134<https://www.Python.org/dev/peps/pep-3134>},
59 U{here<https://StackOverflow.com/questions/17091520/how-can-i-more-
60 easily-suppress-previous-exceptions-when-i-raise-my-own-exception>}
61 and U{here<https://StackOverflow.com/questions/1350671/
62 inner-exception-with-traceback-in-python>}.
63 '''
64 inst.__cause__ = cause # None, no exception chaining
65 return inst
67except AttributeError: # Python 2+
69 def _error_cause(inst, **unused): # PYCHOK expected
70 return inst # no-op
73class _AssertionError(AssertionError):
74 '''(INTERNAL) Format an C{AssertionError} with/-out exception chaining.
75 '''
76 def __init__(self, *args, **kwds):
77 _error_init(AssertionError, self, args, **kwds)
80class _AttributeError(AttributeError):
81 '''(INTERNAL) Format an C{AttributeError} with/-out exception chaining.
82 '''
83 def __init__(self, *args, **kwds):
84 _error_init(AttributeError, self, args, **kwds)
87class _ImportError(ImportError):
88 '''(INTERNAL) Format an C{ImportError} with/-out exception chaining.
89 '''
90 def __init__(self, *args, **kwds):
91 _error_init(ImportError, self, args, **kwds)
94class _IndexError(IndexError):
95 '''(INTERNAL) Format an C{IndexError} with/-out exception chaining.
96 '''
97 def __init__(self, *args, **kwds):
98 _error_init(IndexError, self, args, **kwds)
101class _KeyError(KeyError):
102 '''(INTERNAL) Format a C{KeyError} with/-out exception chaining.
103 '''
104 def __init__(self, *args, **kwds): # txt=_invalid_
105 _error_init(KeyError, self, args, **kwds)
108class _NameError(NameError):
109 '''(INTERNAL) Format a C{NameError} with/-out exception chaining.
110 '''
111 def __init__(self, *args, **kwds):
112 _error_init(NameError, self, args, **kwds)
115class _NotImplementedError(NotImplementedError):
116 '''(INTERNAL) Format a C{NotImplementedError} with/-out exception chaining.
117 '''
118 def __init__(self, *args, **kwds):
119 _error_init(NotImplementedError, self, args, **kwds)
122class _OverflowError(OverflowError):
123 '''(INTERNAL) Format an C{OverflowError} with/-out exception chaining.
124 '''
125 def __init__(self, *args, **kwds): # txt=_invalid_
126 _error_init(OverflowError, self, args, **kwds)
129class _TypeError(TypeError):
130 '''(INTERNAL) Format a C{TypeError} with/-out exception chaining.
131 '''
132 def __init__(self, *args, **kwds):
133 _error_init(TypeError, self, args, fmt_name_value='type(%s) (%r)', **kwds)
136def _TypesError(name, value, *Types, **kwds):
137 '''(INTERNAL) Format a C{TypeError} with/-out exception chaining.
138 '''
139 # no longer C{class _TypesError} to avoid missing value
140 # argument errors in _XError line ...E = Error(str(e))
141 t = _not_(_an(_or(*(t.__name__ for t in Types))))
142 return _TypeError(name, value, txt=t, **kwds)
145class _ValueError(ValueError):
146 '''(INTERNAL) Format a C{ValueError} with/-out exception chaining.
147 '''
148 def __init__(self, *args, **kwds): # ..., cause=None, txt=_invalid_, ...
149 _error_init(ValueError, self, args, **kwds)
152class _ZeroDivisionError(ZeroDivisionError):
153 '''(INTERNAL) Format a C{ZeroDivisionError} with/-out exception chaining.
154 '''
155 def __init__(self, *args, **kwds):
156 _error_init(ZeroDivisionError, self, args, **kwds)
159class AuxError(_ValueError):
160 '''Error raised for a L{rhumb.aux_}, C{Aux}, C{AuxDLat} or C{AuxLat} issue.
161 '''
162 pass
165class ClipError(_ValueError):
166 '''Clip box or clip region issue.
167 '''
168 def __init__(self, *name_n_corners, **txt_cause):
169 '''New L{ClipError}.
171 @arg name_n_corners: Either just a name (C{str}) or
172 name, number, corners (C{str},
173 C{int}, C{tuple}).
174 @kwarg txt_cause: Optional C{B{txt}=str} explanation
175 of the error and C{B{cause}=None}
176 for exception chaining.
177 '''
178 if len(name_n_corners) == 3:
179 t, n, v = name_n_corners
180 n = _SPACE_(t, _clip_, (_box_ if n == 2 else _region_))
181 name_n_corners = n, v
182 _ValueError.__init__(self, *name_n_corners, **txt_cause)
185class CrossError(_ValueError):
186 '''Error raised for zero or near-zero vectorial cross products,
187 occurring for coincident or colinear points, lines or bearings.
188 '''
189 pass
192class GeodesicError(_ValueError):
193 '''Error raised for lack of convergence or other issues in L{pygeodesy.geodesicx},
194 L{pygeodesy.geodesicw} or L{pygeodesy.karney}.
195 '''
196 pass
199class IntersectionError(_ValueError): # in .ellipsoidalBaseDI, .formy, ...
200 '''Error raised for line or circle intersection issues.
201 '''
202 def __init__(self, *args, **kwds):
203 '''New L{IntersectionError}.
204 '''
205 if args:
206 _ValueError.__init__(self, _SPACE_(*args), **kwds)
207 else:
208 _ValueError.__init__(self, **kwds)
211class LenError(_ValueError): # in .ecef, .fmath, .heights, .iters, .named
212 '''Error raised for mis-matching C{len} values.
213 '''
214 def __init__(self, where, **lens_txt): # txt=None
215 '''New L{LenError}.
217 @arg where: Object with C{.__name__} attribute
218 (C{class}, C{method}, or C{function}).
219 @kwarg lens_txt: Two or more C{name=len(name)} pairs
220 (C{keyword arguments}).
221 '''
222 def _ns_vs_txt_x(cause=None, txt=_invalid_, **kwds):
223 ns, vs = zip(*_MODS.basics.itemsorted(kwds)) # unzip
224 return ns, vs, txt, cause
226 ns, vs, txt, x = _ns_vs_txt_x(**lens_txt)
227 ns = _COMMASPACE_.join(ns)
228 t = _MODS.streprs.Fmt.PAREN(where.__name__, ns)
229 vs = _vs__.join(map(str, vs))
230 t = _SPACE_(t, _len_, vs)
231 _ValueError.__init__(self, t, txt=txt, cause=x)
234class LimitError(_ValueError):
235 '''Error raised for lat- or longitudinal values or deltas exceeding
236 the given B{C{limit}} in functions L{pygeodesy.equirectangular},
237 L{pygeodesy.equirectangular_}, C{nearestOn*} and C{simplify*}
238 or methods with C{limit} or C{options} keyword arguments.
240 @see: Subclass L{UnitError}.
241 '''
242 pass
245class MGRSError(_ValueError):
246 '''Military Grid Reference System (MGRS) parse or other L{Mgrs} issue.
247 '''
248 pass
251class NumPyError(_ValueError):
252 '''Error raised for C{NumPy} issues.
253 '''
254 pass
257class ParseError(_ValueError): # in .dms, .elevations, .utmupsBase
258 '''Error parsing degrees, radians or several other formats.
259 '''
260 pass
263class PointsError(_ValueError): # in .clipy, .frechet, ...
264 '''Error for an insufficient number of points.
265 '''
266 pass
269class RangeError(_ValueError):
270 '''Error raised for lat- or longitude values outside the B{C{clip}},
271 B{C{clipLat}}, B{C{clipLon}} in functions L{pygeodesy.parse3llh},
272 L{pygeodesy.parseDMS}, L{pygeodesy.parseDMS2} and L{pygeodesy.parseRad}
273 or the given B{C{limit}} in functions L{pygeodesy.clipDegrees} and
274 L{pygeodesy.clipRadians}.
276 @see: Function L{pygeodesy.rangerrors}.
277 '''
278 pass
281class RhumbError(_ValueError):
282 '''Error raised for a L{pygeodesy.rhumb.aux_}, L{pygeodesy.rhumb.ekx}
283 or L{pygeodesy.rhumb.solve} issue.
284 '''
285 pass
288class TriangleError(_ValueError): # in .resections, .vector2d
289 '''Error raised for triangle, inter- or resection issues.
290 '''
291 pass
294class SciPyError(PointsError):
295 '''Error raised for C{SciPy} issues.
296 '''
297 pass
300class SciPyWarning(PointsError):
301 '''Error thrown for C{SciPy} warnings.
303 To raise C{SciPy} warnings as L{SciPyWarning} exceptions, Python
304 C{warnings} must be filtered as U{warnings.filterwarnings('error')
305 <https://docs.Python.org/3/library/warnings.html#the-warnings-filter>}
306 I{prior to} C{import scipy} OR by setting env var U{PYTHONWARNINGS
307 <https://docs.Python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>}
308 OR by invoking C{python} with command line option U{-W<https://docs.
309 Python.org/3/using/cmdline.html#cmdoption-w>} set to C{-W error}.
310 '''
311 pass
314class TRFError(_ValueError): # in .ellipsoidalBase, .trf, .units
315 '''Terrestrial Reference Frame (TRF), L{Epoch}, L{RefFrame}
316 or L{RefFrame} conversion issue.
317 '''
318 pass
321class UnitError(LimitError): # in .named, .units
322 '''Default exception for L{units} issues for a value exceeding the
323 C{low} or C{high} limit.
324 '''
325 pass
328class VectorError(_ValueError): # in .nvectorBase, .vector3d, .vector3dBase
329 '''L{Vector3d}, C{Cartesian*} or C{*Nvector} issues.
330 '''
331 pass
334def _an(noun):
335 '''(INTERNAL) Prepend an article to a noun based
336 on the pronounciation of the first letter.
337 '''
338 a = _an_ if noun[:1].lower() in 'aeinoux' else _a_
339 return _SPACE_(a, noun)
342def _and(*words):
343 '''(INTERNAL) Join C{words} with C{", "} and C{" and "}.
344 '''
345 return _and_or(_and_, *words)
348def _and_or(last, *words):
349 '''(INTERNAL) Join C{words} with C{", "} and C{B{last}}.
350 '''
351 t, w = NN, list(words)
352 if w:
353 t = w.pop()
354 if w:
355 w = _COMMASPACE_.join(w)
356 t = _SPACE_(w, last, t)
357 return t
360def crosserrors(raiser=None):
361 '''Report or ignore vectorial cross product errors.
363 @kwarg raiser: Use C{True} to throw or C{False} to ignore
364 L{CrossError} exceptions. Use C{None} to
365 leave the setting unchanged.
367 @return: Previous setting (C{bool}).
369 @see: Property C{Vector3d[Base].crosserrors}.
370 '''
371 V = _MODS.vector3dBase.Vector3dBase
372 t = V._crosserrors # XXX class attr!
373 if raiser in (True, False):
374 V._crosserrors = raiser
375 return t
378def _error_init(Error, inst, args, fmt_name_value='%s (%r)', txt=NN,
379 cause=None, **kwds): # by .lazily
380 '''(INTERNAL) Format an error text and initialize an C{Error} instance.
382 @arg Error: The error super-class (C{Exception}).
383 @arg inst: Sub-class instance to be __init__-ed (C{_Exception}).
384 @arg args: Either just a value or several name, value, ...
385 positional arguments (C{str}, any C{type}), in
386 particular for name conflicts with keyword
387 arguments of C{error_init} or which can't be
388 given as C{name=value} keyword arguments.
389 @kwarg fmt_name_value: Format for (name, value) (C{str}).
390 @kwarg txt: Optional explanation of the error (C{str}).
391 @kwarg cause: Optional, caught error (L{Exception}), for
392 exception chaining (supported in Python 3+).
393 @kwarg kwds: Additional C{B{name}=value} pairs, if any.
394 '''
395 def _fmtuple(pairs):
396 return tuple(fmt_name_value % t for t in pairs)
398 t, n = (), len(args)
399 if n > 2:
400 s = _MODS.basics.isodd(n)
401 t = _fmtuple(zip(args[0::2], args[1::2]))
402 if s: # XXX _xzip(..., strict=s)
403 t += args[-1:]
404 elif n == 2:
405 t = (fmt_name_value % args),
406 elif n: # == 1
407 t = str(args[0]),
409 if kwds:
410 t += _fmtuple(_MODS.basics.itemsorted(kwds))
411 t = _or(*t) if t else _SPACE_(_name_value_, MISSING)
413 if txt is not None:
414 x = str(txt) or (str(cause) if cause else _invalid_)
415 C = _COMMASPACE_ if _COLON_ in t else _COLONSPACE_
416 t = C(t, x)
417# else: # LenError, _xzip, .dms, .heights, .vector2d
418# x = NN # XXX or t?
419 Error.__init__(inst, t)
420# inst.__x_txt__ = x # hold explanation
421 _error_cause(inst, cause=cause if _exception_chaining else None)
422 _error_under(inst)
425def _error_under(inst):
426 '''(INTERNAL) Remove leading underscore from instance' class name.
427 '''
428 n = inst.__class__.__name__
429 if n.startswith(_UNDER_):
430 inst.__class__.__name__ = n.lstrip(_UNDER_)
431 return inst
434def exception_chaining(exc=None):
435 '''Get an error's I{cause} or the exception chaining setting.
437 @kwarg exc: An error instance (C{Exception}) or C{None}.
439 @return: If C{B{exc} is None}, return C{True} if exception
440 chaining is enabled for PyGeodesy errors, C{False}
441 if turned off and C{None} if not available. If
442 B{C{exc}} is not C{None}, return it's error I{cause}
443 or C{None}.
445 @note: To enable exception chaining for C{pygeodesy} errors,
446 set env var C{PYGEODESY_EXCEPTION_CHAINING} to any
447 non-empty value prior to C{import pygeodesy}.
448 '''
449 return _exception_chaining if exc is None else \
450 getattr(exc, '__cause__', None)
453def _incompatible(this):
454 '''(INTERNAL) Format an C{"incompatible with ..."} text.
455 '''
456 return _SPACE_(_incompatible_, _with_, this)
459def _InvalidError(Error=_ValueError, **txt_name_values_cause): # txt=_invalid_, name=value [, ...]
460 '''(INTERNAL) Create an C{Error} instance.
462 @kwarg Error: The error class or sub-class (C{Exception}).
463 @kwarg txt_name_values: One or more C{B{name}=value} pairs
464 and optionally, keyword argument C{B{txt}=str}
465 to override the default C{B{txt}='invalid'} and
466 C{B{cause}=None} for exception chaining.
468 @return: An B{C{Error}} instance.
469 '''
470 return _XError(Error, **txt_name_values_cause)
473def isError(exc):
474 '''Check a (caught) exception.
476 @arg exc: The exception C({Exception}).
478 @return: C{True} if B{C{exc}} is a C{pygeodesy} error,
479 C{False} if B{C{exc}} is a standard Python error
480 of C{None} if neither.
481 '''
482 return True if isinstance(exc, _XErrors) else (
483 False if isinstance(exc, Exception) else None)
486def _IsnotError(*nouns, **name_value_Error_cause): # name=value [, Error=TypeError, cause=None]
487 '''Create a C{TypeError} for an invalid C{name=value} type.
489 @arg nouns: One or more expected class or type names, usually nouns (C{str}).
490 @kwarg name_value_Error_cause: One C{B{name}=value} pair and optionally,
491 keyword argument C{B{Error}=TypeError} to override the default
492 and C{B{cause}=None} for exception chaining.
494 @return: A C{TypeError} or an B{C{Error}} instance.
495 '''
496 def _n_v_E_x(cause=None, Error=TypeError, **name_value):
497 return _xkwds_item2(name_value) + (Error, cause)
499 n, v, E, x = _n_v_E_x(**name_value_Error_cause)
501 n = _MODS.streprs.Fmt.PARENSPACED(n, repr(v))
502 t = _not_(_an(_or(*nouns)) if nouns else _specified_)
503 return _XError(E, n, txt=t, cause=x)
506def limiterrors(raiser=None):
507 '''Get/set the throwing of L{LimitError}s.
509 @kwarg raiser: Choose C{True} to raise or C{False} to
510 ignore L{LimitError} exceptions. Use
511 C{None} to leave the setting unchanged.
513 @return: Previous setting (C{bool}).
514 '''
515 global _limiterrors
516 t = _limiterrors
517 if raiser in (True, False):
518 _limiterrors = raiser
519 return t
522def _or(*words):
523 '''(INTERNAL) Join C{words} with C{", "} and C{" or "}.
524 '''
525 return _and_or(_or_, *words)
528def _parseX(parser, *args, **name_values_Error): # name=value[, ..., Error=ParseError]
529 '''(INTERNAL) Invoke a parser and handle exceptions.
531 @arg parser: The parser (C{callable}).
532 @arg args: Any B{C{parser}} arguments (any C{type}s).
533 @kwarg name_values_Error: Any C{B{name}=value} pairs and
534 optionally, C{B{Error}=ParseError} keyword
535 argument to override the default.
537 @return: Parser result.
539 @raise ParseError: Or the specified C{B{Error}}.
540 '''
541 try:
542 return parser(*args)
543 except Exception as x:
544 E = type(x) if isError(x) else ParseError
545 E, kwds = _xkwds_pop2(name_values_Error, Error=E)
546 raise _XError(E, **_xkwds(kwds, cause=x))
549def rangerrors(raiser=None):
550 '''Get/set the throwing of L{RangeError}s.
552 @kwarg raiser: Choose C{True} to raise or C{False} to ignore
553 L{RangeError} exceptions. Use C{None} to leave
554 the setting unchanged.
556 @return: Previous setting (C{bool}).
557 '''
558 global _rangerrors
559 t = _rangerrors
560 if raiser in (True, False):
561 _rangerrors = raiser
562 return t
565def _SciPyIssue(exc, *extras): # PYCHOK no cover
566 if isinstance(exc, (RuntimeWarning, UserWarning)):
567 E = SciPyWarning
568 else:
569 E = SciPyError # PYCHOK not really
570 t = _SPACE_(str(exc).strip(), *extras)
571 return E(t, cause=exc)
574def _xAssertionError(where, *args, **kwds):
575 '''(INTERNAL) Embellish an C{AssertionError} with/-out exception chaining.
576 '''
577 x, kwds = _xkwds_pop2(kwds, cause=None)
578 w = _MODS.streprs.unstr(where, *args, **kwds)
579 return _AssertionError(w, txt=None, cause=x)
582def _xattr(obj, **name_default): # see .strerprs._xattrs
583 '''(INTERNAL) Get an C{obj}'s attribute by C{name}.
584 '''
585 if len(name_default) == 1:
586 for n, d in name_default.items():
587 return getattr(obj, n, d)
588 raise _xAssertionError(_xattr, obj, **name_default)
591def _xdatum(datum1, datum2, Error=None):
592 '''(INTERNAL) Check for datum, ellipsoid or rhumb mis-match.
593 '''
594 if Error:
595 e1, e2 = datum1.ellipsoid, datum2.ellipsoid
596 if e1 != e2:
597 raise Error(e2.named2, txt=_incompatible(e1.named2))
598 elif datum1 != datum2:
599 t = _SPACE_(_datum_, repr(datum1.name),
600 _not_, repr(datum2.name))
601 raise _AssertionError(t)
604def _xellipsoidal(**name_value): # see _xellipsoidall elel
605 '''(INTERNAL) Check an I{ellipsoidal} item and return its value.
606 '''
607 if len(name_value) == 1:
608 for n, v in name_value.items():
609 try:
610 if v.isEllipsoidal:
611 return v
612 except AttributeError:
613 pass
614 raise _TypeError(n, v, txt=_not_(_ellipsoidal_))
615 raise _xAssertionError(_xellipsoidal, name_value)
618def _xellipsoidall(point): # see _xellipsoidal
619 '''(INTERNAL) Check an ellipsoidal C{point}, return C{True}
620 if geodetic latlon or C{False} if cartesian.
621 '''
622 m = _MODS.ellipsoidalBase
623 ll = isinstance(point, m.LatLonEllipsoidalBase)
624 if not ll:
625 b = _MODS.basics
626 b._xinstanceof(m.CartesianEllipsoidalBase,
627 m.LatLonEllipsoidalBase, point=point)
628 return ll
631def _XError(Error, *args, **kwds):
632 '''(INTERNAL) Format an C{Error} or C{_Error}.
633 '''
634 try: # C{_Error} style
635 return Error(*args, **kwds)
636 except TypeError: # no keyword arguments
637 pass
638 e = _ValueError(*args, **kwds)
639 E = Error(str(e))
640 if _exception_chaining:
641 _error_cause(E, cause=e.__cause__) # PYCHOK OK
642 return E
645def _xError(exc, *args, **kwds):
646 '''(INTERNAL) Embellish a (caught) exception.
648 @arg exc: The exception (usually, C{_Error}).
649 @arg args: Embelishments (C{any}).
650 @kwarg kwds: Embelishments (C{any}).
651 '''
652 return _XError(type(exc), *args, **_xkwds(kwds, cause=exc))
655def _xError2(exc): # in .constants, .fsums, .lazily, .vector2d
656 '''(INTERNAL) Map an exception to 2-tuple (C{_Error} class, error C{txt}).
658 @arg exc: The exception instance (usually, C{Exception}).
659 '''
660 m = __name__ # 'pygeodesy.errors'
661 X = type(exc)
662 n = NN(_UNDER_, _tailof(X.__name__))
663 E = _MODS.getattr(m, n, X) # == _X2Error.get(X, X)
664 if E is X and not isError(exc):
665 E = _NotImplementedError
666 t = repr(exc)
667 else:
668 t = str(exc)
669 return E, t
672_XErrors = _TypeError, _ValueError
673# map certain C{Exception} classes to the C{_Error}
674# _X2Error = {AssertionError: _AssertionError,
675# AttributeError: _AttributeError,
676# ImportError: _ImportError,
677# IndexError: _IndexError,
678# KeyError: _KeyError,
679# NameError: _NameError,
680# NotImplementedError: _NotImplementedError,
681# OverflowError: _OverflowError,
682# TypeError: _TypeError,
683# ValueError: _ValueError,
684# ZeroDivisionError: _ZeroDivisionError}
686try:
687 _ = {}.__or__ # {} | {} # Python 3.9+
689 def _xkwds(kwds, **dflts):
690 '''(INTERNAL) Update C{dflts} with specified C{kwds}.
691 '''
692 return (dflts | kwds) if kwds else dflts
694except AttributeError:
696 def _xkwds(kwds, **dflts): # PYCHOK expected
697 '''(INTERNAL) Update C{dflts} with specified C{kwds}.
698 '''
699 d = dflts
700 if kwds:
701 d = _copy(d)
702 d.update(kwds)
703 return d
706# def _xkwds_bool(inst, **kwds): # no longer in .frechet, .hausdorff, .heights
707# '''(INTERNAL) Set applicable C{bool} properties/attributes.
708# '''
709# for n, v in kwds.items():
710# b = getattr(inst, n, None)
711# if b is None: # invalid bool attr
712# t = _SPACE_(_EQUAL_(n, repr(v)), 'for', inst.__class__.__name__) # XXX .classname
713# raise _AttributeError(t, txt=_not_('applicable'))
714# if v in (False, True) and v != b:
715# setattr(inst, NN(_UNDER_, n), v)
718def _xkwds_get(kwds, **name_default):
719 '''(INTERNAL) Get a C{kwds} value by C{name} or the
720 C{default} if not present.
721 '''
722 if isinstance(kwds, dict) and len(name_default) == 1:
723 for n, d in name_default.items():
724 return kwds.get(n, d)
725 raise _xAssertionError(_xkwds_get, kwds, **name_default)
728def _xkwds_get_(kwds, **names_defaults):
729 '''(INTERNAL) Yield each C{kwds} value or its C{default}
730 in I{case-insensitive, alphabetical} C{name} order.
731 '''
732 if not isinstance(kwds, dict):
733 raise _xAssertionError(_xkwds_get_, kwds)
734 for n, d in _MODS.basics.itemsorted(names_defaults):
735 yield kwds.get(n, d)
738def _xkwds_item2(kwds):
739 '''(INTERNAL) Return the 2-tuple C{item}, keeping the
740 single-item C{kwds} I{unmodified}.
741 '''
742 if isinstance(kwds, dict) and len(kwds) == 1:
743 for item in kwds.items():
744 return item
745 raise _xAssertionError(_xkwds_item2, kwds)
748def _xkwds_not(*args, **kwds):
749 '''(INTERNAL) Return C{kwds} with a value not in C{args}.
750 '''
751 return dict((n, v) for n, v in kwds.items() if v not in args)
754def _xkwds_pop2(kwds, **name_default):
755 '''(INTERNAL) Pop a C{kwds} item by C{name} and return the value and
756 reduced C{kwds} copy, otherwise the C{default} and original C{kwds}.
757 '''
758 if isinstance(kwds, dict) and len(name_default) == 1:
759 for n, d in name_default.items():
760 if n in kwds:
761 kwds = _copy(kwds)
762 d = kwds.pop(n, d)
763 return d, kwds
764 raise _xAssertionError(_xkwds_pop2, kwds, **name_default)
767def _Xorder(_Coeffs, Error, **Xorder): # in .auxLat, .ktm, .rhumb.bases, .rhumb.ekx
768 '''(INTERNAL) Validate C{RAorder} or C{TMorder}.
769 '''
770 X, m = _xkwds_item2(Xorder)
771 if m in _Coeffs and _MODS.basics.isint(m):
772 return m
773 t = sorted(map(str, _Coeffs.keys()))
774 raise Error(X, m, txt=_not_(_or(*t)))
776# **) MIT License
777#
778# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
779#
780# Permission is hereby granted, free of charge, to any person obtaining a
781# copy of this software and associated documentation files (the "Software"),
782# to deal in the Software without restriction, including without limitation
783# the rights to use, copy, modify, merge, publish, distribute, sublicense,
784# and/or sell copies of the Software, and to permit persons to whom the
785# Software is furnished to do so, subject to the following conditions:
786#
787# The above copyright notice and this permission notice shall be included
788# in all copies or substantial portions of the Software.
789#
790# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
791# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
792# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
793# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
794# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
795# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
796# OTHER DEALINGS IN THE SOFTWARE.