Coverage for pygeodesy/internals.py: 90%

278 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-10 16:16 -0500

1 

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

3 

4u'''Mostly INTERNAL functions, except L{machine}, L{print_} and L{printf}. 

5''' 

6# from pygeodesy.basics import isiterablen, ubstr # _MODS 

7# from pygeodesy.errors import _AttributeError, _error_init, _ImmutableError, _UnexpectedError, \ 

8# _xError2, _xkwds_item2 # _MODS 

9from pygeodesy.interns import _BAR_, _COLON_, _DASH_, _DMAIN_, _DOT_, _ELLIPSIS_, _NL_, NN, \ 

10 _NLATvar_, _pygeodesy_, _PyPy__, _python_, _QUOTE1_, _QUOTE2_, \ 

11 _s_, _sys, _SPACE_, _UNDER_ 

12from pygeodesy.interns import _COMMA_, _Python_ # PYCHOK used! 

13# from pygeodesy.streprs import anstr, pairs, unstr # _MODS 

14 

15# import os # _MODS 

16# import os.path # _MODS 

17# import sys as _sys # from .interns 

18 

19_0_0 = 0.0 # PYCHOK in .basics, .constants 

20_100_0 = 100.0 # in .constants 

21_arm64_ = 'arm64' 

22_iOS_ = 'iOS' 

23_macOS_ = 'macOS' 

24_SIsecs = 'fs', 'ps', 'ns', 'us', 'ms', 'sec' # reversed 

25_Windows_ = 'Windows' 

26 

27 

28def typename(obj, *dflt): 

29 '''Get the C{obj.__name__}, the C{dflt} or its outer C{type.__name__} or C{NN} (C{str}). 

30 ''' 

31 try: 

32 return obj.__name__ 

33 except (AttributeError, ImportError): # LazyImportError 

34 pass 

35 return dflt[0] if dflt else typename(type(obj), NN) 

36 

37 

38def _Property_RO(method): 

39 '''(INTERNAL) Can't import L{props.Property_RO}, I{recursively}. 

40 ''' 

41 name = typename(method) 

42 

43 def _del(inst, *unused): # PYCHOK no cover 

44 inst.__dict__.pop(name, None) 

45 

46 def _get(inst, *unused): # PYCHOK 2 vs 3 args 

47 try: # to get the cached value immediately 

48 v = inst.__dict__[name] 

49 except (AttributeError, KeyError): 

50 # cache the value in the instance' __dict__ 

51 inst.__dict__[name] = v = method(inst) 

52 return v 

53 

54 def _set(inst, val): # PYCHOK no cover 

55 setattr(inst, name, val) # force error 

56 

57 return property(_get, _set, _del) 

58 

59 

60class _Enum(object): # in .elliptic, .utily 

61 '''(INTERNAL) Enum-like, immutable items. 

62 ''' 

63 # _ImmutableError = None 

64 

65 def __init__(self, **enums): 

66 self.__dict__.update(enums) 

67 # for item in enums.items(): 

68 # setattr(self, *item) # object.__setattr__ 

69 

70 def __str__(self): 

71 _unstr = _MODS.streprs.unstr 

72 return _unstr(_Enum, **self.__dict__) 

73 

74 # def __delattr__(self, attr): # PYCHOK no cover 

75 # raise _ImmutableError(self, attr) # _del_ 

76 

77 # def __setattr__(self, attr, value): # PYCHOK no cover 

78 # raise _ImmutableError(self, attr, value) 

79 

80 

81class _MODS_Base(object): 

82 '''(INTERNAL) Base-class for C{lazily._ALL_MODS}. 

83 ''' 

84 def __delattr__(self, attr): # PYCHOK no cover 

85 self.__dict__.pop(attr, None) 

86 

87 def __setattr__(self, attr, value): # PYCHOK no cover 

88 raise _ImmutableError(self, attr, value) 

89 

90 @_Property_RO 

91 def basics(self): 

92 '''Get module C{pygeodesy.basics}, I{once}. 

93 ''' 

94 from pygeodesy import basics as b # DON'T _lazy_import2 

95 return b 

96 

97 @_Property_RO 

98 def bits_machine2(self): # in test/bases.py 

99 '''Get platform 2-list C{[bits, machine]}, I{once}. 

100 ''' 

101 import platform as p 

102 m = p.machine() # ARM64, arm64, x86_64, iPhone13,2, etc. 

103 m = m.replace(_COMMA_, _UNDER_) 

104 if m.lower() == 'x86_64': # PYCHOK on Intel or Rosetta2 ... 

105 v = p.mac_ver()[0] # ... and only on macOS ... 

106 if v and _version2(v) > (10, 15): # ... 11+ aka 10.16 

107 # <https://Developer.Apple.com/forums/thread/659846> 

108 # _sysctl_uint('hw.optional.arm64') and \ 

109 if _sysctl_uint('sysctl.proc_translated'): 

110 m = _UNDER_(_arm64_, m) # Apple Si emulating Intel x86-64 

111 return [p.architecture()[0], # bits 

112 m] # arm64, arm64_x86_64, x86_64, etc. 

113 

114 @_Property_RO 

115 def ctypes3(self): 

116 '''Get C{ctypes.CDLL}, C{find_library} and C{dlopen}, I{once}. 

117 ''' 

118 import ctypes as c 

119 from ctypes.util import find_library as f 

120 

121 def dlopen(name): # on macOS only 

122 return c._dlopen(name, c.DEFAULT_MODE) 

123 

124 return c.CDLL, f, (dlopen if _ismacOS() else None) 

125 

126 @_Property_RO 

127 def errors(self): 

128 '''Get module C{pygeodesy.errors}, I{once}. 

129 ''' 

130 from pygeodesy import errors as e # DON'T _lazy_import2 

131 return e 

132 

133 @_Property_RO 

134 def inspect(self): # in .basics 

135 '''Get module C{inspect}, I{once}. 

136 ''' 

137 import inspect as i 

138 return i 

139 

140 def ios_ver(self): 

141 '''Mimick C{platform.xxx_ver} for C{iOS}. 

142 ''' 

143 try: # Pythonista only 

144 from platform import iOS_ver 

145 t = iOS_ver() 

146 except (AttributeError, ImportError): 

147 t = NN, (NN, NN, NN), NN 

148 return t 

149 

150 @_Property_RO 

151 def name(self): 

152 '''Get this name (C{str}). 

153 ''' 

154 return typename(type(self)) 

155 

156 @_Property_RO 

157 def nix2(self): # PYCHOK no cover 

158 '''Get Linux 2-tuple C{(distro, version)}, I{once}. 

159 ''' 

160 from platform import uname 

161 v, n = NN, uname()[0] # [0] == .system 

162 if n.lower() == 'linux': 

163 try: # use distro only on Linux, not macOS, etc. 

164 import distro # <https://PyPI.org/project/distro> 

165 _a = _MODS.streprs.anstr 

166 v = _a(distro.version()) # first 

167 n = _a(distro.id()) # .name()? 

168 except (AttributeError, ImportError): 

169 pass # v = str(_0_0) 

170 n = n.capitalize() 

171 return n, v 

172 

173 def nix_ver(self): # PYCHOK no cover 

174 '''Mimick C{platform.xxx_ver} for C{*nix}. 

175 ''' 

176 _, v = _MODS.nix2 

177 t = _version2(v, n=3) if v else (NN, NN, NN) 

178 return v, t, machine() 

179 

180 @_Property_RO 

181 def os(self): 

182 '''Get module C{os}, I{once}. 

183 ''' 

184 import os as o 

185 import os.path # PYLINT W0611 

186 return o 

187 

188 @_Property_RO 

189 def osversion2(self): 

190 '''Get 2-list C{[OS, release]}, I{once}. 

191 ''' 

192 import platform as p 

193 _Nix, _ = _MODS.nix2 

194 # - mac_ver() returns ('10.12.5', ..., 'x86_64') on 

195 # macOS and ('10.3.3', ..., 'iPad4,2') on iOS 

196 # - win32_ver is ('XP', ..., 'SP3', ...) on Windows XP SP3 

197 # - platform() returns 'Darwin-16.6.0-x86_64-i386-64bit' 

198 # on macOS and 'Darwin-16.6.0-iPad4,2-64bit' on iOS 

199 # - sys.platform is 'darwin' on macOS, 'ios' on iOS, 

200 # 'win32' on Windows and 'cygwin' on Windows/Gygwin 

201 # - distro.id() and .name() return 'Darwin' on macOS 

202 for n, v in ((_iOS_, _MODS.ios_ver), 

203 (_macOS_, p.mac_ver), 

204 (_Windows_, p.win32_ver), 

205 (_Nix, _MODS.nix_ver), 

206# removed Py 3.15 ('Java', p.java_ver), 

207 ('uname', p.uname)): 

208 v = v()[0] 

209 if v and n: 

210 break 

211 else: 

212 n = v = NN # XXX AssertionError? 

213 return [n, v] 

214 

215 @_Property_RO 

216 def _Popen_kwds2(self): 

217 '''(INTERNAL) Get C{subprocess.Popen} and C{-kwds}. 

218 ''' 

219 import subprocess as s 

220 kwds = dict(creationflags=0, # executable=sys.executable, shell=True, 

221 stdin=s.PIPE, stdout=s.PIPE, stderr=s.STDOUT) 

222 if _MODS.sys_version_info2 > (3, 6): # PYLINT W0143 

223 kwds.update(text=True) 

224 return s.Popen, kwds 

225 

226 @_Property_RO 

227 def Pythonarchine(self): 

228 '''Get 3- or 4-list C{[PyPy, Python, bits, machine]}, I{once}. 

229 ''' 

230 v = _sys.version 

231 l3 = [_Python_(v)] + _MODS.bits_machine2 

232 pypy = _PyPy__(v) 

233 if pypy: # PYCHOK no cover 

234 l3.insert(0, pypy) 

235 return l3 

236 

237 @_Property_RO 

238 def streprs(self): 

239 '''Get module C{pygeodesy.streprs}, I{once}. 

240 ''' 

241 from pygeodesy import streprs as s # DON'T _lazy_import2 

242 return s 

243 

244 @_Property_RO 

245 def sys_version_info2(self): 

246 '''Get C{sys.version_inf0[:2], I{once}. 

247 ''' 

248 return _sys.version_info[:2] 

249 

250 @_Property_RO 

251 def version(self): 

252 '''Get pygeodesy version, I{once}. 

253 ''' 

254 from pygeodesy import version as v 

255 return v 

256 

257_MODS = _MODS_Base() # PYCHOK overwritten by .lazily 

258 

259 

260def _caller3(up, base=True): # in .lazily, .named 

261 '''(INTERNAL) Get 3-tuple C{(caller name, file name, line number)} 

262 for the caller B{C{up}} frames back in the Python call stack. 

263 

264 @kwarg base: Use C{B{base}=False} for the fully-qualified file 

265 name, otherwise the base (module) name (C{bool}). 

266 ''' 

267 f = None 

268 _b = _MODS.os.path.basename if base else _passarg 

269 try: 

270 f = _sys._getframe(up + 1) # == inspect.stack()[up + 1][0] 

271 t = _MODS.inspect.getframeinfo(f) 

272 t = t.function, _b(t.filename), t.lineno 

273# or ... 

274 # f = _sys._getframe(up + 1) 

275 # c = f.f_code 

276 # t = (c.co_name, # caller name 

277 # _b(c.co_filename), # file name .py 

278 # f.f_lineno) # line number 

279# or ... 

280 # t = _MODS.inspect.stack()[up + 1] # (frame, filename, lineno, function, ...) 

281 # t = t[3], _b(t[1]), t[2] 

282 except (AttributeError, IndexError, ValueError): 

283 # sys._getframe(1) ... 'importlib._bootstrap' line 1032, 

284 # may throw a ValueError('call stack not deep enough') 

285 t = NN, NN, 0 

286 finally: 

287 del f # break ref cycle 

288 return t 

289 

290 

291def _enquote(strs, quote=_QUOTE2_, white=NN): # in .basics, .solveBase 

292 '''(INTERNAL) Enquote a string containing whitespace or replace 

293 whitespace by C{white} if specified. 

294 ''' 

295 if strs: 

296 t = strs.split() 

297 if len(t) > 1: 

298 strs = white.join(t if white else (quote, strs, quote)) 

299 return strs 

300 

301 

302def _envPYGEODESY(which, dflt=NN): 

303 '''(INTERNAL) Return an C{PYGEODESY_...} ENV value or C{dflt}. 

304 ''' 

305 return _getenv(_PYGEODESY_ENV(which), dflt) 

306 

307 

308def _fper(p, q, per=_100_0, prec=1): 

309 '''Format a percentage C{B{p} * B{per} / B{q}} (C{str}). 

310 ''' 

311 return '%.*f%%' % (prec, (float(p) * per / float(q))) 

312 

313 

314_getenv = _MODS.os.getenv # PYCHOK in .lazily, ... , pyrdnap 

315 

316 

317def _headof(name): 

318 '''(INTERNAL) Get the head name of qualified C{name} or the C{name}. 

319 ''' 

320 i = name.find(_DOT_) 

321 return name if i < 0 else name[:i] 

322 

323 

324def _ImmutableError(*inst_attr_value): 

325 '''(INTERNAL) Format an C{_ImmutableError}. 

326 ''' 

327 return _MODS.errors._ImmutableError(*inst_attr_value) 

328 

329 

330# def _is(a, b): # PYCHOK no cover 

331# '''(INTERNAL) C{a is b}? in C{PyPy} 

332# ''' 

333# return (a == b) if _isPyPy() else (a is b) 

334 

335 

336def _isAppleSi(): # PYCHOK no cover 

337 '''(INTERNAL) Is this C{macOS on Apple Silicon}? (C{bool}) 

338 ''' 

339 return _ismacOS() and machine().startswith(_arm64_) 

340 

341 

342def _isiOS(): # in test/bases 

343 '''(INTERNAL) Is this C{iOS}? (C{bool}) 

344 ''' 

345 return _MODS.osversion2[0] is _iOS_ 

346 

347 

348def _ismacOS(): # in test/bases 

349 '''(INTERNAL) Is this C{macOS}? (C{bool}) 

350 ''' 

351 return _sys.platform[:6] == 'darwin' and \ 

352 _MODS.osversion2[0] is _macOS_ # and _MODS.os.name == 'posix' 

353 

354 

355def _isNix(): # in test/bases 

356 '''(INTERNAL) Is this a C{Linux} distro? (C{str} or L{NN}) 

357 ''' 

358 return _MODS.nix2[0] 

359 

360 

361def _isPyChecker(): # PYCHOK no cover 

362 '''(INTERNAL) Is C{PyChecker} running? (C{bool}) 

363 ''' 

364 # .../pychecker/checker.py --limit 0 --stdlib pygeodesy/<mod>/<name>.py 

365 return _sys.argv[0].endswith('/pychecker/checker.py') or \ 

366 bool(_envPYGEODESY('PYCHECKER')) 

367 

368 

369def _isPyChOK(): # PYCHOK no cover 

370 '''(INTERNAL) Is C{PyChOK} running? (C{bool}) 

371 ''' 

372 return bool(_envPYGEODESY('PYCHOK')) 

373 

374 

375def _isPyLint(): # PYCHOK no cover 

376 '''(INTERNAL) Is C{PyLint} running? (C{bool}) 

377 ''' 

378 # pylint doesn't parse and/or run python code? 

379 return bool(_envPYGEODESY('PYLINT')) 

380 

381 

382def _isPyPy(): # in test/bases 

383 '''(INTERNAL) Is this C{PyPy}? (C{bool}) 

384 ''' 

385 # platform.python_implementation() == 'PyPy' 

386 return _MODS.Pythonarchine[0].startswith(_PyPy__) 

387 

388 

389def _isWindows(): # in test/bases 

390 '''(INTERNAL) Is this C{Windows}? (C{bool}) 

391 ''' 

392 return _sys.platform[:3] == 'win' and \ 

393 _MODS.osversion2[0] is _Windows_ 

394 

395 

396def _load_lib(name): 

397 '''(INTERNAL) Load a C{dylib}, B{C{name}} must startwith('lib'). 

398 ''' 

399 CDLL, find_lib, dlopen = _MODS.ctypes3 

400 ns = find_lib(name), name 

401 if dlopen: 

402 # macOS 11+ (aka 10.16) no longer provides direct loading of 

403 # system libraries. As a result, C{ctypes.util.find_library} 

404 # will not find any library, unless previously installed by a 

405 # low-level dlopen(name) call (with the library base C{name}). 

406 ns += (_DOT_(name, 'dylib'), 

407 _DOT_(name, 'framework'), _MODS.os.path.join( 

408 _DOT_(name, 'framework'), name)) 

409 else: # not macOS 

410 dlopen = _passarg # no-op 

411 

412 for n in ns: 

413 try: 

414 if n and dlopen(n): # pre-load handle 

415 lib = CDLL(n) # == ctypes.cdll.LoadLibrary(n) 

416 if lib._name: # has a qualified name 

417 return lib 

418 except (AttributeError, OSError): 

419 pass 

420 

421 return None # raise OSError 

422 

423 

424def machine(): 

425 '''Return standard C{platform.machine}, but distinguishing Intel I{native} 

426 from Intel I{emulation} on Apple Silicon (on macOS only). 

427 

428 @return: Machine C{'arm64'} for Apple Silicon I{native}, C{'x86_64'} 

429 for Intel I{native}, C{"arm64_x86_64"} for Intel I{emulation}, 

430 etc. (C{str} with C{comma}s replaced by C{underscore}s). 

431 ''' 

432 return _MODS.bits_machine2[1] 

433 

434 

435def _name_version(pkg): 

436 '''(INTERNAL) Return C{pkg.__name__ + ' ' + .__version__}. 

437 ''' 

438 return _SPACE_(typename(pkg), pkg.__version__) # _DVERSION_ 

439 

440 

441def _osversion2(sep=NN): # in .lazily, test/bases.versions 

442 '''(INTERNAL) Get the O/S name and release as C{2-list} or C{str}. 

443 ''' 

444 l2 = _MODS.osversion2 

445 return sep.join(l2) if sep else l2 # 2-list() 

446 

447 

448def _passarg(arg): 

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

450 ''' 

451 return arg 

452 

453 

454def _passargs(*args): 

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

456 ''' 

457 return args 

458 

459 

460def _plural(noun, n, nn=NN): 

461 '''(INTERNAL) Return C{noun}['s'] or C{NN}. 

462 ''' 

463 return NN(noun, _s_) if n > 1 else (noun if n else nn) 

464 

465 

466def _popen2(cmd, stdin=None): # in .mgrs, .solveBase, .testMgrs 

467 '''(INTERNAL) Invoke C{B{cmd} tuple} and return 2-tuple C{(std, status)} 

468 with all C{stdout/-err} output, I{stripped} and C{int} exit status. 

469 ''' 

470 _Popen, kwds = _MODS._Popen_kwds2 

471 p = _Popen(cmd, **kwds) # PYCHOK kwArgs 

472 r = p.communicate(stdin)[0] # stdout + NL + stderr 

473 return _MODS.basics.ub2str(r).strip(), p.returncode 

474 

475 

476def _pregistry(registry): 

477 '''(INTERNAL) Print all items of a C{registry}. 

478 ''' 

479 t = [NN] + registry.toRepr(all=True, asorted=True).split(_NL_) 

480 printf(_NLATvar_.join(i.strip(_COMMA_) for i in t)) 

481 

482 

483def print_(*args, **nl_nt_prec_prefix__end_file_flush_sep__kwds): # PYCHOK no cover 

484 '''Python 3+ C{print}-like formatting and printing. 

485 

486 @arg args: Values to be converted to C{str} and joined by B{C{sep}}, 

487 all positional. 

488 

489 @see: Function L{printf} for further details. 

490 ''' 

491 return printf(NN, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds) 

492 

493 

494def printf(fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds): 

495 '''C{Printf-style} and Python 3+ C{print}-like formatting and printing. 

496 

497 @arg fmt: U{Printf-style<https://Docs.Python.org/3/library/stdtypes.html# 

498 printf-style-string-formatting>} format specification (C{str}). 

499 @arg args: Arguments to be formatted (any C{type}, all positional). 

500 @kwarg nl_nt_prec_prefix__end_file_flush_sep__kwds: Optional keyword arguments 

501 C{B{nl}=0} for the number of leading blank lines (C{int}), C{B{nt}=0} 

502 the number of trailing blank lines (C{int}), C{B{prefix}=NN} to be 

503 inserted before the formatted text (C{str}) and Python 3+ C{print} 

504 keyword arguments C{B{end}}, C{B{sep}}, C{B{file}} and C{B{flush}}. 

505 Any remaining C{B{kwds}} are C{printf-style} name-value pairs to be 

506 formatted, I{iff no B{C{args}} are present} using C{B{prec}=6} for 

507 the number of decimal digits (C{int}). 

508 

509 @return: Number of bytes written. 

510 ''' 

511 b, e, f, fl, p, s, kwds = _print7(**nl_nt_prec_prefix__end_file_flush_sep__kwds) 

512 try: 

513 if args: 

514 t = (fmt % args) if fmt else s.join(map(str, args)) 

515 elif kwds: 

516 t = (fmt % kwds) if fmt else s.join( 

517 _MODS.streprs.pairs(kwds, prec=p)) 

518 else: 

519 t = fmt 

520 except Exception as x: 

521 _Error, s = _MODS.errors._xError2(x) 

522 _unstr = _MODS.streprs.unstr 

523 t = _unstr(printf, fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds) 

524 raise _Error(s, txt=t, cause=x) 

525 try: 

526 n = f.write(NN(b, t, e)) 

527 except UnicodeEncodeError: # XXX only Windows 

528 t = t.replace('\u2032', _QUOTE1_).replace('\u2033', _QUOTE2_) 

529 n = f.write(NN(b, t, e)) 

530 if fl: # PYCHOK no cover 

531 f.flush() 

532 return n 

533 

534 

535def _print7(nl=0, nt=0, prec=6, prefix=NN, sep=_SPACE_, file=_sys.stdout, 

536 end=_NL_, flush=False, **kwds): 

537 '''(INTERNAL) Unravel the C{printf} and remaining keyword arguments. 

538 ''' 

539 if nl > 0: 

540 prefix = NN(_NL_ * nl, prefix) 

541 if nt > 0: 

542 end = NN(end, _NL_ * nt) 

543 return prefix, end, file, flush, prec, sep, kwds 

544 

545 

546def _PYGEODESY_ENV(which): 

547 '''(INTERNAL) Return an ENV C{str} C{PYGEODESY_...}. 

548 ''' 

549 return _UNDER_(_pygeodesy_, typename(which, which)).upper() 

550 

551 

552def _Pythonarchine(sep=NN): # in .lazily, .test/bases versions 

553 '''(INTERNAL) Get PyPy and Python versions, bits and machine as C{3- or 4-list} or C{str}. 

554 ''' 

555 l3 = _MODS.Pythonarchine 

556 return sep.join(l3) if sep else l3 # 3- or 4-list 

557 

558 

559def _secs2str(secs): # in .geoids, ../test/bases, .pyrdnap 

560 '''Convert a time in C{secs} to C{str}. 

561 ''' 

562 if secs < _100_0: 

563 unit = len(_SIsecs) - 1 

564 while 0 < secs < 1 and unit > 0: 

565 secs *= 1e3 # _1000_0 

566 unit -= 1 

567 t = '%.3f %s' % (secs, _SIsecs[unit]) 

568 else: 

569 m, s = divmod(secs, 60) 

570 if m < 60: 

571 t = '%d:%06.3f' % (int(m), s) 

572 else: 

573 h, m = divmod(int(m), 60) 

574 t = '%d:%02d:%06.3f' % (h, m, s) 

575 return t 

576 

577 

578def _sizeof(obj, deep=True): 

579 '''(INTERNAL) Recursively size an C{obj}ect. 

580 

581 @kwarg deep: If C{True}, include the size of all 

582 C{.__dict__.values()} (C{bool}). 

583 

584 @return: The C{obj} size in bytes (C{int}), ignoring 

585 class attributes and counting instances only 

586 once or C{None}. 

587 

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

589 ''' 

590 try: 

591 _zB = _sys.getsizeof 

592 _zD = _zB(None) # some default 

593 except TypeError: # PyPy3.10 

594 return None 

595 

596 b = _MODS.basics 

597 _isiterablen = b.isiterablen 

598 _Str_Bytes = b._Strs + b._Bytes # + (range, map) 

599 

600 def _zR(s, iterable): 

601 z, _s = 0, s.add 

602 for o in iterable: 

603 i = id(o) 

604 if i not in s: 

605 _s(i) 

606 z += _zB(o, _zD) 

607 if isinstance(o, dict): 

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

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

610 elif _isiterablen(o) and not \ 

611 isinstance(o, _Str_Bytes): 

612 z += _zR(s, o) 

613 elif deep: 

614 try: # size instance' attr values only 

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

616 except AttributeError: # None, int, etc. 

617 pass 

618 return z 

619 

620 return _zR(set(), (obj,)) 

621 

622 

623def _sysctl_uint(name): 

624 '''(INTERNAL) Get an C{unsigned int sysctl} item by name, I{ONLY on macOS!} 

625 ''' 

626 libc = _load_lib('libc') if _ismacOS() else None 

627 if libc: # <https://StackOverflow.com/questions/759892/python-ctypes-and-sysctl> 

628 import ctypes as c 

629 n = c.c_char_p(_MODS.basics.str2ub(name)) # bytes(name, _utf_8_) 

630 u = c.c_uint(0) 

631 z = c.c_size_t(c.sizeof(u)) 

632 r = libc.sysctlbyname(n, c.byref(u), c.byref(z), None, c.c_size_t(0)) # PYCHOK attr 

633 else: # not macOS or couldn't find or load 'libc'= 

634 r = -2 

635 return int(r if r else u.value) # -1 ENOENT error, -2 no libc or not macOS 

636 

637 

638def _tailof(name): 

639 '''(INTERNAL) Get the base name of qualified C{name} or the C{name}. 

640 ''' 

641 i = name.rfind(_DOT_) + 1 

642 return name[i:] if i > 0 else name 

643 

644 

645def _under(name): # PYCHOK in .datums, .auxilats, .geodesicw, .ups, .utm, .utmupsBase, ... 

646 '''(INTERNAL) Prefix C{name} with an I{underscore}. 

647 ''' 

648 return name if name.startswith(_UNDER_) else NN(_UNDER_, name) 

649 

650 

651def _usage(file_py, *args, **opts_help): # in .etm, .geodesici # PYCHOK no cover 

652 '''(INTERNAL) Build "usage: python -m ..." cmd line for module B{C{file_py}}. 

653 ''' 

654 if opts_help: 

655 

656 def _help(alts=(), help=NN, **unused): # PYLINT W0622 

657 if alts and help: 

658 h = NN(help, _SPACE_).lstrip(_DASH_) 

659 for a in alts: 

660 if a.startswith(h): 

661 return NN(_DASH_, a), 

662 

663 def _opts(opts=NN, alts=(), **unused): 

664 # opts='T--v-C-R meter-c|i|n|o' 

665 d, fmt = NN, _MODS.streprs.Fmt.SQUARE 

666 for o in (opts + _BAR_(*alts)).split(_DASH_): 

667 if o: 

668 yield fmt(NN(d, _DASH_, o.replace(_BAR_, ' | -'))) 

669 d = NN 

670 else: 

671 d = _DASH_ 

672 

673 args = _help(**opts_help) or (tuple(_opts(**opts_help)) + args) 

674 

675 u = _COLON_(typename(_usage)[1:], NN) 

676 return _SPACE_(u, *_usage_argv(file_py, *args)) 

677 

678 

679def _usage_argv(argv0, *args): 

680 '''(INTERNAL) Return 3-tuple C{(python, '-m', module, *args)}. 

681 ''' 

682 o = _MODS.os 

683 p = o.path 

684 m = p.dirname(argv0).replace(o.getcwd(), _ELLIPSIS_) \ 

685 .replace(o.sep, _DOT_).strip() 

686 b, x = p.splitext(p.basename(argv0)) 

687 if x == '.py' and b != _DMAIN_: 

688 m = _DOT_(m or _pygeodesy_, b) 

689 p = NN(_python_, _MODS.sys_version_info2[0]) 

690 return (p, '-m', _enquote(m)) + args 

691 

692 

693def _version2(version, n=2): 

694 '''(INTERNAL) Split C{B{version} str} into a C{1-, 2- or 3-tuple} of C{int}s. 

695 ''' 

696 t = _version_ints(version.split(_DOT_, 2)) 

697 if len(t) < n: 

698 t += (0,) * n 

699 return t[:n] 

700 

701 

702def _version_info(package): # in .basics, .karney._kWrapped.Math 

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

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

705 ''' 

706 try: 

707 return _version_ints(package.__version_info__) 

708 except AttributeError: 

709 return _version2(package.__version__.strip(), n=3) 

710 

711 

712def _version_ints(vs): 

713 # helper for _version2 and _version_info above 

714 

715 def _ints(vs): 

716 for v in vs: 

717 try: 

718 yield int(v.strip()) 

719 except (TypeError, ValueError): 

720 pass 

721 

722 return tuple(_ints(vs)) 

723 

724 

725def _versions(sep=_SPACE_, **prefix_version): # in pyaxqg, pybelbg, pyrdnap 

726 '''(INTERNAL) Get pygeodesy, PyPy and Python versions, bits, machine and OS 

727 as C{8- or 10-list} or C{str}, with optional prefix name and version. 

728 ''' 

729 vl = [_pygeodesy_, _MODS.version] + _Pythonarchine() + _osversion2() 

730 if prefix_version: 

731 p, v = _MODS.errors._xkwds_item2(prefix_version) 

732 vl = [p, _DOT_(*_version2(v, n=3))] + vl 

733 return sep.join(vl) if sep else vl 

734 

735 

736__all__ = tuple(map(typename, (machine, print_, printf, typename))) 

737__version__ = '26.10.03' 

738 

739if __name__ == _DMAIN_: 

740 

741 def _main(): 

742 from pygeodesy import _isfrozen, isLazy 

743 

744 print_(*(_versions(sep=NN) + ['_isfrozen', _isfrozen, 

745 'isLazy', isLazy])) 

746 

747 _main() 

748 

749# % python3 -m pygeodesy.internals 

750# pygeodesy 25.8.18 Python 3.13.5 64bit arm64 macOS 15.6 _isfrozen False isLazy 1 

751 

752# **) MIT License 

753# 

754# Copyright (C) 2016-2027 -- mrJean1 at Gmail -- All Rights Reserved. 

755# 

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

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

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

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

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

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

762# 

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

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

765# 

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

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

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

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

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

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

772# OTHER DEALINGS IN THE SOFTWARE.