Coverage for pygeodesy/basics.py: 95%

250 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-02-09 17:20 -0500

1 

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

3 

4u'''Some, basic definitions, functions and dependencies. 

5 

6Use env variable C{PYGEODESY_XPACKAGES} to avoid import of dependencies 

7C{geographiclib}, C{numpy} and/or C{scipy}. Set C{PYGEODESY_XPACKAGES} 

8to a comma-separated list of package names to be excluded from import. 

9''' 

10# make sure int/int division yields float quotient 

11from __future__ import division 

12division = 1 / 2 # .albers, .azimuthal, .constants, etc., .utily 

13if not division: 

14 raise ImportError('%s 1/2 == %s' % ('division', division)) 

15del division 

16 

17from pygeodesy.errors import _AssertionError, _AttributeError, _ImportError, \ 

18 _TypeError, _TypesError, _ValueError, _xkwds_get, \ 

19 _xAssertionError 

20from pygeodesy.interns import MISSING, NN, _1_, _by_, _COMMA_, _DOT_, _DEPRECATED_, \ 

21 _ELLIPSIS4_, _enquote, _EQUAL_, _in_, _invalid_, _N_A_, \ 

22 _not_scalar_, _SPACE_, _UNDER_, _version_ # _utf_8_ 

23from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _FOR_DOCS, \ 

24 _getenv, LazyImportError, _sys, _sys_version_info2 

25 

26from copy import copy as _copy, deepcopy as _deepcopy 

27from math import copysign as _copysign 

28import inspect as _inspect 

29 

30__all__ = _ALL_LAZY.basics 

31__version__ = '24.02.06' 

32 

33_0_0 = 0.0 # in .constants 

34_below_ = 'below' 

35_can_t_ = "can't" 

36_list_tuple_types = (list, tuple) 

37_list_tuple_set_types = (list, tuple, set) 

38_odd_ = 'odd' 

39_required_ = 'required' 

40_PYGEODESY_XPACKAGES_ = 'PYGEODESY_XPACKAGES' 

41 

42try: # Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 395, 2022 p. 577+ 

43 from numbers import Integral as _Ints, Real as _Scalars # .units 

44except ImportError: 

45 try: 

46 _Ints = int, long # int objects (C{tuple}) 

47 except NameError: # Python 3+ 

48 _Ints = int, # int objects (C{tuple}) 

49 _Scalars = _Ints + (float,) 

50 

51try: 

52 try: # use C{from collections.abc import ...} in Python 3.9+ 

53 from collections.abc import Sequence as _Sequence # in .points 

54 except ImportError: # no .abc in Python 3.8- and 2.7- 

55 from collections import Sequence as _Sequence # in .points 

56 if isinstance([], _Sequence) and isinstance((), _Sequence): 

57 # and isinstance(range(1), _Sequence): 

58 _Seqs = _Sequence 

59 else: 

60 raise ImportError # _AssertionError 

61except ImportError: 

62 _Sequence = tuple # immutable for .points._Basequence 

63 _Seqs = list, _Sequence # , range for function len2 below 

64 

65 

66def _passarg(arg): # in .auxilats.auxLat 

67 '''(INTERNAL) Helper, no-op. 

68 ''' 

69 return arg 

70 

71 

72def _passargs(*args): # in .utily 

73 '''(INTERNAL) Helper, no-op. 

74 ''' 

75 return args 

76 

77 

78try: 

79 _Bytes = unicode, bytearray # PYCHOK expected 

80 _Strs = basestring, str # XXX , bytes 

81 str2ub = ub2str = _passarg # avoids UnicodeDecodeError 

82 

83 def _Xstr(exc): # PYCHOK no cover 

84 '''I{Invoke only with caught ImportError} B{C{exc}}. 

85 

86 C{... "can't import name _distributor_init" ...} 

87 

88 only for C{numpy}, C{scipy} import errors occurring 

89 on arm64 Apple Silicon running macOS' Python 2.7.16? 

90 ''' 

91 t = str(exc) 

92 if '_distributor_init' in t: 

93 from sys import exc_info 

94 from traceback import extract_tb 

95 tb = exc_info()[2] # 3-tuple (type, value, traceback) 

96 t4 = extract_tb(tb, 1)[0] # 4-tuple (file, line, name, 'import ...') 

97 t = _SPACE_(_can_t_, t4[3] or _N_A_) 

98 del tb, t4 

99 return t 

100 

101except NameError: # Python 3+ 

102 from pygeodesy.interns import _utf_8_ 

103 

104 _Bytes = bytes, bytearray 

105 _Strs = str, # tuple 

106 _Xstr = str 

107 

108 def str2ub(sb): 

109 '''Convert C{str} to C{unicode bytes}. 

110 ''' 

111 if isinstance(sb, _Strs): 

112 sb = sb.encode(_utf_8_) 

113 return sb 

114 

115 def ub2str(ub): 

116 '''Convert C{unicode bytes} to C{str}. 

117 ''' 

118 if isinstance(ub, _Bytes): 

119 ub = str(ub.decode(_utf_8_)) 

120 return ub 

121 

122 

123def clips(sb, limit=50, white=NN): 

124 '''Clip a string to the given length limit. 

125 

126 @arg sb: String (C{str} or C{bytes}). 

127 @kwarg limit: Length limit (C{int}). 

128 @kwarg white: Optionally, replace all whitespace (C{str}). 

129 

130 @return: The clipped or unclipped B{C{sb}}. 

131 ''' 

132 T = type(sb) 

133 if len(sb) > limit > 8: 

134 h = limit // 2 

135 sb = T(_ELLIPSIS4_).join((sb[:h], sb[-h:])) 

136 if white: # replace whitespace 

137 sb = T(white).join(sb.split()) 

138 return sb 

139 

140 

141def copysign0(x, y): 

142 '''Like C{math.copysign(x, y)} except C{zero}, I{unsigned}. 

143 

144 @return: C{math.copysign(B{x}, B{y})} if B{C{x}} else 

145 C{type(B{x})(0)}. 

146 ''' 

147 return _copysign(x, (y if y else 0)) if x else copytype(0, x) 

148 

149 

150def copytype(x, y): 

151 '''Return the value of B{x} as C{type} of C{y}. 

152 

153 @return: C{type(B{y})(B{x})}. 

154 ''' 

155 return type(y)(x if x else _0_0) 

156 

157 

158def halfs2(str2): 

159 '''Split a string in 2 halfs. 

160 

161 @arg str2: String to split (C{str}). 

162 

163 @return: 2-Tuple C{(_1st, _2nd)} half (C{str}). 

164 

165 @raise ValueError: Zero or odd C{len(B{str2})}. 

166 ''' 

167 h, r = divmod(len(str2), 2) 

168 if r or not h: 

169 raise _ValueError(str2=str2, txt=_odd_) 

170 return str2[:h], str2[h:] 

171 

172 

173def int1s(x): 

174 '''Count the number of 1-bits in an C{int}, I{unsigned}. 

175 

176 @note: C{int1s(-B{x}) == int1s(abs(B{x}))}. 

177 ''' 

178 try: 

179 return x.bit_count() # Python 3.10+ 

180 except AttributeError: 

181 # bin(-x) = '-' + bin(abs(x)) 

182 return bin(x).count(_1_) 

183 

184 

185def isbool(obj): 

186 '''Check whether an object is C{bool}ean. 

187 

188 @arg obj: The object (any C{type}). 

189 

190 @return: C{True} if B{C{obj}} is C{bool}ean, 

191 C{False} otherwise. 

192 ''' 

193 return isinstance(obj, bool) # and (obj is False 

194# or obj is True) 

195 

196if isbool(1) or isbool(0): # PYCHOK assert 

197 raise _AssertionError(isbool=1) 

198 

199if _FOR_DOCS: # XXX avoid epidoc Python 2.7 error 

200 

201 def isclass(obj): 

202 '''Return C{True} if B{C{obj}} is a C{class} or C{type}. 

203 

204 @see: Python's C{inspect.isclass}. 

205 ''' 

206 return _inspect.isclass(obj) 

207else: 

208 isclass = _inspect.isclass 

209 

210 

211def isCartesian(obj, ellipsoidal=None): 

212 '''Is B{C{obj}} some C{Cartesian}? 

213 

214 @arg obj: The object (any C{type}). 

215 @kwarg ellipsoidal: If C{None}, return the type of any C{Cartesian}, 

216 if C{True}, only an ellipsoidal C{Cartesian type} 

217 or if C{False}, only a spherical C{Cartesian type}. 

218 

219 @return: C{type(B{obj}} if B{C{obj}} is a C{Cartesian} instance of 

220 the required type, C{False} if a C{Cartesian} of an other 

221 type or C{None} otherwise. 

222 ''' 

223 if ellipsoidal is not None: 

224 try: 

225 return obj.ellipsoidalCartesian if ellipsoidal else obj.sphericalCartesian 

226 except AttributeError: 

227 return None 

228 return isinstanceof(obj, _MODS.cartesianBase.CartesianBase) 

229 

230 

231def iscomplex(obj): 

232 '''Check whether an object is a C{complex} or complex C{str}. 

233 

234 @arg obj: The object (any C{type}). 

235 

236 @return: C{True} if B{C{obj}} is C{complex}, otherwise 

237 C{False}. 

238 ''' 

239 try: # hasattr('conjugate'), hasattr('real') and hasattr('imag') 

240 return isinstance(obj, complex) or (isstr(obj) 

241 and isinstance(complex(obj), complex)) # numbers.Complex? 

242 except (TypeError, ValueError): 

243 return False 

244 

245 

246def isDEPRECATED(obj): 

247 '''Return C{True} if C{B{obj}} is a C{DEPRECATED} class, method 

248 or function, C{False} if not or C{None} if undetermined. 

249 ''' 

250 try: # XXX inspect.getdoc(obj) 

251 return bool(obj.__doc__.lstrip().startswith(_DEPRECATED_)) 

252 except AttributeError: 

253 return None 

254 

255 

256def isfloat(obj): 

257 '''Check whether an object is a C{float} or float C{str}. 

258 

259 @arg obj: The object (any C{type}). 

260 

261 @return: C{True} if B{C{obj}} is a C{float}, otherwise 

262 C{False}. 

263 ''' 

264 try: 

265 return isinstance( obj, float) or (isstr(obj) 

266 and isinstance(float(obj), float)) 

267 except (TypeError, ValueError): 

268 return False 

269 

270 

271try: 

272 isidentifier = str.isidentifier # Python 3, must be str 

273except AttributeError: # Python 2- 

274 

275 def isidentifier(obj): 

276 '''Return C{True} if B{C{obj}} is a Python identifier. 

277 ''' 

278 return bool(obj and isstr(obj) 

279 and obj.replace(_UNDER_, NN).isalnum() 

280 and not obj[:1].isdigit()) 

281 

282 

283def isinstanceof(obj, *classes): 

284 '''Is B{C{ob}} an intance of one of the C{classes}? 

285 

286 @arg obj: The instance (any C{type}). 

287 @arg classes: One or more classes (C{class}). 

288 

289 @return: C{type(B{obj}} if B{C{obj}} is an instance 

290 of the B{C{classes}}, C{None} otherwise. 

291 ''' 

292 return type(obj) if isinstance(obj, classes) else None 

293 

294 

295def isint(obj, both=False): 

296 '''Check for C{int} type or an integer C{float} value. 

297 

298 @arg obj: The object (any C{type}). 

299 @kwarg both: If C{true}, check C{float} and L{Fsum} 

300 type and value (C{bool}). 

301 

302 @return: C{True} if B{C{obj}} is C{int} or I{integer} 

303 C{float} or L{Fsum}, C{False} otherwise. 

304 

305 @note: Both C{isint(True)} and C{isint(False)} return 

306 C{False} (and no longer C{True}). 

307 ''' 

308 if isinstance(obj, _Ints) and not isbool(obj): 

309 return True 

310 elif both: # and isinstance(obj, (float, Fsum)) 

311 try: # NOT , _Scalars) to include Fsum! 

312 return obj.is_integer() 

313 except AttributeError: 

314 pass # XXX float(int(obj)) == obj? 

315 return False 

316 

317 

318try: 

319 from keyword import iskeyword # Python 2.7+ 

320except ImportError: 

321 

322 def iskeyword(unused): 

323 '''Not Implemented, C{False} always. 

324 ''' 

325 return False 

326 

327 

328def isLatLon(obj, ellipsoidal=None): 

329 '''Is B{C{obj}} some C{LatLon}? 

330 

331 @arg obj: The object (any C{type}). 

332 @kwarg ellipsoidal: If C{None}, return the type of any C{LatLon}, 

333 if C{True}, only an ellipsoidal C{LatLon type} 

334 or if C{False}, only a spherical C{LatLon type}. 

335 

336 @return: C{type(B{obj}} if B{C{obj}} is a C{LatLon} instance of 

337 the required type, C{False} if a C{LatLon} of an other 

338 type or {None} otherwise. 

339 ''' 

340 if ellipsoidal is not None: 

341 try: 

342 return obj.ellipsoidalLatLon if ellipsoidal else obj.sphericalLatLon 

343 except AttributeError: 

344 return None 

345 return isinstanceof(obj, _MODS.latlonBase.LatLonBase) 

346 

347 

348def islistuple(obj, minum=0): 

349 '''Check for list or tuple C{type} with a minumal length. 

350 

351 @arg obj: The object (any C{type}). 

352 @kwarg minum: Minimal C{len} required C({int}). 

353 

354 @return: C{True} if B{C{obj}} is C{list} or C{tuple} with 

355 C{len} at least B{C{minum}}, C{False} otherwise. 

356 ''' 

357 return isinstance(obj, _list_tuple_types) and len(obj) >= minum 

358 

359 

360def isNvector(obj, ellipsoidal=None): 

361 '''Is B{C{obj}} some C{Nvector}? 

362 

363 @arg obj: The object (any C{type}). 

364 @kwarg ellipsoidal: If C{None}, return the type of any C{Nvector}, 

365 if C{True}, only an ellipsoidal C{Nvector type} 

366 or if C{False}, only a spherical C{Nvector type}. 

367 

368 @return: C{type(B{obj}} if B{C{obj}} is an C{Nvector} instance of 

369 the required type, C{False} if an C{Nvector} of an other 

370 type or {None} otherwise. 

371 ''' 

372 if ellipsoidal is not None: 

373 try: 

374 return obj.ellipsoidalNvector if ellipsoidal else obj.sphericalNvector 

375 except AttributeError: 

376 return None 

377 return isinstanceof(obj, _MODS.nvectorBase.NvectorBase) 

378 

379 

380def isodd(x): 

381 '''Is B{C{x}} odd? 

382 

383 @arg x: Value (C{scalar}). 

384 

385 @return: C{True} if B{C{x}} is odd, 

386 C{False} otherwise. 

387 ''' 

388 return bool(int(x) & 1) # == bool(int(x) % 2) 

389 

390 

391def isscalar(obj): 

392 '''Check for scalar types. 

393 

394 @arg obj: The object (any C{type}). 

395 

396 @return: C{True} if B{C{obj}} is C{scalar}, C{False} otherwise. 

397 ''' 

398 return isinstance(obj, _Scalars) and not isbool(obj) 

399 

400 

401def issequence(obj, *excls): 

402 '''Check for sequence types. 

403 

404 @arg obj: The object (any C{type}). 

405 @arg excls: Classes to exclude (C{type}), all positional. 

406 

407 @note: Excluding C{tuple} implies excluding C{namedtuple}. 

408 

409 @return: C{True} if B{C{obj}} is a sequence, C{False} otherwise. 

410 ''' 

411 return isinstance(obj, _Seqs) and not (excls and isinstance(obj, excls)) 

412 

413 

414def isstr(obj): 

415 '''Check for string types. 

416 

417 @arg obj: The object (any C{type}). 

418 

419 @return: C{True} if B{C{obj}} is C{str}, C{False} otherwise. 

420 ''' 

421 return isinstance(obj, _Strs) 

422 

423 

424def issubclassof(Sub, *Supers): 

425 '''Check whether a class is a sub-class of some other class(es). 

426 

427 @arg Sub: The sub-class (C{class}). 

428 @arg Supers: One or more C(super) classes (C{class}). 

429 

430 @return: C{True} if B{C{Sub}} is a sub-class of any B{C{Supers}}, 

431 C{False} if not (C{bool}) or C{None} if B{C{Sub}} is not 

432 a class or if no B{C{Supers}} are given or none of those 

433 are a class. 

434 ''' 

435 if isclass(Sub): 

436 t = tuple(S for S in Supers if isclass(S)) 

437 if t: 

438 return bool(issubclass(Sub, t)) 

439 return None 

440 

441 

442def len2(items): 

443 '''Make built-in function L{len} work for generators, iterators, 

444 etc. since those can only be started exactly once. 

445 

446 @arg items: Generator, iterator, list, range, tuple, etc. 

447 

448 @return: 2-Tuple C{(n, items)} of the number of items (C{int}) 

449 and the items (C{list} or C{tuple}). 

450 ''' 

451 if not isinstance(items, _Seqs): # NOT hasattr(items, '__len__'): 

452 items = list(items) 

453 return len(items), items 

454 

455 

456def map1(fun1, *xs): # XXX map_ 

457 '''Apply each B{C{xs}} to a single-argument function and 

458 return a C{tuple} of results. 

459 

460 @arg fun1: 1-Arg function to apply (C{callable}). 

461 @arg xs: Arguments to apply (C{any positional}). 

462 

463 @return: Function results (C{tuple}). 

464 ''' 

465 return tuple(map(fun1, xs)) 

466 

467 

468def map2(func, *xs): 

469 '''Apply arguments to a function and return a C{tuple} of results. 

470 

471 Unlike Python 2's built-in L{map}, Python 3+ L{map} returns a 

472 L{map} object, an iterator-like object which generates the 

473 results only once. Converting the L{map} object to a tuple 

474 maintains the Python 2 behavior. 

475 

476 @arg func: Function to apply (C{callable}). 

477 @arg xs: Arguments to apply (C{list, tuple, ...}). 

478 

479 @return: Function results (C{tuple}). 

480 ''' 

481 return tuple(map(func, *xs)) 

482 

483 

484def neg(x, neg0=None): 

485 '''Negate C{x} and optionally, negate C{0.0} amd C{-0.0}. 

486 

487 @kwarg neg0: Defines the return value for zero C{B{x}}: if C{None} 

488 return C{0.0}, if C{True} return C{NEG0 if B{x}=0.0} 

489 and C{0.0 if B{x}=NEG0} or if C{False} return C{B{x}} 

490 I{as-is} (C{bool} or C{None}). 

491 

492 @return: C{-B{x} if B{x} else 0.0, NEG0 or B{x}}. 

493 ''' 

494 return (-x) if x else (_0_0 if neg0 is None else (x if not neg0 else 

495 (_0_0 if signBit(x) else _MODS.constants.NEG0))) 

496 

497 

498def neg_(*xs): 

499 '''Negate all C{xs} with L{neg}. 

500 

501 @return: A C{map(neg, B{xs})}. 

502 ''' 

503 return map(neg, xs) 

504 

505 

506def _reverange(n, stop=-1, step=-1): 

507 '''(INTERNAL) Reversed range yielding C{n-1, n-1-step, ..., stop+1}. 

508 ''' 

509 return range(n - 1, stop, step) 

510 

511 

512def signBit(x): 

513 '''Return C{signbit(B{x})}, like C++. 

514 

515 @return: C{True} if C{B{x} < 0} or C{NEG0} (C{bool}). 

516 ''' 

517 return x < 0 or _MODS.constants.isneg0(x) 

518 

519 

520def _signOf(x, ref): # in .fsums 

521 '''(INTERNAL) Return the sign of B{C{x}} versus B{C{ref}}. 

522 ''' 

523 return +1 if x > ref else (-1 if x < ref else 0) 

524 

525 

526def signOf(x): 

527 '''Return sign of C{x} as C{int}. 

528 

529 @return: -1, 0 or +1 (C{int}). 

530 ''' 

531 try: 

532 s = x.signOf() # Fsum instance? 

533 except AttributeError: 

534 s = _signOf(x, 0) 

535 return s 

536 

537 

538def _sizeof(inst): 

539 '''(INTERNAL) Recursively size an C{inst}ance. 

540 

541 @return: Instance' size in bytes (C{int}), 

542 ignoring class attributes and 

543 counting duplicates only once or 

544 C{None}. 

545 

546 @note: With C{PyPy}, the size is always C{None}. 

547 ''' 

548 try: 

549 _zB = _sys.getsizeof 

550 _zD = _zB(None) # get some default 

551 except TypeError: # PyPy3.10 

552 return None 

553 

554 def _zR(s, iterable): 

555 z, _s = 0, s.add 

556 for o in iterable: 

557 i = id(o) 

558 if i not in s: 

559 _s(i) 

560 z += _zB(o, _zD) 

561 if isinstance(o, dict): 

562 z += _zR(s, o.keys()) 

563 z += _zR(s, o.values()) 

564 elif isinstance(o, _list_tuple_set_types): 

565 z += _zR(s, o) 

566 else: 

567 try: # size instance' attr values only 

568 z += _zR(s, o.__dict__.values()) 

569 except AttributeError: # None, int, etc. 

570 pass 

571 return z 

572 

573 return _zR(set(), (inst,)) 

574 

575 

576def splice(iterable, n=2, **fill): 

577 '''Split an iterable into C{n} slices. 

578 

579 @arg iterable: Items to be spliced (C{list}, C{tuple}, ...). 

580 @kwarg n: Number of slices to generate (C{int}). 

581 @kwarg fill: Optional fill value for missing items. 

582 

583 @return: A generator for each of B{C{n}} slices, 

584 M{iterable[i::n] for i=0..n}. 

585 

586 @raise TypeError: Invalid B{C{n}}. 

587 

588 @note: Each generated slice is a C{tuple} or a C{list}, 

589 the latter only if the B{C{iterable}} is a C{list}. 

590 

591 @example: 

592 

593 >>> from pygeodesy import splice 

594 

595 >>> a, b = splice(range(10)) 

596 >>> a, b 

597 ((0, 2, 4, 6, 8), (1, 3, 5, 7, 9)) 

598 

599 >>> a, b, c = splice(range(10), n=3) 

600 >>> a, b, c 

601 ((0, 3, 6, 9), (1, 4, 7), (2, 5, 8)) 

602 

603 >>> a, b, c = splice(range(10), n=3, fill=-1) 

604 >>> a, b, c 

605 ((0, 3, 6, 9), (1, 4, 7, -1), (2, 5, 8, -1)) 

606 

607 >>> tuple(splice(list(range(9)), n=5)) 

608 ([0, 5], [1, 6], [2, 7], [3, 8], [4]) 

609 

610 >>> splice(range(9), n=1) 

611 <generator object splice at 0x0...> 

612 ''' 

613 if not isint(n): 

614 raise _TypeError(n=n) 

615 

616 t = iterable 

617 if not isinstance(t, _list_tuple_types): 

618 t = tuple(t) # force tuple, also for PyPy3 

619 

620 if n > 1: 

621 if fill: 

622 fill = _xkwds_get(fill, fill=MISSING) 

623 if fill is not MISSING: 

624 m = len(t) % n 

625 if m > 0: # same type fill 

626 t += type(t)((fill,) * (n - m)) 

627 for i in range(n): 

628 # XXX t[i::n] chokes PyChecker 

629 yield t[slice(i, None, n)] 

630 else: 

631 yield t 

632 

633 

634def _splituple(strs, *sep_splits): # in .mgrs, .osgr, .webmercator 

635 '''(INTERNAL) Split a C{comma}- or C{whitespace}-separated 

636 string into a C{tuple} of stripped strings. 

637 ''' 

638 t = (strs.split(*sep_splits) if sep_splits else 

639 strs.replace(_COMMA_, _SPACE_).split()) if strs else () 

640 return tuple(s.strip() for s in t if s) 

641 

642 

643_XPACKAGES = _splituple(_getenv(_PYGEODESY_XPACKAGES_, NN)) 

644 

645 

646def unsigned0(x): 

647 '''Unsign if C{0.0}. 

648 

649 @return: C{B{x}} if B{C{x}} else C{0.0}. 

650 ''' 

651 return x if x else _0_0 

652 

653 

654def _xargs_kwds_names(func): 

655 '''(INTERNAL) Get a C{func}'s args and kwds names, including 

656 C{self} for methods. 

657 

658 @note: Python 2 does I{not} include the C{*args} nor the 

659 C{**kwds} names. 

660 ''' 

661 try: 

662 args_kwds = _inspect.signature(func).parameters.keys() 

663 except AttributeError: # .signature new Python 3+ 

664 args_kwds = _inspect.getargspec(func).args 

665 return tuple(args_kwds) 

666 

667 

668def _xcopy(inst, deep=False): 

669 '''(INTERNAL) Copy an object, shallow or deep. 

670 

671 @arg inst: The object to copy (any C{type}). 

672 @kwarg deep: If C{True} make a deep, otherwise 

673 a shallow copy (C{bool}). 

674 

675 @return: The copy of B{C{inst}}. 

676 ''' 

677 return _deepcopy(inst) if deep else _copy(inst) 

678 

679 

680def _xdup(inst, deep=False, **items): 

681 '''(INTERNAL) Duplicate an object, replacing some attributes. 

682 

683 @arg inst: The object to copy (any C{type}). 

684 @kwarg deep: If C{True} copy deep, otherwise shallow. 

685 @kwarg items: Attributes to be changed (C{any}). 

686 

687 @return: A duplicate of B{C{inst}} with modified 

688 attributes, if any B{C{items}}. 

689 

690 @raise AttributeError: Some B{C{items}} invalid. 

691 ''' 

692 d = _xcopy(inst, deep=deep) 

693 for n, v in items.items(): 

694 if getattr(d, n, v) != v: 

695 setattr(d, n, v) 

696 elif not hasattr(d, n): 

697 t = _MODS.named.classname(inst) 

698 t = _SPACE_(_DOT_(t, n), _invalid_) 

699 raise _AttributeError(txt=t, this=inst, **items) 

700 return d 

701 

702 

703def _xgeographiclib(where, *required): 

704 '''(INTERNAL) Import C{geographiclib} and check required version. 

705 ''' 

706 try: 

707 _xpackage(_xgeographiclib) 

708 import geographiclib 

709 except ImportError as x: 

710 raise _xImportError(x, where, Error=LazyImportError) 

711 return _xversion(geographiclib, where, *required) 

712 

713 

714def _xImportError(x, where, Error=_ImportError, **name): 

715 '''(INTERNAL) Embellish an C{Lazy/ImportError}. 

716 ''' 

717 t = _SPACE_(_required_, _by_, _xwhere(where, **name)) 

718 return Error(_Xstr(x), txt=t, cause=x) 

719 

720 

721def _xinstanceof(*Types, **names_values): 

722 '''(INTERNAL) Check C{Types} of all C{name=value} pairs. 

723 

724 @arg Types: One or more classes or types (C{class}), 

725 all positional. 

726 @kwarg names_values: One or more C{B{name}=value} pairs 

727 with the C{value} to be checked. 

728 

729 @raise TypeError: One B{C{names_values}} pair is not an 

730 instance of any of the B{C{Types}}. 

731 ''' 

732 if not (Types and names_values): 

733 raise _xAssertionError(_xinstanceof, *Types, **names_values) 

734 

735 for n, v in names_values.items(): 

736 if not isinstance(v, Types): 

737 raise _TypesError(n, v, *Types) 

738 

739 

740def _xisscalar(**names_values): 

741 '''(INTERNAL) Check all C{name=value} pairs to be C{scalar}. 

742 ''' 

743 for n, v in names_values.items(): 

744 if not isscalar(v): 

745 raise _TypeError(n, v, txt=_not_scalar_) 

746 

747 

748def _xnumpy(where, *required): 

749 '''(INTERNAL) Import C{numpy} and check required version. 

750 ''' 

751 try: 

752 _xpackage(_xnumpy) 

753 import numpy 

754 except ImportError as x: 

755 raise _xImportError(x, where) 

756 return _xversion(numpy, where, *required) 

757 

758 

759def _xpackage(_xpkg): 

760 '''(INTERNAL) Check dependency to be excluded. 

761 ''' 

762 n = _xpkg.__name__[2:] 

763 if n in _XPACKAGES: 

764 x = _SPACE_(n, _in_, _PYGEODESY_XPACKAGES_) 

765 e = _enquote(_getenv(_PYGEODESY_XPACKAGES_, NN)) 

766 raise ImportError(_EQUAL_(x, e)) 

767 

768 

769def _xor(x, *xs): 

770 '''(INTERNAL) Exclusive-or C{x} and C{xs}. 

771 ''' 

772 for x_ in xs: 

773 x ^= x_ 

774 return x 

775 

776 

777def _xscipy(where, *required): 

778 '''(INTERNAL) Import C{scipy} and check required version. 

779 ''' 

780 try: 

781 _xpackage(_xscipy) 

782 import scipy 

783 except ImportError as x: 

784 raise _xImportError(x, where) 

785 return _xversion(scipy, where, *required) 

786 

787 

788def _xsubclassof(*Classes, **names_values): 

789 '''(INTERNAL) Check (super) class of all C{name=value} pairs. 

790 

791 @arg Classes: One or more classes or types (C{class}), all 

792 positional. 

793 @kwarg names_values: One or more C{B{name}=value} pairs 

794 with the C{value} to be checked. 

795 

796 @raise TypeError: One B{C{names_values}} pair is not a 

797 (sub-)class of any of the B{C{Classes}}. 

798 ''' 

799 if not (Classes and names_values): 

800 raise _xAssertionError(_xsubclassof, *Classes, **names_values) 

801 

802 for n, v in names_values.items(): 

803 if not issubclassof(v, *Classes): 

804 raise _TypesError(n, v, *Classes) 

805 

806 

807def _xversion(package, where, *required, **name): 

808 '''(INTERNAL) Check the C{package} version vs B{C{required}}. 

809 ''' 

810 n = len(required) 

811 if n: 

812 t = _xversion_info(package) 

813 if t[:n] < required: 

814 t = _SPACE_(package.__name__, _version_, _DOT_(*t), 

815 _below_, _DOT_(*required), 

816 _required_, _by_, _xwhere(where, **name)) 

817 raise ImportError(t) 

818 return package 

819 

820 

821def _xversion_info(package): # in .karney 

822 '''(INTERNAL) Get the C{package.__version_info__} as a 2- or 

823 3-tuple C{(major, minor, revision)} if C{int}s. 

824 ''' 

825 try: 

826 t = package.__version_info__ 

827 except AttributeError: 

828 t = package.__version__.strip() 

829 t = t.replace(_DOT_, _SPACE_).split()[:3] 

830 return map2(int, t) 

831 

832 

833def _xwhere(where, **name): 

834 '''(INTERNAL) Get the fully qualified name. 

835 ''' 

836 m = _MODS.named.modulename(where, prefixed=True) 

837 if name: 

838 n = _xkwds_get(name, name=NN) 

839 if n: 

840 m = _DOT_(m, n) 

841 return m 

842 

843 

844if _sys_version_info2 < (3, 10): # see .errors 

845 _zip = zip # PYCHOK exported 

846else: # Python 3.10+ 

847 

848 def _zip(*args): 

849 return zip(*args, strict=True) 

850 

851# **) MIT License 

852# 

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

854# 

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

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

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

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

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

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

861# 

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

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

864# 

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

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

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

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

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

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

871# OTHER DEALINGS IN THE SOFTWARE.