Coverage for pygeodesy/lazily.py: 90%

193 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-12-02 13:46 -0500

1 

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

3 

4u'''Lazily import C{pygeodesy} modules and attributes, based on 

5U{lazy_import<https://modutil.ReadTheDocs.io/en/latest/#lazy_import>} 

6from I{Brett Cannon}'s U{modutil<https://PyPI.org/project/modutil>}. 

7 

8C{Lazy import} is I{supported only for }U{Python 3.7+ 

9<https://Snarky.Ca/lazy-importing-in-python-3-7>} and is I{enabled by 

10default} in U{PyGeodesy 18.11.10<https://PyPI.org/project/PyGeodesy>} 

11I{and newer}. 

12 

13To I{enable} C{lazy import}, set C{env} variable C{PYGEODESY_LAZY_IMPORT} 

14to C{1}, C{2}, C{3} or higher prior to C{import pygeodesy}. To I{disable} 

15C{lazy import}, set C{env} variable C{PYGEODESY_LAZY_IMPORT} to C{0} or 

16an empty string. Use C{2} or higher to print a message for each lazily 

17imported module and attribute, similar to C{env} variable C{PYTHONVERBOSE} 

18showing imports. Using C{3} or higher also shows the importing file name 

19and line number. 

20 

21@note: C{Lazy import} applies only to top-level modules of C{pygeodesy}. 

22A C{lazy import} of a top-level module inherently loads all sub-modules 

23imported by that top-level module. 

24 

25@note: C{Lazy import} raises a L{LazyAttributeError} or L{LazyImportError} 

26depending on the cause of the error and such errors can occur late, after 

27all initial imports. 

28''' 

29 

30# from pygeodesy.errors import _xError2 # _ALL_MODS 

31from pygeodesy.interns import MISSING, NN, __all__ as _interns_a_l_l_, _areaOf_, \ 

32 _attribute_, _by_, _COLONSPACE_, _COMMASPACE_, \ 

33 _doesn_t_exist_, _DOT_, _enabled_, _EQUALSPACED_, \ 

34 _from_, _immutable_, _isclockwise_, _ispolar_, _NL_, \ 

35 _no_, _NorthPole_, _not_, _or_, _pygeodesy_, _line_, \ 

36 _module_, _pygeodesy_abspath_, _Python_, _QUOTE1_, \ 

37 _QUOTE2_, _SouthPole_, _SPACE_, _sub_packages, _UNDER_, \ 

38 _version_, _dunder_nameof, _headof, _tailof # _DEPRECATED_ 

39from pygeodesy.interns import _intern # PYCHOK used! 

40# from pygeodesy.streprs import Fmt, pairs, unstr # _ALL_MODS 

41 

42from os import getenv as _getenv # in .errors, .geodsolve, .props, .units 

43from os.path import basename as _basename 

44import sys as _sys # in .basics._sizeof 

45try: 

46 from importlib import import_module 

47except ImportError: # Python 2.6- 

48 def import_module(name, *package): 

49 t = _ALL_MODS.streprs.unstr(import_module, name, *package) 

50 raise LazyImportError(t, txt=_doesn_t_exist_) 

51 

52_a_l_l_ = '__all__' # in .__init__ 

53__as__ = ' as ' 

54_FOR_DOCS = _getenv('PYGEODESY_FOR_DOCS', NN) # for epydoc ... 

55_from_DOT__ = _SPACE_(NN, _from_, _DOT_) 

56_i0 = () # PYCHOK empty tuple 

57_imports_ = 'imports' 

58_init__all__ = _FOR_DOCS or _getenv('PYGEODESY_INIT__ALL__', _a_l_l_) == _a_l_l_ # PYCHOK expoted 

59_lazily_ = 'lazily' 

60_lazily_imported__ = _SPACE_('#', _lazily_, 'imported', NN) 

61_p_a_c_k_a_g_e_ = '__package__' 

62_PYGEODESY_GEOCONVERT_ = 'PYGEODESY_GEOCONVERT' # PYCHOK .mgrs, test.bases 

63_PYGEODESY_GEODSOLVE_ = 'PYGEODESY_GEODSOLVE' # PYCHOK .geodsolve, test.bases 

64_PYGEODESY_LAZY_IMPORT_ = 'PYGEODESY_LAZY_IMPORT' 

65_PYGEODESY_RHUMBSOLVE_ = 'PYGEODESY_RHUMBSOLVE' # PYCHOK .rhumb.solve, test.bases 

66_PYTHON_X_DEV = getattr(_sys, '_xoptions', {}).get('dev', # Python 3.2+ 

67 _getenv('PYTHONDEVMODE', NN)) # PYCHOK exported 

68_sys_version_info2 = _sys.version_info[:2] # in .basics, .fmath, ... 

69_unlazy = _unLazy0 = _sys_version_info2 < (3, 7) # PYCHOK mod.__getattr__ 3.7+ 

70_WARNINGS_X_DEV = _getenv('PYGEODESY_WARNINGS', NN) and ( 

71 _PYTHON_X_DEV or bool(_sys.warnoptions)) # PYCHOK .props 

72# @module_property[_RO?] <https://GitHub.com/jtushman/proxy_tools/> 

73isLazy = None # see @var isLazy in .__init__ 

74 

75 

76class LazyAttributeError(AttributeError): 

77 '''Raised if a C{lazily imported} attribute is missing or invalid. 

78 ''' 

79 def __init__(self, *name_value, **txt): 

80 _ALL_MODS.errors._error_init(AttributeError, self, name_value, **txt) 

81 

82 

83class LazyImportError(ImportError): 

84 '''Raised if C{lazy import} is not supported, disabled or failed some other way. 

85 ''' 

86 def __init__(self, *name_value, **txt): 

87 _ALL_MODS.errors._error_init(ImportError, self, name_value, **txt) 

88 

89 

90class _Dict(dict): 

91 '''(INTERNAL) Imports C{dict}. 

92 ''' 

93 def __getattr__(self, attr): 

94 try: 

95 return self[attr] 

96 except KeyError: 

97 return dict.__getattr__(self, attr) 

98 

99# def __setattr__(self, attr, value): 

100# if attr in self: 

101# self[attr] = value 

102# else: 

103# dict.__setattr__(self, attr, value) 

104 

105 def add(self, key, value, *values): 

106 '''Add C{[key] = value}, typically C{[attr] = mod}. 

107 

108 @raise AssertionError: The B{C{key}} already exists 

109 with a different B{C{value}}. 

110 ''' 

111 if key in self: 

112 val = self[key] # duplicate OK 

113 if val != value and val not in values: # PYCHOK no cover 

114 k = _ALL_MODS.streprs.Fmt.SQUARE(_imports_, key) 

115 t = _COLONSPACE_(k, repr(val)) 

116 t = _COMMASPACE_(t, _not_(repr(value))) 

117 raise AssertionError(t) 

118 else: 

119 self[key] = value 

120 

121 

122class _NamedEnum_RO(dict): 

123 '''(INTERNAL) C{Read_Only} enum-like C{dict} sub-class. 

124 ''' 

125# _name = NN # also first kwd, __init__(_name=...) 

126 

127 def _DOT_(self, attr): # PYCHOK no cover 

128 return _DOT_(self._name, attr) # PYCHOK _name 

129 

130 def __getattr__(self, attr): 

131 try: 

132 return self[attr] 

133 except KeyError: 

134 t = self._DOT_(attr) 

135 raise LazyAttributeError(t, txt=_doesn_t_exist_) 

136 

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

138 t = _EQUALSPACED_(self._DOT_(attr), value) 

139 raise LazyAttributeError(t, txt=_immutable_) 

140 

141 def enums(self): 

142 for m, t in dict.items(self): 

143 if isinstance(t, tuple) and \ 

144 not m.startswith(_UNDER_): # skip _name 

145 m = m.replace(_UNDER_, _DOT_, 1) 

146 yield m, t 

147 

148 def imports(self): 

149 _D = _Dict() 

150 _add = _D.add 

151 for m, t in self.enums(): 

152 _add(m, m) 

153 for a in t: 

154 a, _, as_ = a.partition(__as__) 

155 if as_: 

156 _add(as_, _DOT_(m, a), *_sub_packages) 

157 else: 

158 _add(a, m) 

159 return _D 

160 

161 

162def _i(*names): 

163 '''(INTERNAL) Intern all C{names}. 

164 ''' 

165 return tuple(map(_intern, names)) if names else _i0 

166 

167 

168_ALL_INIT = _i(_pygeodesy_abspath_, _version_) 

169 

170# __all__ value for most modules, accessible as _ALL_LAZY.<module> 

171_ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY', 

172 albers=_i('AlbersEqualArea', 'AlbersEqualArea2', 'AlbersEqualArea4', 

173 'AlbersEqualAreaCylindrical', 'AlbersEqualAreaNorth', 'AlbersEqualAreaSouth', 

174 'AlbersError', 'Albers7Tuple'), 

175 auxilats=_i(), # module only 

176 azimuthal=_i('AzimuthalError', 'Azimuthal7Tuple', 

177 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney', 

178 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney', 

179 'LambertEqualArea', 'Orthographic', 'Stereographic', 

180 'equidistant', 'gnomonic'), 

181 basics=_i('clips', 'copysign0', 'copytype', 'halfs2', 

182 'int1s', 'isbool', 'isCartesian', 'isclass', 'iscomplex', 'isDEPRECATED', 'isfloat', 

183 'isidentifier', 'isinstanceof', 'isint', 'iskeyword', 'isLatLon', 'islistuple', 

184 'isNvector', 'isodd', 'isscalar', 'issequence', 'isstr', 'issubclassof', 

185 'len2', 'map1', 'map2', 'neg', 'neg_', 

186 'signBit', 'signOf', 'splice', 'str2ub', 'ub2str', 'unsigned0'), 

187 booleans=_i('BooleanFHP', 'BooleanGH', 'LatLonFHP', 'LatLonGH', 

188 'isBoolean'), 

189 cartesianBase=_i(), # module only 

190 clipy=_i('ClipCS4Tuple', 'ClipFHP4Tuple', 'ClipGH4Tuple', 'ClipLB6Tuple', 'ClipSH3Tuple', 

191 'clipCS4', 'clipFHP4', 'clipGH4', 'clipLB6', 'clipSH', 'clipSH3'), 

192 css=_i('CassiniSoldner', 'Css', 'CSSError', 'toCss', 

193 'EasNorAziRk4Tuple', 'EasNorAziRkEqu6Tuple', 'LatLonAziRk4Tuple'), 

194 constants=_i('DIG', 'EPS', 'EPS0', 'EPS02', 'EPS1', 'EPS2', 'EPS4', 'EPS_2', 

195 'INF', 'INT0', 'MANT_DIG', 'MAX', 'MAX_EXP', 'MIN', 'MIN_EXP', 'NAN', 'NEG0', 'NINF', 

196 'PI', 'PI2', 'PI_2', 'PI3', 'PI_3', 'PI3_2', 'PI4', 'PI_4', 

197 'R_FM', 'R_GM', 'R_KM', 'R_M', 'R_MA', 'R_MB', 'R_NM', 'R_QM', 'R_SM', 'R_VM', 

198 'float_', 'float0_', 'isclose', 'isfinite', 'isinf', 'isint0', 

199 'isnan', 'isnear0', 'isnear1', 'isnear90', 'isneg0', 'isninf', 'isnon0', 

200 'remainder'), 

201 datums=_i('Datum', 'Datums', 'Transform', 'Transforms'), 

202# deprecated=_i(), # module only 

203 dms=_i('F_D', 'F_DM', 'F_DMS', 'F_DEG', 'F_MIN', 'F_SEC', 'F_D60', 'F__E', 'F__F', 'F__G', 'F_RAD', 

204 'F_D_', 'F_DM_', 'F_DMS_', 'F_DEG_', 'F_MIN_', 'F_SEC_', 'F_D60_', 'F__E_', 'F__F_', 'F__G_', 'F_RAD_', 

205 'F_D__', 'F_DM__', 'F_DMS__', 'F_DEG__', 'F_MIN__', 'F_SEC__', 'F_D60__', 'F__E__', 'F__F__', 'F__G__', 'F_RAD__', 

206 'S_DEG', 'S_MIN', 'S_SEC', 'S_DMS', 'S_RAD', 'S_SEP', 

207 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint', 

208 'degDMS', 'latDMS', 'latlonDMS', 'latlonDMS_', 'lonDMS', 'normDMS', 

209 'parseDDDMMSS', 'parseDMS', 'parseDMS2', 'parse3llh', 'parseRad', 'precision', 'toDMS'), 

210 ecef=_i('EcefError', 'EcefFarrell21', 'EcefFarrell22', 'EcefKarney', 'EcefMatrix', 

211 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'), 

212 elevations=_i('Elevation2Tuple', 'GeoidHeight2Tuple', 

213 'elevation2', 'geoidHeight2'), 

214 ellipsoidalBase=_i(), # module only 

215 ellipsoidalBaseDI=_i(), # module only 

216 ellipsoidalExact=_i(), # module only 

217 ellipsoidalGeodSolve=_i(), # module only 

218 ellipsoidalKarney=_i(), # module only 

219 ellipsoidalNvector=_i(), # module only 

220 ellipsoidalVincenty=_i('VincentyError',), # nothing else 

221 ellipsoids=_i('a_f2Tuple', 'Circle4Tuple', 'Curvature2Tuple', 

222 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids', 

223 'a_b2e', 'a_b2e2', 'a_b2e22', 'a_b2e32', 'a_b2f', 'a_b2f_', 'a_b2f2', 'a_b2n', 

224 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a', 

225 'e2f', 'e22f', 

226 'f2e2', 'f2e22', 'f2e32', 'f_2f', 'f2f_', 'f2f2', 'f2n', 'n2e2', 'n2f', 'n2f_'), 

227 elliptic=_i('Elliptic', 'EllipticError', 'Elliptic3Tuple'), 

228 epsg=_i('Epsg', 'EPSGError'), 

229 errors=_i('AuxError', 'ClipError', 'CrossError', 'GeodesicError', 'IntersectionError', 

230 'NumPyError', 'LenError', 'LimitError', 'MGRSError', 

231 'ParseError', 'PointsError', 'RangeError', 'RhumbError', 

232 'SciPyError', 'SciPyWarning', 'TRFError', 'TriangleError', 'UnitError', 'VectorError', 

233 'crosserrors', 'exception_chaining', 'isError', 'itemsorted', 

234 'limiterrors', 'rangerrors'), 

235 etm=_i('Etm', 'ETMError', 'ExactTransverseMercator', 

236 'parseETM5', 'toEtm8'), 

237 fmath=_i('Fdot', 'Fhorner', 'Fhypot', 'Fpolynomial', 'Fpowers', 'Fn_rt', 'Fcbrt', 'Fsqrt', 

238 'bqrt', 'cbrt', 'cbrt2', 'euclid', 'euclid_', 

239 'facos1', 'fasin1', 'fatan', 'fatan1', 'fatan2', 'favg', 

240 'fdot', 'fdot3', 'fmean', 'fmean_', 'fhorner', 'fidw', 'fpolynomial', 

241 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder', 

242 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_', 

243 'norm2', 'norm_', 'sqrt0', 'sqrt3', 'sqrt_a', 'zcrt', 'zqrt'), 

244 formy=_i('Radical2Tuple', 

245 'antipode', 'antipode_', 'bearing', 'bearing_', 

246 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_', 

247 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_', 

248 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_', 

249 'excessAbc_', 'excessCagnoli_', 'excessGirard_', 'excessLHuilier_', 

250 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_', 

251 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_', 

252 'hartzell', 'haversine', 'haversine_', 'heightOf', 'heightOrthometric', 'horizon', 'hubeny', 'hubeny_', 

253 'intersection2', 'intersections2', 'isantipode', 'isantipode_', 'isnormal', 'isnormal_', 

254 'latlon2n_xyz', 'normal', 'normal_', 'n_xyz2latlon', 'n_xyz2philam', 

255 'opposing', 'opposing_', 'philam2n_xyz', 'radical2', 'rtp2xyz', 'rtp2xyz_', 

256 'thomas', 'thomas_', 'vincentys', 'vincentys_', 'xyz2rtp', 'xyz2rtp_'), 

257 frechet=_i('Frechet', 'FrechetDegrees', 'FrechetError', 'FrechetRadians', 

258 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert', 

259 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular', 

260 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar', 

261 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas', 

262 'FrechetVincentys', 'Frechet6Tuple', 

263 'frechet_'), 

264 fstats=_i('Fcook', 'Flinear', 'Fwelford'), 

265 fsums=_i('Fsum', 'Fsum2Tuple', 'ResidualError', 

266 'fsum', 'fsum_', 'fsumf_', 'fsum1', 'fsum1_', 'fsum1f_'), 

267 gars=_i('Garef', 'GARSError'), 

268 geodesicw=_i('Geodesic', 'GeodesicLine', 'Geodesic_WGS84'), 

269 geodesicx=_i('gx', 'gxarea', 'gxbases', 'gxline', # modules, see _sub_packages 

270 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

271 geodsolve=_i('GeodesicSolve', 'GeodesicLineSolve', 'GeodSolve12Tuple'), 

272 geohash=_i('Geohash', 'GeohashError', 'Neighbors8Dict', 'Resolutions2Tuple'), 

273 geoids=_i('GeoidError', 'GeoidG2012B', 'GeoidKarney', 'GeoidPGM', 'egmGeoidHeights', 

274 'PGMError', 'GeoidHeight5Tuple'), 

275 hausdorff=_i('Hausdorff', 'HausdorffDegrees', 'HausdorffError', 'HausdorffRadians', 

276 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

277 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

278 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

279 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

280 'HausdorffVincentys', 'Hausdorff6Tuple', 

281 'hausdorff_', 'randomrangenerator'), 

282 heights=_i('HeightCubic', 'HeightError', 

283 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

284 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

285 'HeightIDWeuclidean', 'HeightIDWexact', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 

286 'HeightIDWhaversine', 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 

287 'HeightIDWvincentys', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

288 interns=_interns_a_l_l_, 

289 iters=_i('LatLon2PsxyIter', 'PointsIter', 'points2', 

290 'isNumpy2', 'isPoints2', 'isTuple2', 'iterNumpy2', 'iterNumpy2over'), 

291 karney=_i('Area3Tuple', 'Caps', 'Direct9Tuple', 'GDict', 'Inverse10Tuple', 'Rhumb8Tuple'), 

292 ktm=_i('KTMError', 'KTransverseMercator'), 

293 latlonBase=_i(), # module only 

294 lazily=_i('LazyAttributeError', 'LazyImportError', 'isLazy', 'print_', 'printf'), 

295 lcc=_i('Conic', 'Conics', 'Lcc', 'LCCError', 'toLcc'), 

296 ltp=_i('Attitude', 'AttitudeError', 'ChLV', 'ChLVa', 'ChLVe', 'Frustum', 

297 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

298 ltpTuples=_i('Aer', 'Aer4Tuple', 'Attitude4Tuple', 

299 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

300 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 'Los', 

301 'Ned', 'Ned4Tuple', 'Uvw', 'Uvw3Tuple', 'XyzLocal', 'Xyz4Tuple'), 

302 mgrs=_i('Mgrs', 'parseMGRS', 'toMgrs', 'Mgrs4Tuple', 'Mgrs6Tuple'), 

303 named=_i('callername', 'classname', 'classnaming', 'modulename', 

304 'nameof', 'notImplemented', 'notOverloaded'), 

305 namedTuples=_i('Bearing2Tuple', 'Bounds2Tuple', 'Bounds4Tuple', 

306 'Destination2Tuple', 'Destination3Tuple', 

307 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

308 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

309 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

310 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

311 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

312 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

313 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

314 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

315 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

316 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

317 nvectorBase=_i(_NorthPole_, _SouthPole_), 

318 osgr=_i('Osgr', 'OSGRError', 'parseOSGR', 'toOsgr'), 

319 points=_i('LatLon_', 'LatLon2psxy', 'Numpy2LatLon', 'Shape2Tuple', 'Tuple2LatLon', 

320 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

321 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

322 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

323 props=_i('Property', 'Property_RO', 'property_RO', 'property_doc_', 

324 'deprecated_class', 'deprecated_function', 'deprecated_method', 

325 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

326 resections=_i('Collins5Tuple', 'ResectionError', 'Survey3Tuple', 'Tienstra7Tuple', 

327 'TriAngle5Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

328 'cassini', 'collins5', 'pierlot', 'pierlotx', 'tienstra7', 

329 'snellius3', 'wildberger3', 

330 'triAngle', 'triAngle5', 'triArea', 'triSide', 'triSide2', 'triSide4'), 

331 rhumb=_i(), # module only 

332 rhumb_aux_=_i('RhumbAux', 'RhumbLineAux'), 

333 rhumb_ekx=_i('Rhumb', 'RhumbLine'), 

334 rhumb_solve=_i('RhumbSolve', 'RhumbLineSolve', 'RhumbSolve7Tuple'), 

335 sphericalBase=_i(), # module only 

336 sphericalNvector=_i(), # module only 

337 sphericalTrigonometry=_i(), # module only 

338 simplify=_i('simplify1', 'simplifyRDP', 'simplifyRDPm', 'simplifyRW', 'simplifyVW', 'simplifyVWm'), 

339 solveBase=_i(), # module only 

340 streprs=_i('anstr', 'attrs', 'enstr2', 'fstr', 'fstrzs', 'hstr', 'instr', 

341 'lrstrip', 'pairs', 'reprs', 'strs', 'unstr'), 

342 trf=_i('Helmert7Tuple', 'RefFrame', 'RefFrames', 

343 'date2epoch', 'epoch2date', 'trfXform'), 

344 triaxials=_i('BetaOmega2Tuple', 'BetaOmega3Tuple', 'Jacobi2Tuple', 

345 'JacobiConformal', 'JacobiConformalSpherical', 

346 'Triaxial', 'Triaxial_', 'TriaxialError', 'Triaxials', 'hartzell4'), 

347 units=_i('Band', 'Bearing', 'Bearing_', 'Bool', 

348 'Degrees', 'Degrees_', 'Degrees2', 'Distance', 'Distance_', 'Easting', 'Epoch', 

349 'Feet', 'FIx', 'Float_', 'Height', 'Height_', 'HeightX', 'Int_', 

350 'Lam', 'Lam_', 'Lat', 'Lat_', 'Lon', 'Lon_', 

351 'Meter', 'Meter_', 'Meter2', 'Meter3', 'Northing', 'Number_', 

352 'Phi', 'Phi_', 'Precision_', 'Radians', 'Radians_', 'Radians2', 

353 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

354 unitsBase=_i('Float', 'Int', 'Radius', 'Str'), 

355 ups=_i('Ups', 'UPSError', 'parseUPS5', 'toUps8', 'upsZoneBand5'), 

356 utily=_i('acos1', 'acre2ha', 'acre2m2', 'asin1', 'atan1', 'atan1d', 'atan2b', 'atan2d', 

357 'chain2m', 'circle4', 'cot', 'cot_', 'cotd', 'cotd_', 

358 'degrees', 'degrees90', 'degrees180', 'degrees360', 'degrees2grades', 'degrees2m', 

359# 'degrees2grades as degrees2gons', 

360 'fathom2m', 'ft2m', 'furlong2m', 

361 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

362# 'grades as gons', 'grades400 as gons400', 'grades2degrees as gons2degrees', 'grades2radians as gons2radians', 

363 'km2m', 'm2chain', 'm2degrees', 'm2fathom', 'm2ft', 'm2furlong', 

364 'm2km', 'm2NM', 'm2radians', 'm2SM', 'm2toise', 'm2yard', 'NM2m', 

365 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

366 'sincos2', 'SinCos2', 'sincos2_', 'sincos2d', 'sincos2d_', 'sincostan3', 'SM2m', 

367 'tand', 'tand_', 'tan_2', 'tanPI_2_2', 'toise2m', 'truncate', 

368 'unroll180', 'unrollPI', 

369 'wrap90', 'wrap180', 'wrap360', 'wrapPI_2', 'wrapPI', 'wrapPI2', 'wrap_normal', 

370 'yard2m'), 

371 utm=_i('Utm', 'UTMError', 'parseUTM5', 'toUtm8', 'utmZoneBand5'), 

372 utmups=_i('UtmUps', 'UTMUPSError', 'parseUTMUPS5', 'toUtmUps8', 

373 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

374 utmupsBase=_i(), # module only 

375 vector2d=_i('Circin6Tuple', 'Circum3Tuple', 'Circum4Tuple', 'Meeus2Tuple', 'Radii11Tuple', 'Soddy4Tuple', 

376 'circin6', 'circum3', 'circum4_', 'meeus2', 'radii11', 'soddy4'), 

377 vector3d=_i('Vector3d', 'intersection3d3', 'iscolinearWith', 'nearestOn', 'nearestOn6', 'parse3d', 

378 'trilaterate2d2', 'trilaterate3d2'), 

379 vector3dBase=_i(), # module only 

380 webmercator=_i('Wm', 'WebMercatorError', 'parseWM', 'toWm', 'EasNorRadius3Tuple'), 

381 wgrs=_i('Georef', 'WGRSError'),) 

382 

383_ALL_DEPRECATED = _NamedEnum_RO(_name='_ALL_DEPRECATED', 

384 deprecated=_i('bases', 'datum', 'nvector', # DEPRECATED modules and ... 

385 'rhumbaux', 'rhumbBase', 'rhumbsolve', 'rhumbx'), # ... names 

386 deprecated_bases=_i('LatLonHeightBase', 'points2'), 

387 deprecated_classes=_i('ClipCS3Tuple', 'EasNorExact4Tuple', 'EcefCartesian', 

388 'HeightIDW', 'HeightIDW2', 'HeightIDW3', 

389 'LatLonExact4Tuple', 'NearestOn4Tuple', 'Ned3Tuple', 

390 'RefFrameError', 'Rhumb7Tuple', 'RhumbOrder2Tuple', 

391 'Transform7Tuple', 'TriAngle4Tuple', 'UtmUps4Tuple'), 

392 deprecated_consterns=_i('EPS1_2', 'MANTIS', 'OK'), 

393 deprecated_datum=_i('Curvature2Tuple', 'Datum', 'Ellipsoid', 'Transform', # assert 

394 'Datums', 'Ellipsoids', 'Transforms', 

395 'R_M', 'R_MA', 'R_MB', 'R_KM', 'R_NM', 'R_SM', 'R_FM', 'R_VM'), 

396 deprecated_functions=_i('anStr', 'areaof', 'atand', 'bounds', # most of the DEPRECATED functions, except ... 

397 'clipCS3', 'clipDMS', 'clipStr', 'collins', 'copysign', # ... ellipsoidal, spherical flavors 

398 'decodeEPSG2', 'encodeEPSG', 'enStr2', 'equirectangular3', 

399 'excessAbc', 'excessGirard', 'excessLHuilier', 

400 'false2f', 'falsed2f', 'float0', 'fStr', 'fStrzs', 'hypot3', 

401 'inStr', 'isenclosedby', 'istuplist', 

402 'joined', 'joined_', 'nearestOn3', 'nearestOn4', 

403 'parseUTM', 'perimeterof', 'polygon', 'scalar', 'simplify2', 

404 'tienstra', 'toUtm', 'triAngle4', 'unsign0', 'unStr', 'utmZoneBand2'), 

405 deprecated_nvector=_i('LatLonNvectorBase', 'Nvector', 'sumOf', 'NorthPole', 'SouthPole'),) 

406 

407 

408class _ALL_MODS(object): 

409 '''(INTERNAL) Memoize import of any L{pygeodesy} module. 

410 ''' 

411 def _DOT_(self, name): # PYCHOK no cover 

412 return _DOT_(self.__class__.__name__, name) 

413 

414 def __getattr__(self, name): 

415 '''Get a C{pygeodesy} module or attribute by B{C{name}}. 

416 

417 @arg name: Qualified module or attribute name (C{str}). 

418 

419 @raise ImportError: Importing module B{C{name}} failed. 

420 

421 @raise AttributeError: No attribute named B{C{name}}. 

422 ''' 

423 m = self.getmodule(name) 

424 return m if _tailof(m.__name__) == name else \ 

425 getattr(m, _tailof(name)) 

426 

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

428 t = _EQUALSPACED_(self._DOT_(attr), repr(value)) 

429 raise AttributeError(_COLONSPACE_(t, _immutable_)) 

430 

431 def getattr(self, mod, *attr_dflt): # , parent=_pygeodesy_ 

432 '''Get an attribute of/or a C{pygeodesy} module. 

433 

434 @arg mod: Qualified module name (C{str}). 

435 @arg attr_dflt: Optional attribute name (C{str}) and 

436 optional default value (any C{type}). 

437 

438 @return: The C{pygeodesy} module's attribute value. 

439 

440 @raise ImportError: Importing module B{C{mod}} failed. 

441 

442 @raise AttributeError: No attribute named B{C{attr}}. 

443 ''' 

444 v = self.getmodule(mod) 

445 if attr_dflt: 

446 v = getattr(v, *attr_dflt) 

447 return v 

448 

449 def getmodule(self, name, parent=_pygeodesy_): 

450 '''Get a C{pygeodesy} module. 

451 

452 @arg name: Qualified module name (C{str}). 

453 

454 @return: The C{pygeodesy} module. 

455 

456 @raise ImportError: Importing module B{C{name}} failed. 

457 ''' 

458 if _headof(name) != parent: 

459 name = _DOT_(parent, name) 

460 try: 

461 return _sys.modules[name] 

462 except KeyError: 

463 return import_module(name, parent) 

464 

465 def items(self): # no module named 'items' 

466 '''Yield the modules imported so far. 

467 ''' 

468 for n, m in _sys.modules.items(): 

469 yield n, m 

470 

471_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

472 

473__all__ = _ALL_LAZY.lazily 

474__version__ = '23.12.02' 

475 

476 

477def _ALL_ATTRS(*mods): 

478 '''(INTERNAL) Unravel all exported module attributes. 

479 ''' 

480 t = [] 

481 for mod in mods: 

482 t.extend(map(_attrof, mod)) 

483 return tuple(t) 

484 

485 

486def _ALL_OTHER(*objs): 

487 '''(INTERNAL) Get class and function B{C{objs}} for __all__. 

488 ''' 

489 _interns = _ALL_MODS.interns # from pygeodesy import interns 

490 

491 def _interned(o): # intern'd base name 

492 n = _tailof(_dunder_nameof(o)) 

493 i = NN(_UNDER_, n, _UNDER_) # intern'd 

494 return getattr(_interns, i, n) 

495 

496 return tuple(map(_interned, objs)) # map2 

497 

498 

499if _FOR_DOCS: 

500 _ALL_DOCS = _ALL_OTHER 

501 # (INTERNAL) Only export B{C{objs.__name__}} when making the 

502 # docs to force C{epydoc} to include certain classes, methods, 

503 # functions and other names in the documentation. Using the 

504 # C{epydoc --private ...} command line option tends to include 

505 # too much internal documentation. 

506else: 

507 def _ALL_DOCS(*unused): 

508 return () 

509 

510 

511def _all_deprecates(): 

512 '''(INTERNAL) Build C{dict} of all deprecated imports. 

513 ''' 

514 _D = _ALL_DEPRECATED.imports() # see _all_imports() 

515 _ALL_DEPRECATES.update(_D) 

516 return _D 

517 

518_ALL_DEPRECATES = _Dict() # PYCHOK _ALL_DEPRECATED.imports() 

519 

520 

521def _all_imports(): 

522 '''(INTERNAL) Build C{dict} of all lazy imports. 

523 ''' 

524 # imports naming conventions stored below - [<key>] = <from>: 

525 # import <module> - [<module>] = <module> 

526 # from <module> import <attr> - [<attr>] = <module> 

527 # from pygeodesy import <attr> - [<attr>] = <attr> 

528 # from <module> import <attr> as <name> - [<name>] = <module>.<attr> 

529 _D = _ALL_LAZY.imports() 

530 _ALL_IMPORTS.update(_D) 

531 return _D 

532 

533_ALL_IMPORTS = _Dict() # PYCHOK _ALL_LAZY.imports() 

534 

535 

536def _all_missing2(_all_): 

537 '''(INTERNAL) Get diffs between pygeodesy.__all__ and lazily._all_imports. 

538 ''' 

539 def _diff(one, two): 

540 return tuple(sorted(a for a in one if a not in two)) 

541 

542 _alzy = _Dict((a, a) for a in _ALL_INIT) 

543 _alzy.update(_all_imports()) # without _all_backups! 

544 return ((_DOT_(_lazily_, _all_imports.__name__), _diff(_all_, _alzy)), 

545 (_DOT_(_pygeodesy_, _a_l_l_), _diff(_alzy.keys(), _all_))) 

546 

547 

548def _attrof(attr_as): # .testDeprecated 

549 a, _, as_ = attr_as.partition(__as__) 

550 return as_ or a 

551 

552 

553def _caller3(up): # in .named 

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

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

556 ''' 

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

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

559 f = _sys._getframe(up + 1) 

560 return (f.f_code.co_name, # caller name 

561 _basename(f.f_code.co_filename), # file name 

562 f.f_lineno) # line number 

563 

564 

565def _lazy_attr(unused): # PYCHOK overwritten in _lazy_import 

566 pass 

567 

568 

569# def _lazy_attributes(_name_): 

570# '''(INTERNAL) Return a function to C{B{_name_}.__getattr__(attr)} 

571# on lazily imported modules and sub-modules. 

572# ''' 

573# if _unlazy: 

574# raise AssertionError(_COMMASPACE_(_name_, _not_(_DEPRECATED_))) 

575# 

576# def _getattr(attr, *dflt): 

577# try: # a module name 

578# return _ALL_MODS.getmodule(attr) 

579# except (AttributeError, ImportError): 

580# return _ALL_MODS.getattr(_name_, attr, *dflt) 

581# 

582# return _getattr 

583 

584 

585def _lazy_import2(pack): # MCCABE 14 

586 '''Check for and set up C{lazy import}. 

587 

588 @arg pack: The name of the package (C{str}) performing the imports, 

589 to help resolving relative imports, usually C{__package__}. 

590 

591 @return: 2-Tuple C{(package, getattr)} of the importing package for 

592 easy reference within itself and the callable to be set to 

593 C{package.__getattr__}. 

594 

595 @raise LazyAttributeError: The package, module or attribute name is 

596 invalid or does not exist. 

597 

598 @raise LazyImportError: Lazy import not supported or not enabled or 

599 an import failed. 

600 

601 @note: This is I{Brett Cannon}'s function U{modutil.lazy_import 

602 <https://GitHub.com/brettcannon/modutil/blob/master/modutil.py>} 

603 modified to handle the C{__all__} and C{__dir__} attributes and 

604 call C{importlib.import_module(<module>.<name>, ...)} without 

605 causing a C{ModuleNotFoundError}. 

606 

607 @see: The original U{modutil<https://PyPI.org/project/modutil>}, 

608 U{PEP 562<https://www.Python.org/dev/peps/pep-0562>} and the 

609 U{new way<https://Snarky.Ca/lazy-importing-in-python-3-7/>}. 

610 ''' 

611 if pack != _pygeodesy_ or _unlazy: # new in 3.7 

612 t = _no_(_DOT_(pack, _lazy_import2.__name__)) # PYCHOK no cover 

613 raise LazyImportError(t, txt=_Python_(_sys.version)) 

614 

615 package, parent = _lazy_init2(pack) # _pygeodesy_ 

616 

617 subpacks = set((parent, '__main__', NN) + tuple( 

618 _DOT_(parent, s) for s in _sub_packages)) 

619 imports = _all_imports() 

620 deprecates = _all_deprecates() 

621 

622 def __getattr__(name): # __getattr__ only for Python 3.7+ 

623 # only called once for each undefined pygeodesy attribute 

624 mod = imports.get(name, NN) or deprecates.get(name, NN) 

625 if mod: 

626 # importlib.import_module() implicitly sets sub-modules 

627 # on this module as appropriate for direct imports (see 

628 # note in the _lazy_import2.__doc__ above). 

629 if mod in imports or mod in deprecates: 

630 attr = name if name != _tailof(mod) else NN 

631 else: # must be 'mod.attr as name' 

632 mod, _, attr = mod.rpartition(_DOT_) 

633 if not (attr and (mod in imports or mod in deprecates)): 

634 t = _DOT_(parent, mod) 

635 # <https://GitHub.com/mrJean1/PyGeodesy/issues/76> 

636 raise LazyAttributeError(_no_(_module_), txt=t) 

637 imported = import_module(_DOT_(pack, mod), parent) 

638 t = getattr(imported, _p_a_c_k_a_g_e_, None) 

639 if t not in subpacks: # invalid module package 

640 raise LazyImportError(_DOT_(mod, _p_a_c_k_a_g_e_), t) 

641 if attr: # get the attribute 

642 imported = getattr(imported, attr, MISSING) 

643 if imported is MISSING: # PYCHOK no cover 

644 t = _DOT_(mod, attr) 

645 # <https://GitHub.com/mrJean1/PyGeodesy/issues/76> 

646 raise LazyAttributeError(_no_(_attribute_), txt=t) 

647 

648 elif name in (_a_l_l_,): # XXX '_d_i_r_', '_m_e_m_b_e_r_s_'? 

649 imported = _ALL_INIT + tuple(imports.keys()) 

650 else: # PYCHOK no cover 

651 t = _no_(_module_, _or_, _attribute_) 

652 # <https://GitHub.com/mrJean1/PyGeodesy/issues/76> 

653 raise LazyAttributeError(t, txt=_DOT_(parent, name)) 

654 

655 setattr(package, name, imported) 

656 if isLazy > 1: 

657 t = NN(_lazily_imported__, _DOT_(parent, name)) 

658 if mod and _tailof(mod) != name: 

659 t = NN(t, _from_DOT__, mod) 

660 if isLazy > 2: 

661 try: # see C{_caller3} 

662 _, f, s = _caller3(2) 

663 t = _SPACE_(t, _by_, f, _line_, s) 

664 except ValueError: 

665 pass 

666 printf(t) # XXX print 

667 

668 return imported # __getattr__ 

669 

670 global _lazy_attr 

671 _lazy_attr = __getattr__ 

672 

673 return package, __getattr__ # _lazy_import2 

674 

675 

676# def _lazy_import_all(_name_): 

677# '''(INTERNAL) Return a function mimicking C{from B{__name__} import *}, 

678# of all items, see .deprecated.__init__ 

679# ''' 

680# if _unlazy: 

681# raise AssertionError(_COMMASPACE_(_name_, _not_(_DEPRECATED_))) 

682# 

683# _getattr = _lazy_attributes(_name_) # _name_.__getattr__ 

684# _import_start = _lazy_import_star(_name_, ALL_=_ALL_IMPORTS) 

685# 

686# def _import_all(attr, *dflt): 

687# return _import_star(_name_) if attr == _a_l_l_ else \ 

688# _getattr(attr, *dflt) 

689# 

690# return _import_all 

691 

692 

693def _lazy_import_as(_name_): 

694 '''(INTERNAL) Return a function to C{import B{__name__}.mod as mod} 

695 I{of modules only}, see .deprecated, .rhumb or get an attribute 

696 lazily exported by C{__name__}. 

697 ''' 

698 if _unlazy: 

699 return None 

700 

701 def _import_as(mod): 

702 try: 

703 return _ALL_MODS.getmodule(_DOT_(_name_, mod)) 

704 except ImportError: 

705 return _lazy_attr(mod) 

706 

707 return _import_as 

708 

709 

710# def _lazy_import_star(_name_, ALL_=_ALL_DEPRECATES): 

711# '''(INTERNAL) Return a function to mimick C{from B{__name__} import *}, 

712# of all DEPRECATED items, see .deprecated, .testDeprecated 

713# ''' 

714# if _unlazy: 

715# raise AssertionError(_COMMASPACE_(_name_, _not_(_DEPRECATED_))) 

716# 

717# def _import_star(_into_): 

718# '''Do C{from B{__name__} import *} inside module C{B{__into__}}. 

719# ''' 

720# d = dict() 

721# nm = _tailof(_name_) 

722# _g = _ALL_MODS.getattr # pygeodesy.__getattr__ 

723# _h = _headof 

724# for a, m in ALL_.items(): 

725# if _h(m) == nm: 

726# try: 

727# d[a] = _g(m, a) 

728# except (AttributeError, ImportError): 

729# pass 

730# _sys.modules[_into_].__dict__.update(d) 

731# return d.keys() # imported names 

732# 

733# return _import_star 

734 

735 

736# def _lazy_subs(_name_, force=_FOR_DOCS, over=False): 

737# '''(INTERNAL) Return the names of a package's sub-packages and 

738# update the package's C{__dict__} accordingly. 

739# ''' 

740# sm = dict() 

741# if force and _name_ != '__main__': 

742# nm = _tailof(_name_) 

743# _a = _ALL_MODS.getattr 

744# _m = _ALL_MODS.getmodule 

745# d = _a(_name_, '__dict__', {}) 

746# for n in _a(_name_, _a_l_l_, ()): 

747# try: # n is a class name, get its mod name 

748# m = _a(_name_, n).__module__ 

749# n, s = m.split(_DOT_)[-2:] 

750# if n == nm and s not in sm: 

751# # like import m as s 

752# m = _m(m) 

753# sm[s] = m if over else d.get(s, m) 

754# except (AttributeError, ImportError, ValueError) as x: 

755# pass 

756# d.update(sm) 

757# 

758# return _ALL_OTHER(*sm.values()) 

759 

760 

761def _lazy_init2(pack): 

762 '''(INTERNAL) Initialize lazy import and set globals C{isLazy} and C{_unLazy0}. 

763 

764 @arg pack: The name of the package (C{str}) performing the imports, 

765 to help resolving relative imports, usually C{__package__}. 

766 

767 @return: 2-Tuple C{(package, parent)} with the importing C{package} 

768 for easy reference within itself and its name aka the 

769 C{parent}, same as B{C{pack}}. 

770 

771 @raise LazyImportError: Lazy import not supported or not enabled, 

772 an import failed or the package name is 

773 invalid or does not exist. 

774 

775 @note: Global C{isLazy} is set accordingly. 

776 ''' 

777 global isLazy, _unLazy0 

778 

779 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

780 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

781 isLazy = 1 # ... but only by default on 3.7 

782 else: 

783 z = z.strip() # like PYTHONVERBOSE et.al. 

784 isLazy = int(z) if z.isdigit() else (1 if z else 0) 

785 

786 _unLazy0 = _unlazy or not isLazy # pre-3.7 or w/o lazy import 

787 

788 if isLazy < 1: # not enabled 

789 raise LazyImportError(_PYGEODESY_LAZY_IMPORT_, repr(z), txt=_not_(_enabled_)) 

790 if _getenv('PYTHONVERBOSE', None): # PYCHOK no cover 

791 isLazy += 1 

792 

793 try: # to initialize in Python 3+ 

794 package = import_module(pack) 

795 parent = package.__spec__.parent # __spec__ only in Python 3.7+ 

796 if parent != pack: # assert 

797 t = _COMMASPACE_(parent, _not_(pack)) # PYCHOK no cover 

798 raise AttributeError(_EQUALSPACED_('parent', t)) 

799 

800 except (AttributeError, ImportError) as x: 

801 isLazy = False # failed 

802 raise LazyImportError(_lazy_init2.__name__, pack, cause=x) 

803 

804 return package, parent 

805 

806 

807def _pairs(*args, **kwds): # in .errors, .ktm 

808 # from pygeodesy.streprs import pairs 

809 return _ALL_MODS.streprs.pairs(*args, **kwds) 

810 

811 

812def print_(*args, **nl_nt_prefix_end_file_flush_sep): # PYCHOK no cover 

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

814 

815 @arg args: Arguments to be converted to C{str} and joined by B{C{sep}} 

816 (any C{type}, all positional). 

817 @kwarg nl_nt_prefix_end_file_flush_sep: Keyword arguments C{B{nl}=0} 

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

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

820 to be inserted before the formatted text (C{str}) and Python 

821 3+ C{print} keyword arguments C{B{end}}, C{B{sep}}, C{B{file}} 

822 and C{B{flush}}. 

823 

824 @return: Number of bytes written. 

825 ''' 

826 return printf(NN, *args, **nl_nt_prefix_end_file_flush_sep) 

827 

828 

829def printf(fmt, *args, **nl_nt_prefix_end_file_flush_sep_kwds): 

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

831 

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

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

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

835 @kwarg nl_nt_prefix_end_file_flush_sep_kwds: Keyword arguments C{B{nl}=0} 

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

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

838 be inserted before the formatted text (C{str}) and Python 3+ 

839 C{print} keyword arguments C{B{end}}, C{B{sep}}, C{B{file}} and 

840 C{B{flush}}. Any remaining C{B{kwds}} are U{printf-style 

841 <https://Docs.Python.org/3/library/stdtypes.html#printf-style-string-formatting>} 

842 keyword arguments to be formatted, I{iff no B{C{args}} are present}. 

843 

844 @return: Number of bytes written. 

845 ''' 

846 b, e, s, f, fl, p, kwds = _xprint7(**nl_nt_prefix_end_file_flush_sep_kwds) 

847 try: 

848 if args: 

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

850 elif kwds: # PYCHOK no cover 

851 t = (fmt % kwds) if fmt else s.join(_pairs(kwds, prec=p)) 

852 else: # PYCHOK no cover 

853 t = fmt 

854 except Exception as x: 

855 _E, s = _ALL_MODS.errors._xError2(x) 

856 unstr = _ALL_MODS.streprs.unstr 

857 t = unstr(printf, fmt, *args, **nl_nt_prefix_end_file_flush_sep_kwds) 

858 raise _E(s, txt=t, cause=x) 

859 try: 

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

861 except UnicodeEncodeError: # XXX only Windows 

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

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

864 if fl: # PYCHOK no cover 

865 f.flush() 

866 return n 

867 

868 

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

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

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

872 ''' 

873 if nl > 0: 

874 prefix = NN(_NL_ * nl, prefix) 

875 if nt > 0: 

876 end = NN(end, _NL_ * nt) 

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

878 

879 

880# del _i, _i0, _intern 

881 

882if __name__ == '__main__': 

883 

884 from timeit import timeit 

885 

886 def t1(): 

887 from pygeodesy.trf import RefFrame 

888 return RefFrame 

889 

890 def t2(): 

891 return _ALL_MODS.trf.RefFrame 

892 

893 assert t1() is t2() # prime each 

894 

895 t1 = timeit(t1, number=1000000) 

896 t2 = timeit(t2, number=1000000) 

897 v = _Python_(_sys.version) 

898 printf('%.8f import vs %.8f _ALL_MODS: %.3fX, %s', t1, t2, t2 / t1, v) 

899 del t1, t2, v 

900 

901# python3.12 -m pygeodesy.lazily 

902# 0.13352763 import vs 0.70804508 _ALL_MODS: 5.303X, Python 3.12.0 

903 

904# % python3.11 -W ignore -m pygeodesy.lazily 

905# 0.37998008 import vs 0.79537812 _ALL_MODS: 2.093X, Python 3.11.5 

906 

907# % python3.10 -W ignore -m pygeodesy.lazily 

908# 0.39046367 import vs 0.90492925 _ALL_MODS: 2.318X, Python 3.10.8 

909 

910# % python2 -m pygeodesy.lazily 

911# 1.17563510 import vs 2.02626395 _ALL_MODS: 1.724X, Python 2.7.18 

912 

913# **) MIT License 

914# 

915# Copyright (C) 2018-2023 -- mrJean1 at Gmail -- All Rights Reserved. 

916# 

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

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

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

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

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

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

923# 

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

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

926# 

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

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

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

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

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

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

933# OTHER DEALINGS IN THE SOFTWARE.