Coverage for pygeodesy/lazily.py: 89%

152 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-08-28 15:52 -0400

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''' 

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

30 _areaOf_, _attribute_, _by_, _COLONSPACE_, \ 

31 _COMMASPACE_, _doesn_t_exist_, _DOT_, _enabled_, \ 

32 _EQUALSPACED_, _from_, _immutable_, _isclockwise_, \ 

33 _ispolar_, _line_, _module_, _NL_, _no_, _not_, _or_, \ 

34 _pygeodesy_, _pygeodesy_abspath_, _Python_, _QUOTE1_, \ 

35 _QUOTE2_, _SPACE_, _UNDER_, _version_, _dunder_nameof 

36 

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

38from os.path import basename as _basename 

39import sys as _sys # in .basics._sizeof 

40 

41_a_l_l_ = '__all__' 

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

43_from_DOT__ = _SPACE_(NN, _from_, _DOT_) 

44_imports_ = 'imports' 

45_lazily_ = 'lazily' 

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

47_p_a_c_k_a_g_e_ = '__package__' 

48_PYGEODESY_LAZY_IMPORT_ = 'PYGEODESY_LAZY_IMPORT' 

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

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

51_sub_packages = 'auxilats', 'deprecated', 'geodesicx' # in make._dist 

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

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

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

55 

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

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

58 

59try: 

60 from importlib import import_module 

61except ImportError: # Python 2.6- 

62 

63 def import_module(name, package=None): 

64 raise LazyImportError(name=name, package=package, 

65 txt=_no_(import_module.__name__)) 

66 

67 

68class LazyAttributeError(AttributeError): 

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

70 ''' 

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

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

73 

74 

75class LazyImportError(ImportError): 

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

77 ''' 

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

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

80 

81 

82class _Dict(dict): 

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

84 ''' 

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

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

87 

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

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

90 ''' 

91 if key in self: 

92 val = self[key] # duplicate OK 

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

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

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

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

97 raise AssertionError(t) 

98 else: 

99 self[key] = value 

100 

101 

102class _NamedEnum_RO(dict): 

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

104 ''' 

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

106 

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

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

109 

110 def __getattr__(self, attr): 

111 try: 

112 return self[attr] 

113 except KeyError: 

114 t = self._DOT_(attr) 

115 raise LazyAttributeError(t, txt=_doesn_t_exist_) 

116 

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

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

119 raise LazyAttributeError(t, txt=_immutable_) 

120 

121 def enums(self): 

122 for k, v in dict.items(self): 

123 if not k.startswith(_UNDER_): # skip _name 

124 yield k, v 

125 

126 

127_ALL_INIT = _pygeodesy_abspath_, _version_ 

128 

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

130_ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY', 

131 albers=('AlbersEqualArea', 'AlbersEqualArea2', 'AlbersEqualArea4', 

132 'AlbersEqualAreaCylindrical', 'AlbersEqualAreaNorth', 'AlbersEqualAreaSouth', 

133 'AlbersError', 'Albers7Tuple'), 

134 auxilats=(), # no modules: 'auxAngle', 'auxDLat', 'auxDST', 'auxily', 'auxLat' 

135 # and no classes: 'Aux', 'AuxAngle', 'AuxBeta', 'AuxChi', 'AuxDLat', 'AuxDST', 

136 # 'AuxLat', 'AuxMu', 'AuxPhi', 'AuxTheta', 'AuxXi' 

137 azimuthal=('AzimuthalError', 'Azimuthal7Tuple', 

138 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney', 

139 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney', 

140 'LambertEqualArea', 'Orthographic', 'Stereographic', 

141 'equidistant', 'gnomonic'), 

142 basics=('clips', 'copysign0', 'copytype', 'halfs2', 

143 'isbool', 'isclass', 'iscomplex', 'isfloat', 

144 'isidentifier', 'isinstanceof', 'isint', 'iskeyword', 'islistuple', 'isodd', 

145 'isscalar', 'issequence', 'isstr', 'issubclassof', 

146 'len2', 'map1', 'map2', 'neg', 'neg_', 

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

148 booleans=('BooleanFHP', 'BooleanGH', 'LatLonFHP', 'LatLonGH', 

149 'isBoolean'), 

150 cartesianBase=(), # module only 

151 clipy=('ClipCS4Tuple', 'ClipFHP4Tuple', 'ClipGH4Tuple', 'ClipLB6Tuple', 'ClipSH3Tuple', 

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

153 css=('CassiniSoldner', 'Css', 'CSSError', 'toCss', 

154 'EasNorAziRk4Tuple', 'EasNorAziRkEqu6Tuple', 'LatLonAziRk4Tuple'), 

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

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

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

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

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

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

161 'remainder'), 

162 datums=('Datum', 'Datums', 'Transform', 'Transforms'), 

163 deprecated=('EPS1_2', 'MANTIS', 'OK', # DEPRECATED constants 

164 'bases', 'datum', 'nvector', # DEPRECATED modules, see _sub_packages 

165 'ClipCS3Tuple', 'EcefCartesian', 'EasNorExact4Tuple', 'HeightIDW', 'HeightIDW2', 'HeightIDW3', # DEPRECATED classes 

166 'LatLonExact4Tuple', 'Ned3Tuple', 'RefFrameError', 'Rhumb7Tuple', 'Transform7Tuple', 'UtmUps4Tuple', 

167 'anStr', 'areaof', 'bounds', 'clipCS3', 'clipDMS', 'clipStr', 'collins', # most of the DEPRECATED functions, ... 

168 'decodeEPSG2', 'encodeEPSG', 'equirectangular3', 'enStr2', # ... except ellipsoidal, spherical flavors 

169 'excessAbc', 'excessGirard', 'excessLHuilier', 

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

171 'inStr', 'isDEPRECATED', 'isenclosedby', 'istuplist', 

172 'joined', 'joined_', 'nearestOn3', 'nearestOn4', 'parseUTM', 'perimeterof', 'polygon', 

173 'scalar', 'simplify2', 'tienstra', 'toUtm', 'unsign0', 'unStr', 'utmZoneBand2'), 

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

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

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

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

178 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint', 

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

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

181 ecef=('EcefError', 'EcefFarrell21', 'EcefFarrell22', 'EcefKarney', 'EcefMatrix', 

182 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'), 

183 elevations=('Elevation2Tuple', 'GeoidHeight2Tuple', 

184 'elevation2', 'geoidHeight2'), 

185 ellipsoidalBase=(), # module only 

186 ellipsoidalBaseDI=(), # module only 

187 ellipsoidalExact=(), # module only 

188 ellipsoidalGeodSolve=(), # module only 

189 ellipsoidalKarney=(), # module only 

190 ellipsoidalNvector=(), # module only 

191 ellipsoidalVincenty=('VincentyError',), # nothing else 

192 ellipsoids=('a_f2Tuple', 'Circle4Tuple', 'Curvature2Tuple', 

193 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids', 

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

195 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a', 

196 'e2f', 'e22f', 

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

198 elliptic=('Elliptic', 'EllipticError', 'Elliptic3Tuple'), 

199 epsg=('Epsg', 'EPSGError'), 

200 errors=('AuxError', 'ClipError', 'CrossError', 'GeodesicError', 'IntersectionError', 

201 'NumPyError', 'LenError', 'LimitError', 'MGRSError', 

202 'ParseError', 'PointsError', 'RangeError', 'RhumbError', 

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

204 'crosserrors', 'exception_chaining', 'isError', 'itemsorted', 

205 'limiterrors', 'rangerrors'), 

206 etm=('Etm', 'ETMError', 'ExactTransverseMercator', 

207 'parseETM5', 'toEtm8'), 

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

209 'cbrt', 'cbrt2', 'euclid', 'euclid_', 

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

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

212 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder', 

213 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_', 

214 'norm2', 'norm_', 'sqrt0', 'sqrt3', 'sqrt_a'), 

215 formy=('Radical2Tuple', 

216 'antipode', 'antipode_', 'bearing', 'bearing_', 

217 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_', 

218 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_', 

219 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_', 

220 'excessAbc_', 'excessCagnoli_', 'excessGirard_', 'excessLHuilier_', 

221 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_', 

222 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_', 

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

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

225 'latlon2n_xyz', 'normal', 'normal_', 'n_xyz2latlon', 'n_xyz2philam', 

226 'opposing', 'opposing_', 'philam2n_xyz', 

227 'radical2', 'thomas', 'thomas_', 'vincentys', 'vincentys_'), 

228 frechet=('Frechet', 'FrechetDegrees', 'FrechetError', 'FrechetRadians', 

229 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert', 

230 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular', 

231 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar', 

232 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas', 

233 'FrechetVincentys', 'Frechet6Tuple', 

234 'frechet_'), 

235 fstats=('Fcook', 'Flinear', 'Fwelford'), 

236 fsums=('Fsum', 'Fsum2Tuple', 'ResidualError', 

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

238 gars=('Garef', 'GARSError'), 

239 geodesicw=('Geodesic', 'GeodesicLine', 'Geodesic_WGS84'), 

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

241 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

242 geodsolve=('GeodesicSolve', 'GeodesicLineSolve', 'GeodSolve12Tuple'), 

243 geohash=('Geohash', 'GeohashError', 'Neighbors8Dict', 'Resolutions2Tuple'), 

244 geoids=('GeoidError', 'GeoidG2012B', 'GeoidKarney', 'GeoidPGM', 'egmGeoidHeights', 

245 'PGMError', 'GeoidHeight5Tuple'), 

246 hausdorff=('Hausdorff', 'HausdorffDegrees', 'HausdorffError', 'HausdorffRadians', 

247 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

248 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

249 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

250 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

251 'HausdorffVincentys', 'Hausdorff6Tuple', 

252 'hausdorff_', 'randomrangenerator'), 

253 heights=('HeightCubic', 'HeightError', 

254 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

255 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

256 'HeightIDWeuclidean', 'HeightIDWexact', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 

257 'HeightIDWhaversine', 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 

258 'HeightIDWvincentys', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

259 interns=_interns_a_l_l_, 

260 iters=('LatLon2PsxyIter', 'PointsIter', 'points2', 

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

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

263 ktm=('KTMError', 'KTransverseMercator'), 

264 latlonBase=(), # module only 

265 lazily=('LazyAttributeError', 'LazyImportError', 'isLazy', 'print_', 'printf'), 

266 lcc=('Conic', 'Conics', 'Lcc', 'LCCError', 'toLcc'), 

267 ltp=('Attitude', 'AttitudeError', 'ChLV', 'ChLVa', 'ChLVe', 'Frustum', 

268 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

269 ltpTuples=('Aer', 'Aer4Tuple', 'Attitude4Tuple', 

270 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

271 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 

272 'Ned', 'Ned4Tuple', 'XyzLocal', 'Xyz4Tuple'), 

273 mgrs=('Mgrs', 'parseMGRS', 'toMgrs', 'Mgrs4Tuple', 'Mgrs6Tuple'), 

274 named=('callername', 'classname', 'classnaming', 'modulename', 

275 'nameof', 'notImplemented', 'notOverloaded'), 

276 namedTuples=('Bearing2Tuple', 'Bounds2Tuple', 'Bounds4Tuple', 

277 'Destination2Tuple', 'Destination3Tuple', 

278 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

279 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

280 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

281 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

282 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

283 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn4Tuple', 

284 'NearestOn5Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

285 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

286 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

287 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

288 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

289 nvectorBase=(), # module only 

290 osgr=('Osgr', 'OSGRError', 'parseOSGR', 'toOsgr'), 

291 points=('LatLon_', 'LatLon2psxy', 'Numpy2LatLon', 'Shape2Tuple', 'Tuple2LatLon', 

292 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

293 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

294 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

295 props=('Property', 'Property_RO', 'property_RO', 'property_doc_', 

296 'deprecated_class', 'deprecated_function', 'deprecated_method', 

297 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

298 resections=('Collins5Tuple', 'ResectionError', 'Survey3Tuple', 'Tienstra7Tuple', 

299 'TriAngle4Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

300 'cassini', 'collins5', 'pierlot', 'pierlotx', 'tienstra7', 

301 'snellius3', 'wildberger3', 

302 'triAngle', 'triAngle4', 'triSide', 'triSide2', 'triSide4'), 

303 rhumbaux=('RhumbAux', 'RhumbLineAux'), 

304 rhumbBase=(), # module only 

305 rhumbsolve=('RhumbSolve', 'RhumbLineSolve', 'RhumbSolve7Tuple'), 

306 rhumbx=('Rhumb', 'RhumbLine', 'RhumbOrder2Tuple',), 

307 sphericalBase=(), # module only 

308 sphericalNvector=(), # module only 

309 sphericalTrigonometry=(), # module only 

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

311 solveBase=(), # module only 

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

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

314 trf=('Helmert7Tuple', 'RefFrame', 'RefFrames', 

315 'date2epoch', 'epoch2date', 'trfXform'), 

316 triaxials=('BetaOmega2Tuple', 'BetaOmega3Tuple', 'Jacobi2Tuple', 

317 'JacobiConformal', 'JacobiConformalSpherical', 

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

319 units=('Band', 'Bearing', 'Bearing_', 'Bool', 

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

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

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

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

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

325 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

326 unitsBase=('Float', 'Int', 'Radius', 'Str'), 

327 ups=('Ups', 'UPSError', 'parseUPS5', 'toUps8', 'upsZoneBand5'), 

328 utily=('acos1', 'acre2ha', 'acre2m2', 'asin1', 'atand', 'atan2b', 'atan2d', 

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

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

331# 'degrees2grades as degrees2gons', 

332 'fathom2m', 'ft2m', 'furlong2m', 

333 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

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

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

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

337 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

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

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

340 'unroll180', 'unrollPI', 

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

342 'yard2m'), 

343 utm=('Utm', 'UTMError', 'parseUTM5', 'toUtm8', 'utmZoneBand5'), 

344 utmups=('UtmUps', 'UTMUPSError', 'parseUTMUPS5', 'toUtmUps8', 

345 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

346 utmupsBase=(), # module only 

347 vector2d=('Circin6Tuple', 'Circum3Tuple', 'Circum4Tuple', 'Meeus2Tuple', 'Radii11Tuple', 'Soddy4Tuple', 

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

349 vector3d=('Vector3d', 'intersection3d3', 'iscolinearWith', 'nearestOn', 'nearestOn6', 'parse3d', 

350 'trilaterate2d2', 'trilaterate3d2'), 

351 vector3dBase=(), # module only 

352 webmercator=('Wm', 'WebMercatorError', 'parseWM', 'toWm', 'EasNorRadius3Tuple'), 

353 wgrs=('Georef', 'WGRSError')) 

354 

355# DEPRECATED __all__ names overloading those in _ALL_LAZY.deprecated where 

356# the new name is fully backward compatible in signature and return value 

357_ALL_OVERRIDDEN = _NamedEnum_RO(_name='_ALL_OVERRIDING', # all DEPRECATED 

358 basics=('clips as clipStr',), 

359 fmath=('hypot_ as hypot3',), 

360 formy=('points2 as polygon',), 

361 heights=('HeightIDWequirectangular as HeightIDW2', 'HeightIDWeuclidean as HeightIDW', 

362 'HeightIDWhaversine as HeightIDW3'), 

363 points=('areaOf as areaof', 

364 'isenclosedBy as isenclosedby', 'perimeterOf as perimeterof'), 

365 simplify=('simplifyRW as simplify2',), 

366 streprs=('anstr as anStr', 'enstr2 as enStr2', 'fstr as fStr', 'fstrzs as fStrzs', 

367 'instr as inStr', 'unstr as unStr')) 

368 

369 

370class _ALL_MODS(object): 

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

372 ''' 

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

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

375 

376 def __getattr__(self, name): 

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

378 

379 @arg name: Unqualified module or attribute name (C{str}). 

380 ''' 

381 try: 

382 return self.getmodule(name if name == _pygeodesy_ else 

383 _DOT_(_pygeodesy_, name)) 

384 except ImportError as x: 

385 raise LazyImportError(str(x), txt=_doesn_t_exist_, cause=x) 

386 

387 def __setattr__(self, name, value): # PYCHOK no cover 

388 t = _EQUALSPACED_(self._DOT_(name), repr(value)) 

389 raise LazyAttributeError(t, txt=_immutable_) 

390 

391 def getattr(self, module_name, name, *dflt): 

392 '''Get an attribute of a C{pygeodesy} module. 

393 

394 @arg module_name: Un- or qualified module name (C{str}). 

395 @arg name: Attribute name (C{str}). 

396 

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

398 

399 @raise AttributeError: No attribute with that B{C{name}}. 

400 

401 @raise ImportError: Importing B{C{module_name}} failed. 

402 ''' 

403 n = module_name 

404 if n.split(_DOT_, 1)[0] != _pygeodesy_: 

405 n = _DOT_(_pygeodesy_, n) 

406 m = self.getmodule(n) 

407 try: 

408 return m if name in (None, NN) else ( 

409 getattr(m, name, *dflt) if dflt else getattr(m, name)) 

410 except (AttributeError, TypeError) as x: 

411 raise LazyAttributeError(_DOT_(m, n), cause=x) 

412 

413 def getmodule(self, name): 

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

415 

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

417 

418 @return: The C{pygeodesy} module. 

419 

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

421 ''' 

422 try: 

423 return _sys.modules[name] 

424 except KeyError: 

425 return import_module(name, _pygeodesy_) 

426 

427# def _imported(self, name, module): # in _lazy_import2 below 

428# try: 

429# if name not in _sys.modules: 

430# _sys.modules[name] = module 

431# except (AttributeError, KeyError): 

432# pass 

433# return module 

434 

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

436 '''Yield the modules imported so far. 

437 ''' 

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

439 yield n, m 

440 

441_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

442 

443__all__ = _ALL_LAZY.lazily 

444__version__ = '23.08.22' 

445 

446 

447def _ALL_OTHER(*objs): 

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

449 ''' 

450 _interns = _ALL_MODS.interns # from pygeodesy import interns 

451 

452 def _interned(o): # get base name 

453 n = _dunder_nameof(o).rsplit(_DOT_, 1)[-1] 

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

455 return getattr(_interns, i, n) 

456 

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

458 

459 

460if _FOR_DOCS: 

461 _ALL_DOCS = _ALL_OTHER 

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

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

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

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

466 # too much internal documentation. 

467else: 

468 def _ALL_DOCS(*unused): 

469 return () 

470 

471 

472def _all_imports(**more): 

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

474 ''' 

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

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

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

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

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

480 imports = _Dict() 

481 _add = imports.add 

482 

483 for ALL in (_ALL_LAZY, _ALL_OVERRIDDEN, more): 

484 for mod, attrs in ALL.items(): 

485 if isinstance(attrs, tuple) and not mod.startswith(_UNDER_): 

486 _add(mod, mod) 

487 for attr in attrs: 

488 attr, _, as_attr = attr.partition(' as ') 

489 if as_attr: 

490 _add(as_attr, _DOT_(mod, attr), *_sub_packages) 

491 else: 

492 _add(attr, mod) 

493 return imports 

494 

495 

496def _all_missing2(_all_): 

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

498 ''' 

499 def _diff(one, two): 

500 return _COMMASPACE_.join(a for a in one if a not in two) 

501 

502 _alzy = _all_imports(**_NamedEnum_RO((a, ()) for a in _ALL_INIT)) 

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

504 (_DOT_(_pygeodesy_, _a_l_l_), _diff(_alzy, _all_))) 

505 

506 

507def _caller3(up): # in .named 

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

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

510 ''' 

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

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

513 f = _sys._getframe(up + 1) 

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

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

516 f.f_lineno) # line number 

517 

518 

519def _lazy_import2(package_name): # MCCABE 14 

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

521 

522 @arg package_name: The name of the package (C{str}) performing 

523 the imports, to help facilitate resolving 

524 relative imports, usually C{__package__}. 

525 

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

527 for easy reference within itself and the callable to 

528 be set to `__getattr__`. 

529 

530 @raise LazyAttributeError: The package, module or attribute 

531 name is invalid or does not exist. 

532 

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

534 or an import failed. 

535 

536 @note: This is the original function U{modutil.lazy_import 

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

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

539 and call C{importlib.import_module(<module>.<name>, ...)} 

540 without causing a C{ModuleNotFoundError}. 

541 

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

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

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

545 ''' 

546 if package_name != _pygeodesy_ or _sys_version_info2 < (3, 7): # not supported before 3.7 

547 t = _no_(_DOT_(package_name, _lazy_import2.__name__)) # PYCHOK no cover 

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

549 

550 package, parent = _lazy_init2(_pygeodesy_) 

551 

552 packages = (parent, '__main__', NN) + tuple( 

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

554 imports = _all_imports() 

555 

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

557 # only called once for each undefined pygeodesy attribute 

558 if name in imports: 

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

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

561 # note in the _lazy_import2.__doc__ above). 

562 mod, _, attr = imports[name].partition(_DOT_) 

563 if mod not in imports: 

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

565 raise LazyAttributeError(_no_(_module_), txt=_DOT_(parent, mod)) 

566 imported = import_module(_DOT_(_pygeodesy_, mod), parent) 

567 pkg = getattr(imported, _p_a_c_k_a_g_e_, None) 

568 if pkg not in packages: # invalid package 

569 raise LazyImportError(_DOT_(mod, _p_a_c_k_a_g_e_), pkg) 

570 # _ALL_MODS._imported(mod, imported) 

571 # import the module or module attribute 

572 if attr: 

573 imported = getattr(imported, attr, MISSING) 

574 elif name != mod: 

575 imported = getattr(imported, name, MISSING) 

576 if imported is MISSING: # PYCHOK no cover 

577 t = _DOT_(mod, attr or name) 

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

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

580 

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

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

583 mod = NN 

584 else: # PYCHOK no cover 

585 t = _no_(_module_, _or_, _attribute_) 

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

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

588 

589 setattr(package, name, imported) 

590 if isLazy > 1: 

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

592 if mod and mod != name: 

593 t = NN(t, _from_DOT__, mod) 

594 if isLazy > 2: 

595 try: # see C{_caller3} 

596 _, f, s = _caller3(2) 

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

598 except ValueError: 

599 pass 

600 printf(t) # XXX print 

601 

602 return imported # __getattr__ 

603 

604 return package, __getattr__ # _lazy_import2 

605 

606 

607def _lazy_init2(package_name): 

608 '''(INTERNAL) Try to initialize lazy import. 

609 

610 @arg package_name: The name of the package (C{str}) performing 

611 the imports, to help facilitate resolving 

612 relative imports, usually C{__package__}. 

613 

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

615 for easy reference within itself and its name aka the 

616 C{parent}, same as B{C{package_name}}. 

617 

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

619 an import failed or the package name is 

620 invalid or does not exist. 

621 

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

623 ''' 

624 global isLazy 

625 

626 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

627 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

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

629 else: 

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

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

632 if isLazy < 1: # not enabled 

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

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

635 isLazy += 1 

636 

637 try: # to initialize in Python 3+ 

638 package = import_module(package_name) 

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

640 if parent != package_name: # assert 

641 t = _COMMASPACE_(parent, _not_(package_name)) # PYCHOK no cover 

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

643 

644 except (AttributeError, ImportError) as x: 

645 isLazy = False # failed 

646 raise LazyImportError(_lazy_init2.__name__, package_name, cause=x) 

647 

648 return package, parent 

649 

650 

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

652 # from pygeodesy.streprs import pairs 

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

654 

655 

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

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

658 

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

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

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

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

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

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

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

666 and C{B{flush}}. 

667 

668 @return: Number of bytes written. 

669 ''' 

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

671 

672 

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

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

675 

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

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

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

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

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

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

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

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

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

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

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

687 

688 @return: Number of bytes written. 

689 ''' 

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

691 try: 

692 if args: 

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

694 elif kwds: # PYCHOK no cover 

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

696 else: # PYCHOK no cover 

697 t = fmt 

698 except Exception as x: 

699 _E, t = _ALL_MODS.errors._xError2(x) 

700 unstr = _ALL_MODS.streprs.unstr 

701 raise _E(t, txt=unstr(printf, fmt, *args, ** 

702 nl_nt_prefix_end_file_flush_sep_kwds)) 

703 try: 

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

705 except UnicodeEncodeError: # XXX only Windows 

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

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

708 if fl: 

709 f.flush() 

710 return n 

711 

712 

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

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

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

716 ''' 

717 if nl > 0: 

718 prefix = NN(_NL_ * nl, prefix) 

719 if nt > 0: 

720 end = NN(end, _NL_ * nt) 

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

722 

723 

724if __name__ == '__main__': 

725 

726 from timeit import timeit 

727 

728 def t1(): 

729 from pygeodesy.trf import RefFrame 

730 return RefFrame 

731 

732 def t2(): 

733 return _ALL_MODS.trf.RefFrame 

734 

735 t1(); t2() # PYCHOK prime each 

736 

737 t1 = timeit(t1, number=1000000) 

738 t2 = timeit(t2, number=1000000) 

739 v = _Python_(_sys.version) 

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

741 

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

743# 0.31956017 import vs 0.52837638 _ALL_MODS: 1.653X, Python 3.11.0rc1 

744 

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

746# 0.31828208 import vs 0.58981700 _ALL_MODS: 1.853X, Python 3.10.5 

747 

748# % python2 -m pygeodesy.lazily 

749# 1.19996715 import vs 1.39310884 _ALL_MODS: 1.161X, Python 2.7.18 

750 

751# **) MIT License 

752# 

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

754# 

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

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

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

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

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

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

761# 

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

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

764# 

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

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

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

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

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

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

771# OTHER DEALINGS IN THE SOFTWARE.