Coverage for pygeodesy/errors.py: 93%
291 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-10 14:08 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-10 14:08 -0400
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, issubclassof, itemsorted, _xinstanceof, _zip # _MODS
15# from pygeodesy.ellipsoidalBase import CartesianEllipsoidalBase, LatLonEllipsoidalBase # _MODS
16# from pygeodesy import errors # _MODS, _MODS.getattr
17from pygeodesy.internals import _plural, _tailof
18from pygeodesy.interns import MISSING, NN, _a_, _an_, _and_, _clip_, _COLON_, _COLONSPACE_, \
19 _COMMASPACE_, _datum_, _ellipsoidal_, _incompatible_, _invalid_, \
20 _keyword_, _len_, _not_, _or_, _SPACE_, _specified_, _UNDER_, \
21 _vs_, _with_
22from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _getenv, _PYTHON_X_DEV
23# from pygeodesy.streprs import Fmt, unstr # _MODS
24# from pygeodesy.vector3dBase import Vector3dBase # _MODS
26from copy import copy as _copy
28__all__ = _ALL_LAZY.errors # _ALL_DOCS('_InvalidError', '_IsnotError') _under
29__version__ = '24.06.08'
31_argument_ = 'argument'
32_box_ = 'box'
33_expected_ = 'expected'
34_limiterrors = True # in .formy
35_name_value_ = repr('name=value')
36_rangerrors = True # in .dms
37_region_ = 'region'
38_vs__ = _SPACE_(NN, _vs_, NN)
40try:
41 _exception_chaining = None # not available
42 _ = Exception().__cause__ # Python 3.9+ exception chaining
44 if _PYTHON_X_DEV or _getenv('PYGEODESY_EXCEPTION_CHAINING', NN): # == _std_
45 _exception_chaining = True # turned on, std
46 raise AttributeError() # allow exception chaining
48 _exception_chaining = False # turned off
50 def _error_cause(inst, cause=None):
51 '''(INTERNAL) Set or avoid Python 3+ exception chaining.
53 Setting C{inst.__cause__ = None} is equivalent to syntax
54 C{raise Error(...) from None} to avoid exception chaining.
56 @arg inst: An error instance (I{caught} C{Exception}).
57 @kwarg cause: A previous error instance (I{caught} C{Exception})
58 or C{None} to avoid exception chaining.
60 @see: Alex Martelli, et.al., "Python in a Nutshell", 3rd Ed., page 163,
61 O'Reilly, 2017, U{PEP-3134<https://www.Python.org/dev/peps/pep-3134>},
62 U{here<https://StackOverflow.com/questions/17091520/how-can-i-more-
63 easily-suppress-previous-exceptions-when-i-raise-my-own-exception>}
64 and U{here<https://StackOverflow.com/questions/1350671/
65 inner-exception-with-traceback-in-python>}.
66 '''
67 inst.__cause__ = cause # None, no exception chaining
68 return inst
70except AttributeError: # Python 2+
72 def _error_cause(inst, **unused): # PYCHOK expected
73 return inst # no-op
76class _AssertionError(AssertionError):
77 '''(INTERNAL) Format an C{AssertionError} with/-out exception chaining.
78 '''
79 def __init__(self, *args, **kwds):
80 _error_init(AssertionError, self, args, **kwds)
83class _AttributeError(AttributeError):
84 '''(INTERNAL) Format an C{AttributeError} with/-out exception chaining.
85 '''
86 def __init__(self, *args, **kwds):
87 _error_init(AttributeError, self, args, **kwds)
90class _ImportError(ImportError):
91 '''(INTERNAL) Format an C{ImportError} with/-out exception chaining.
92 '''
93 def __init__(self, *args, **kwds):
94 _error_init(ImportError, self, args, **kwds)
97class _IndexError(IndexError):
98 '''(INTERNAL) Format an C{IndexError} with/-out exception chaining.
99 '''
100 def __init__(self, *args, **kwds):
101 _error_init(IndexError, self, args, **kwds)
104class _KeyError(KeyError):
105 '''(INTERNAL) Format a C{KeyError} with/-out exception chaining.
106 '''
107 def __init__(self, *args, **kwds): # txt=_invalid_
108 _error_init(KeyError, self, args, **kwds)
111class _NameError(NameError):
112 '''(INTERNAL) Format a C{NameError} with/-out exception chaining.
113 '''
114 def __init__(self, *args, **kwds):
115 _error_init(NameError, self, args, **kwds)
118class _NotImplementedError(NotImplementedError):
119 '''(INTERNAL) Format a C{NotImplementedError} with/-out exception chaining.
120 '''
121 def __init__(self, *args, **kwds):
122 _error_init(NotImplementedError, self, args, **kwds)
125class _OverflowError(OverflowError):
126 '''(INTERNAL) Format an C{OverflowError} with/-out exception chaining.
127 '''
128 def __init__(self, *args, **kwds): # txt=_invalid_
129 _error_init(OverflowError, self, args, **kwds)
132class _TypeError(TypeError):
133 '''(INTERNAL) Format a C{TypeError} with/-out exception chaining.
134 '''
135 def __init__(self, *args, **kwds):
136 _error_init(TypeError, self, args, fmt_name_value='type(%s) (%r)', **kwds)
139def _TypesError(name, value, *Types, **kwds):
140 '''(INTERNAL) Format a C{TypeError} with/-out exception chaining.
141 '''
142 # no longer C{class _TypesError} to avoid missing value
143 # argument errors in _XError line ...E = Error(str(e))
144 t = _not_(_an(_or(*(t.__name__ for t in Types))))
145 return _TypeError(name, value, txt=t, **kwds)
148class _UnexpectedError(TypeError): # note, a TypeError!
149 '''(INTERNAL) Format a C{TypeError} I{without exception chaining}.
150 '''
151 def __init__(self, *args, **kwds):
152 n = len(kwds)
153 if args:
154 a = _plural(_argument_, len(args))
155 n = _and(a, _plural(_keyword_, n)) if n else a
156 else:
157 n = _plural(_SPACE_(_keyword_, _argument_), n)
158 u = _MODS.streprs.unstr(_SPACE_(n, NN), *args, **kwds)
159 # _error_init(TypeError, self, (u,), txt_not_=_expected_)
160 TypeError.__init__(self, _SPACE_(u, _not_, _expected_))
163class _ValueError(ValueError):
164 '''(INTERNAL) Format a C{ValueError} with/-out exception chaining.
165 '''
166 def __init__(self, *args, **kwds): # ..., cause=None, txt=_invalid_, ...
167 _error_init(ValueError, self, args, **kwds)
170class _ZeroDivisionError(ZeroDivisionError):
171 '''(INTERNAL) Format a C{ZeroDivisionError} with/-out exception chaining.
172 '''
173 def __init__(self, *args, **kwds):
174 _error_init(ZeroDivisionError, self, args, **kwds)
177class AuxError(_ValueError):
178 '''Error raised for a L{rhumb.aux_}, C{Aux}, C{AuxDLat} or C{AuxLat} issue.
179 '''
180 pass
183class ClipError(_ValueError):
184 '''Clip box or clip region issue.
185 '''
186 def __init__(self, *name_n_corners, **txt_cause):
187 '''New L{ClipError}.
189 @arg name_n_corners: Either just a name (C{str}) or
190 name, number, corners (C{str},
191 C{int}, C{tuple}).
192 @kwarg txt_cause: Optional C{B{txt}=str} explanation
193 of the error and C{B{cause}=None}
194 for exception chaining.
195 '''
196 if len(name_n_corners) == 3:
197 t, n, v = name_n_corners
198 n = _SPACE_(t, _clip_, (_box_ if n == 2 else _region_))
199 name_n_corners = n, v
200 _ValueError.__init__(self, *name_n_corners, **txt_cause)
203class CrossError(_ValueError):
204 '''Error raised for zero or near-zero vectorial cross products,
205 occurring for coincident or colinear points, lines or bearings.
206 '''
207 pass
210class GeodesicError(_ValueError):
211 '''Error raised for lack of convergence or other issues in L{pygeodesy.geodesicx},
212 L{pygeodesy.geodesicw} or L{pygeodesy.karney}.
213 '''
214 pass
217class IntersectionError(_ValueError): # in .ellipsoidalBaseDI, .formy, ...
218 '''Error raised for line or circle intersection issues.
219 '''
220 def __init__(self, *args, **kwds):
221 '''New L{IntersectionError}.
222 '''
223 if args:
224 _ValueError.__init__(self, _SPACE_(*args), **kwds)
225 else:
226 _ValueError.__init__(self, **kwds)
229class LenError(_ValueError): # in .ecef, .fmath, .heights, .iters, .named
230 '''Error raised for mis-matching C{len} values.
231 '''
232 def __init__(self, where, **lens_txt): # txt=None
233 '''New L{LenError}.
235 @arg where: Object with C{.__name__} attribute
236 (C{class}, C{method}, or C{function}).
237 @kwarg lens_txt: Two or more C{name=len(name)} pairs
238 (C{keyword arguments}).
239 '''
240 def _ns_vs_txt_x(cause=None, txt=_invalid_, **kwds):
241 ns, vs = zip(*_MODS.basics.itemsorted(kwds)) # unzip
242 return ns, vs, txt, cause
244 ns, vs, txt, x = _ns_vs_txt_x(**lens_txt)
245 ns = _COMMASPACE_.join(ns)
246 t = _MODS.streprs.Fmt.PAREN(where.__name__, ns)
247 vs = _vs__.join(map(str, vs))
248 t = _SPACE_(t, _len_, vs)
249 _ValueError.__init__(self, t, txt=txt, cause=x)
252class LimitError(_ValueError):
253 '''Error raised for lat- or longitudinal values or deltas exceeding
254 the given B{C{limit}} in functions L{pygeodesy.equirectangular},
255 L{pygeodesy.equirectangular4}, C{nearestOn*} and C{simplify*}
256 or methods with C{limit} or C{options} keyword arguments.
258 @see: Subclass L{UnitError}.
259 '''
260 pass
263class MGRSError(_ValueError):
264 '''Military Grid Reference System (MGRS) parse or other L{Mgrs} issue.
265 '''
266 pass
269class NumPyError(_ValueError):
270 '''Error raised for C{NumPy} issues.
271 '''
272 pass
275class ParseError(_ValueError): # in .dms, .elevations, .utmupsBase
276 '''Error parsing degrees, radians or several other formats.
277 '''
278 pass
281class PointsError(_ValueError): # in .clipy, .frechet, ...
282 '''Error for an insufficient number of points.
283 '''
284 pass
287class RangeError(_ValueError):
288 '''Error raised for lat- or longitude values outside the B{C{clip}},
289 B{C{clipLat}}, B{C{clipLon}} in functions L{pygeodesy.parse3llh},
290 L{pygeodesy.parseDMS}, L{pygeodesy.parseDMS2} and L{pygeodesy.parseRad}
291 or the given B{C{limit}} in functions L{pygeodesy.clipDegrees} and
292 L{pygeodesy.clipRadians}.
294 @see: Function L{pygeodesy.rangerrors}.
295 '''
296 pass
299class RhumbError(_ValueError):
300 '''Error raised for a L{pygeodesy.rhumb.aux_}, L{pygeodesy.rhumb.ekx}
301 or L{pygeodesy.rhumb.solve} issue.
302 '''
303 pass
306class TriangleError(_ValueError): # in .resections, .vector2d
307 '''Error raised for triangle, inter- or resection issues.
308 '''
309 pass
312class SciPyError(PointsError):
313 '''Error raised for C{SciPy} issues.
314 '''
315 pass
318class SciPyWarning(PointsError):
319 '''Error thrown for C{SciPy} warnings.
321 To raise C{SciPy} warnings as L{SciPyWarning} exceptions, Python
322 C{warnings} must be filtered as U{warnings.filterwarnings('error')
323 <https://docs.Python.org/3/library/warnings.html#the-warnings-filter>}
324 I{prior to} C{import scipy} OR by setting env var U{PYTHONWARNINGS
325 <https://docs.Python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>}
326 OR by invoking C{python} with command line option U{-W<https://docs.
327 Python.org/3/using/cmdline.html#cmdoption-w>} set to C{-W error}.
328 '''
329 pass
332class TRFError(_ValueError): # in .ellipsoidalBase, .trf, .units
333 '''Terrestrial Reference Frame (TRF), L{Epoch}, L{RefFrame}
334 or L{RefFrame} conversion issue.
335 '''
336 pass
339class UnitError(LimitError): # in .named, .units
340 '''Default exception for L{units} issues for a value exceeding the
341 C{low} or C{high} limit.
342 '''
343 pass
346class VectorError(_ValueError): # in .nvectorBase, .vector3d, .vector3dBase
347 '''L{Vector3d}, C{Cartesian*} or C{*Nvector} issues.
348 '''
349 pass
352def _an(noun):
353 '''(INTERNAL) Prepend an article to a noun based
354 on the pronounciation of the first letter.
355 '''
356 a = _an_ if noun[:1].lower() in 'aeinoux' else _a_
357 return _SPACE_(a, noun)
360def _and(*words):
361 '''(INTERNAL) Join C{words} with C{", "} and C{" and "}.
362 '''
363 return _and_or(_and_, *words)
366def _and_or(last, *words):
367 '''(INTERNAL) Join C{words} with C{", "} and C{B{last}}.
368 '''
369 t, w = NN, list(words)
370 if w:
371 t = w.pop()
372 if w:
373 w = _COMMASPACE_.join(w)
374 t = _SPACE_(w, last, t)
375 return t
378def crosserrors(raiser=None):
379 '''Report or ignore vectorial cross product errors.
381 @kwarg raiser: Use C{True} to throw or C{False} to ignore
382 L{CrossError} exceptions. Use C{None} to
383 leave the setting unchanged.
385 @return: Previous setting (C{bool}).
387 @see: Property C{Vector3d[Base].crosserrors}.
388 '''
389 V = _MODS.vector3dBase.Vector3dBase
390 t = V._crosserrors # XXX class attr!
391 if raiser in (True, False):
392 V._crosserrors = raiser
393 return t
396def _error_init(Error, inst, args, fmt_name_value='%s (%r)', txt_not_=NN,
397 txt__=None, txt=NN, cause=None, **kwds):
398 '''(INTERNAL) Format an error text and initialize an C{Error} instance.
400 @arg Error: The error super-class (C{Exception}).
401 @arg inst: Sub-class instance to be __init__-ed (C{_Exception}).
402 @arg args: Either just a value or several name, value, ...
403 positional arguments (C{str}, any C{type}), in
404 particular for name conflicts with keyword
405 arguments of C{error_init} or which can't be
406 given as C{name=value} keyword arguments.
407 @kwarg fmt_name_value: Format for (name, value) (C{str}).
408 @kwarg txt: Optional explanation of the error (C{str}).
409 @kwarg txt__: Alternate C{B{txt}=B{txt__}.__name__}.
410 @kwarg txt_not_: Negative explanation C{B{txt}=_not_(B{txt_not_})}.
411 @kwarg cause: Optional, caught error (L{Exception}), for
412 exception chaining (supported in Python 3+).
413 @kwarg kwds: Additional C{B{name}=value} pairs, if any.
414 '''
415 def _fmtuple(pairs):
416 return tuple(fmt_name_value % t for t in pairs)
418 t, n = (), len(args)
419 if n > 2:
420 t = _fmtuple(zip(args[0::2], args[1::2]))
421 s = _MODS.basics.isodd(n)
422 if s: # XXX _xzip(..., strict=s)
423 t += args[-1:]
424 elif n == 2:
425 t = (fmt_name_value % args),
426 elif n: # == 1
427 t = str(args[0]),
428 if kwds:
429 t += _fmtuple(_MODS.basics.itemsorted(kwds))
430 t = _or(*t) if t else _SPACE_(_name_value_, MISSING)
432 x = _not_(txt_not_) if txt_not_ else (txt if txt__ is None
433 else txt__.__name__)
434 if x is not None:
435 x = str(x) or (str(cause) if cause else _invalid_)
436 C = _COMMASPACE_ if _COLON_ in t else _COLONSPACE_
437 t = C(t, x)
438# else: # LenError, _xzip, .dms, .heights, .vector2d
439# x = NN # XXX or t?
440 Error.__init__(inst, t)
441# inst.__x_txt__ = x # hold explanation
442 _error_cause(inst, cause=cause if _exception_chaining else None)
443 _error_under(inst)
446def _error_under(inst):
447 '''(INTERNAL) Remove leading underscore from instance' class name.
448 '''
449 n = inst.__class__.__name__ # _tailof?
450 if n.startswith(_UNDER_):
451 inst.__class__.__name__ = n.lstrip(_UNDER_)
452 return inst
455def exception_chaining(exc=None):
456 '''Get an error's I{cause} or the exception chaining setting.
458 @kwarg exc: An error instance (C{Exception}) or C{None}.
460 @return: If C{B{exc} is None}, return C{True} if exception
461 chaining is enabled for PyGeodesy errors, C{False}
462 if turned off and C{None} if not available. If
463 B{C{exc}} is not C{None}, return it's error I{cause}
464 or C{None}.
466 @note: To enable exception chaining for C{pygeodesy} errors,
467 set env var C{PYGEODESY_EXCEPTION_CHAINING} to any
468 non-empty value prior to C{import pygeodesy}.
469 '''
470 return _exception_chaining if exc is None else \
471 getattr(exc, '__cause__', None)
474def _incompatible(this):
475 '''(INTERNAL) Format an C{"incompatible with ..."} text.
476 '''
477 return _SPACE_(_incompatible_, _with_, this)
480def _InvalidError(Error=_ValueError, **txt_name_values_cause): # txt=_invalid_, name=value [, ...]
481 '''(INTERNAL) Create an C{Error} instance.
483 @kwarg Error: The error class or sub-class (C{Exception}).
484 @kwarg txt_name_values: One or more C{B{name}=value} pairs
485 and optionally, keyword argument C{B{txt}=str}
486 to override the default C{B{txt}='invalid'} and
487 C{B{cause}=None} for exception chaining.
489 @return: An B{C{Error}} instance.
490 '''
491 return _XError(Error, **txt_name_values_cause)
494def isError(exc):
495 '''Check a (caught) exception.
497 @arg exc: The exception C({Exception}).
499 @return: C{True} if B{C{exc}} is a C{pygeodesy} error,
500 C{False} if B{C{exc}} is a standard Python error
501 of C{None} if neither.
502 '''
503 def _X(exc):
504 X = type(exc)
505 m = X.__module__
506 return _MODS.basics.issubclassof(X, *_XErrors) or \
507 ((m is __name__ or m == __name__) and
508 _tailof(X.__name__).startswith(_UNDER_))
510 return True if isinstance(exc, _XErrors) else (
511 _X(exc) if isinstance(exc, Exception) else None)
514def _IsnotError(*nouns, **name_value_Error_cause): # name=value [, Error=TypeError, cause=None]
515 '''Create a C{TypeError} for an invalid C{name=value} type.
517 @arg nouns: One or more expected class or type names, usually nouns (C{str}).
518 @kwarg name_value_Error_cause: One C{B{name}=value} pair and optionally,
519 keyword argument C{B{Error}=TypeError} to override the default
520 and C{B{cause}=None} for exception chaining.
522 @return: A C{TypeError} or an B{C{Error}} instance.
523 '''
524 def _n_v_E_x(cause=None, Error=TypeError, **name_value):
525 return _xkwds_item2(name_value) + (Error, cause)
527 n, v, E, x = _n_v_E_x(**name_value_Error_cause)
529 n = _MODS.streprs.Fmt.PARENSPACED(n, repr(v))
530 t = _not_(_an(_or(*nouns)) if nouns else _specified_)
531 return _XError(E, n, txt=t, cause=x)
534def limiterrors(raiser=None):
535 '''Get/set the throwing of L{LimitError}s.
537 @kwarg raiser: Choose C{True} to raise or C{False} to
538 ignore L{LimitError} exceptions. Use
539 C{None} to leave the setting unchanged.
541 @return: Previous setting (C{bool}).
542 '''
543 global _limiterrors
544 t = _limiterrors
545 if raiser in (True, False):
546 _limiterrors = raiser
547 return t
550def _or(*words):
551 '''(INTERNAL) Join C{words} with C{", "} and C{" or "}.
552 '''
553 return _and_or(_or_, *words)
556def _parseX(parser, *args, **name_values_Error): # name=value[, ..., Error=ParseError]
557 '''(INTERNAL) Invoke a parser and handle exceptions.
559 @arg parser: The parser (C{callable}).
560 @arg args: Any B{C{parser}} arguments (any C{type}s).
561 @kwarg name_values_Error: Any C{B{name}=value} pairs and
562 optionally, C{B{Error}=ParseError} keyword
563 argument to override the default.
565 @return: Parser result.
567 @raise ParseError: Or the specified C{B{Error}}.
568 '''
569 try:
570 return parser(*args)
571 except Exception as x:
572 E = type(x) if isError(x) else ParseError
573 E, kwds = _xkwds_pop2(name_values_Error, Error=E)
574 raise _XError(E, **_xkwds(kwds, cause=x))
577def rangerrors(raiser=None):
578 '''Get/set the throwing of L{RangeError}s.
580 @kwarg raiser: Choose C{True} to raise or C{False} to ignore
581 L{RangeError} exceptions. Use C{None} to leave
582 the setting unchanged.
584 @return: Previous setting (C{bool}).
585 '''
586 global _rangerrors
587 t = _rangerrors
588 if raiser in (True, False):
589 _rangerrors = raiser
590 return t
593def _SciPyIssue(exc, *extras): # PYCHOK no cover
594 if isinstance(exc, (RuntimeWarning, UserWarning)):
595 E = SciPyWarning
596 else:
597 E = SciPyError # PYCHOK not really
598 t = _SPACE_(str(exc).strip(), *extras)
599 return E(t, txt=None, cause=exc)
602def _xAssertionError(where, *args, **kwds):
603 '''(INTERNAL) Embellish an C{AssertionError} with/-out exception chaining.
604 '''
605 x, kwds = _xkwds_pop2(kwds, cause=None)
606 w = _MODS.streprs.unstr(where, *args, **kwds)
607 return _AssertionError(w, txt=None, cause=x)
610def _xattr(obj, **name_default): # see .strerprs._xattrs
611 '''(INTERNAL) Get an C{obj}'s attribute by C{name}.
612 '''
613 if len(name_default) == 1:
614 for n, d in name_default.items():
615 return getattr(obj, n, d)
616 raise _xAssertionError(_xattr, obj, **name_default)
619def _xcallable(**names_callables):
620 '''(INTERNAL) Check one or more C{callable}s.
621 '''
622 for n, c in names_callables.items():
623 if not callable(c):
624 raise _TypeError(n, c, txt_not_=callable.__name__) # txt__
627def _xdatum(datum1, datum2, Error=None):
628 '''(INTERNAL) Check for datum, ellipsoid or rhumb mis-match.
629 '''
630 if Error:
631 e1, e2 = datum1.ellipsoid, datum2.ellipsoid
632 if e1 != e2:
633 raise Error(e2.named2, txt=_incompatible(e1.named2))
634 elif datum1 != datum2:
635 t = _SPACE_(_datum_, repr(datum1.name),
636 _not_, repr(datum2.name))
637 raise _AssertionError(t)
640def _xellipsoidal(**name_value): # see _xellipsoidall elel
641 '''(INTERNAL) Check an I{ellipsoidal} item and return its value.
642 '''
643 if len(name_value) == 1:
644 for n, v in name_value.items():
645 try:
646 if v.isEllipsoidal:
647 return v
648 except AttributeError:
649 pass
650 raise _TypeError(n, v, txt_not_=_ellipsoidal_)
651 raise _xAssertionError(_xellipsoidal, name_value)
654def _xellipsoidall(point): # see _xellipsoidal
655 '''(INTERNAL) Check an ellipsoidal C{point}, return C{True}
656 if geodetic latlon or C{False} if cartesian.
657 '''
658 m = _MODS.ellipsoidalBase
659 ll = isinstance(point, m.LatLonEllipsoidalBase)
660 if not ll:
661 b = _MODS.basics
662 b._xinstanceof(m.CartesianEllipsoidalBase,
663 m.LatLonEllipsoidalBase, point=point)
664 return ll
667def _XError(Error, *args, **kwds):
668 '''(INTERNAL) Format an C{Error} or C{_Error}.
669 '''
670 try: # C{_Error} style
671 return Error(*args, **kwds)
672 except TypeError: # no keyword arguments
673 pass
674 e = _ValueError(*args, **kwds)
675 E = Error(str(e))
676 if _exception_chaining:
677 _error_cause(E, cause=e.__cause__) # PYCHOK OK
678 return E
681def _xError(exc, *args, **kwds):
682 '''(INTERNAL) Embellish a (caught) exception.
684 @arg exc: The exception (usually, C{_Error}).
685 @arg args: Embelishments (C{any}).
686 @kwarg kwds: Embelishments (C{any}).
687 '''
688 return _XError(type(exc), *args, **_xkwds(kwds, cause=exc))
691def _xError2(exc): # in .constants, .fsums, .lazily, .vector2d
692 '''(INTERNAL) Map an exception to 2-tuple (C{_Error} class, error C{txt}).
694 @arg exc: The exception instance (usually, C{Exception}).
695 '''
696 x = isError(exc)
697 if x:
698 E = type(exc)
699 elif x is None:
700 E = _AssertionError
701 else: # get _Error from Error
702 n = NN(_UNDER_, _tailof(type(exc).__name__))
703 E = _MODS.getattr(__name__, n, _NotImplementedError)
704 x = E is not _NotImplementedError
705 return E, (str(exc) if x else repr(exc))
708_XErrors = (_AssertionError, _AttributeError, # some isError's
709 _TypeError, _ValueError, _ZeroDivisionError)
710# map certain C{Exception} classes to the C{_Error}
711# _X2Error = {AssertionError: _AssertionError, ...
712# ZeroDivisionError: _ZeroDivisionError}
714try:
715 _ = {}.__or__ # {} | {} # Python 3.9+
717 def _xkwds(kwds, **dflts):
718 '''(INTERNAL) Update C{dflts} with specified C{kwds}.
719 '''
720 return (dflts | kwds) if kwds else dflts
722except AttributeError:
724 def _xkwds(kwds, **dflts): # PYCHOK expected
725 '''(INTERNAL) Update C{dflts} with specified C{kwds}.
726 '''
727 d = dflts
728 if kwds:
729 d = _copy(d)
730 d.update(kwds)
731 return d
734# def _xkwds_bool(inst, **kwds): # no longer in .frechet, .hausdorff, .heights
735# '''(INTERNAL) Set applicable C{bool} properties/attributes.
736# '''
737# for n, v in kwds.items():
738# b = getattr(inst, n, None)
739# if b is None: # invalid bool attr
740# t = _SPACE_(_EQUAL_(n, repr(v)), 'for', inst.__class__.__name__) # XXX .classname
741# raise _AttributeError(t, txt_not_='applicable')
742# if v in (False, True) and v != b:
743# setattr(inst, NN(_UNDER_, n), v)
746def _xkwds_get(kwds, **name_default):
747 '''(INTERNAL) Get a C{kwds} value by C{name} or the
748 C{default} if not present.
749 '''
750 if isinstance(kwds, dict) and len(name_default) == 1:
751 for n, v in name_default.items():
752 return kwds.get(n, v)
753 raise _xAssertionError(_xkwds_get, kwds, **name_default)
756def _xkwds_get_(kwds, **names_defaults):
757 '''(INTERNAL) Yield each C{kwds} value or its C{default}
758 in I{case-insensitive, alphabetical} C{name} order.
759 '''
760 if not isinstance(kwds, dict):
761 raise _xAssertionError(_xkwds_get_, kwds)
762 for n, v in _MODS.basics.itemsorted(names_defaults):
763 yield kwds.get(n, v)
766def _xkwds_get1(kwds, **name_default):
767 '''(INTERNAL) Get one C{kwds} value by C{name} or the
768 C{default} if not present.
769 '''
770 v, kwds = _xkwds_pop2(kwds, **name_default)
771 if kwds:
772 raise _UnexpectedError(**kwds)
773 return v
776def _xkwds_item2(kwds):
777 '''(INTERNAL) Return the 2-tuple C{item}, keeping the
778 single-item C{kwds} I{unmodified}.
779 '''
780 if isinstance(kwds, dict) and len(kwds) == 1:
781 for item in kwds.items():
782 return item
783 raise _xAssertionError(_xkwds_item2, kwds)
786def _xkwds_not(*args, **kwds):
787 '''(INTERNAL) Return C{kwds} with a value not in C{args}.
788 '''
789 return dict((n, v) for n, v in kwds.items() if v not in args)
792def _xkwds_pop2(kwds, **name_default):
793 '''(INTERNAL) Pop a C{kwds} item by C{name} and return the value and
794 reduced C{kwds} copy, otherwise the C{default} and original C{kwds}.
795 '''
796 if isinstance(kwds, dict) and len(name_default) == 1:
797 for n, v in name_default.items():
798 if n in kwds:
799 kwds = _copy(kwds)
800 v = kwds.pop(n, v)
801 return v, kwds
802 raise _xAssertionError(_xkwds_pop2, kwds, **name_default)
805def _Xorder(_Coeffs, Error, **Xorder): # in .auxLat, .ktm, .rhumb.bases, .rhumb.ekx
806 '''(INTERNAL) Validate C{RAorder} or C{TMorder}.
807 '''
808 X, m = _xkwds_item2(Xorder)
809 if m in _Coeffs and _MODS.basics.isint(m):
810 return m
811 t = sorted(map(str, _Coeffs.keys()))
812 raise Error(X, m, txt_not_=_or(*t))
814# **) MIT License
815#
816# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
817#
818# Permission is hereby granted, free of charge, to any person obtaining a
819# copy of this software and associated documentation files (the "Software"),
820# to deal in the Software without restriction, including without limitation
821# the rights to use, copy, modify, merge, publish, distribute, sublicense,
822# and/or sell copies of the Software, and to permit persons to whom the
823# Software is furnished to do so, subject to the following conditions:
824#
825# The above copyright notice and this permission notice shall be included
826# in all copies or substantial portions of the Software.
827#
828# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
829# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
830# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
831# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
832# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
833# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
834# OTHER DEALINGS IN THE SOFTWARE.