Coverage for pygeodesy/lazily.py: 90%

153 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-07-12 13:40 -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_, \ 

34 _or_, _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_ = 'pygeodesy' 

49_PYGEODESY_LAZY_IMPORT_ = 'PYGEODESY_LAZY_IMPORT' 

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

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

52_sub_packages = 'deprecated', 'geodesicx' 

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

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

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

56 

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

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

59 

60try: 

61 from importlib import import_module 

62except ImportError: # Python 2.6- 

63 

64 def import_module(name, package=None): 

65 raise LazyImportError(name=name, package=package, 

66 txt=_no_(import_module.__name__)) 

67 

68 

69class LazyAttributeError(AttributeError): 

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

71 ''' 

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

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

74 

75 

76class LazyImportError(ImportError): 

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

78 ''' 

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

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

81 

82 

83class _Dict(dict): 

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

85 ''' 

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

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

88 

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

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

91 ''' 

92 if key in self: 

93 val = self[key] # duplicate OK 

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

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

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

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

98 raise AssertionError(t) 

99 else: 

100 self[key] = value 

101 

102 

103class _NamedEnum_RO(dict): 

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

105 ''' 

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

107 

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

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

110 

111 def __getattr__(self, attr): 

112 try: 

113 return self[attr] 

114 except KeyError: 

115 t = self._DOT_(attr) 

116 raise LazyAttributeError(t, txt=_doesn_t_exist_) 

117 

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

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

120 raise LazyAttributeError(t, txt=_immutable_) 

121 

122 def enums(self): 

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

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

125 yield k, v 

126 

127 

128_ALL_INIT = _pygeodesy_abspath_, _version_ 

129 

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

131_ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY', 

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

133 'AlbersEqualAreaCylindrical', 'AlbersEqualAreaNorth', 'AlbersEqualAreaSouth', 

134 'AlbersError', 'Albers7Tuple'), 

135 azimuthal=('AzimuthalError', 'Azimuthal7Tuple', 

136 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney', 

137 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney', 

138 'LambertEqualArea', 'Orthographic', 'Stereographic', 

139 'equidistant', 'gnomonic'), 

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

141 'isbool', 'isclass', 'iscomplex', 'isfloat', 

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

143 'isscalar', 'issequence', 'isstr', 'issubclassof', 

144 'len2', 'map1', 'map2', 'neg', 'neg_', 

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

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

147 'isBoolean'), 

148 cartesianBase=(), # module only 

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

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

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

152 'EasNorAziRk4Tuple', 'EasNorAziRkEqu6Tuple', 'LatLonAziRk4Tuple'), 

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

154 'INF', 'INT0', 'MANT_DIG', 'MAX', 'MIN', 'NAN', 'NEG0', 'NINF', 

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

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

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

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

159 'remainder'), 

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

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

162 'bases', 'datum', 'nvector', # DEPRECATED modules 

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

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

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

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

167 'excessAbc', 'excessGirard', 'excessLHuilier', 

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

169 'inStr', 'isDEPRECATED', 'isenclosedby', 'istuplist', 

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

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

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

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

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

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

176 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint', 

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

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

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

180 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'), 

181 elevations=('Elevation2Tuple', 'GeoidHeight2Tuple', 

182 'elevation2', 'geoidHeight2'), 

183 ellipsoidalBase=(), # module only 

184 ellipsoidalBaseDI=(), # module only 

185 ellipsoidalExact=(), # module only 

186 ellipsoidalGeodSolve=(), # module only 

187 ellipsoidalKarney=(), # module only 

188 ellipsoidalNvector=(), # module only 

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

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

191 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids', 

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

193 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a', 

194 'e2f', 'e22f', 

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

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

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

198 errors=('ClipError', 'CrossError', 'IntersectionError', 'NumPyError', 'LenError', 'LimitError', 

199 'MGRSError', 'ParseError', 'PointsError', 'RangeError', 

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

201 'crosserrors', 'exception_chaining', 'isError', 'itemsorted', 

202 'limiterrors', 'rangerrors'), 

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

204 'parseETM5', 'toEtm8'), 

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

206 'cbrt', 'cbrt2', 'euclid', 'euclid_', 

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

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

209 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder', 

210 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_', 

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

212 formy=('Radical2Tuple', 

213 'antipode', 'antipode_', 'bearing', 'bearing_', 

214 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_', 

215 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_', 

216 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_', 

217 'excessAbc_', 'excessGirard_', 'excessLHuilier_', 

218 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_', 

219 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_', 

220 'hartzell', 'haversine', 'haversine_', 'heightOf', 'horizon', 'hubeny', 'hubeny_', 

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

222 'latlon2n_xyz', 'normal', 'normal_', 'n_xyz2latlon', 'n_xyz2philam', 

223 'opposing', 'opposing_', 'philam2n_xyz', 

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

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

226 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert', 

227 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular', 

228 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar', 

229 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas', 

230 'FrechetVincentys', 'Frechet6Tuple', 

231 'frechet_'), 

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

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

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

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

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

237 geodesicx=('gx', 'gxarea', 'gxline', # modules 

238 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

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

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

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

242 'PGMError', 'GeoidHeight5Tuple'), 

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

244 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

245 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

246 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

247 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

248 'HausdorffVincentys', 'Hausdorff6Tuple', 

249 'hausdorff_', 'randomrangenerator'), 

250 heights=('HeightCubic', 'HeightError', 

251 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

252 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

253 'HeightIDWeuclidean', 'HeightIDWexact', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 

254 'HeightIDWhaversine', 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 

255 'HeightIDWvincentys', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

256 interns=_interns_a_l_l_, 

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

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

259 karney=('Area3Tuple', 'Caps', 'Direct9Tuple', 'GDict', 'GeodesicError', 'Inverse10Tuple'), 

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

261 latlonBase=(), # module only 

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

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

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

265 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

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

267 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

268 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 

269 'Ned', 'Ned4Tuple', 'XyzLocal', 'Xyz4Tuple'), 

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

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

272 'nameof', 'notImplemented', 'notOverloaded'), 

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

274 'Destination2Tuple', 'Destination3Tuple', 

275 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

276 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

277 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

278 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

279 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

280 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn4Tuple', 

281 'NearestOn5Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

282 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

283 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

284 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

285 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

286 nvectorBase=(), # module only 

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

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

289 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

290 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

291 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

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

293 'deprecated_class', 'deprecated_function', 'deprecated_method', 

294 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

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

296 'TriAngle4Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

297 'cassini', 'collins5', 'pierlot', 'pierlotx', 'tienstra7', 

298 'snellius3', 'wildberger3', 

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

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

301 rhumbx=('Rhumb', 'RhumbError', 'RhumbLine', 'RhumbOrder2Tuple', 'Rhumb8Tuple'), 

302 sphericalBase=(), # module only 

303 sphericalNvector=(), # module only 

304 sphericalTrigonometry=(), # module only 

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

306 solveBase=(), # module only 

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

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

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

310 'date2epoch', 'epoch2date', 'trfXform'), 

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

312 'JacobiConformal', 'JacobiConformalSpherical', 

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

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

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

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

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

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

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

320 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

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

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

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

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

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

326# 'degrees2grades as degrees2gons', 

327 'fathom2m', 'ft2m', 'furlong2m', 

328 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

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

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

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

332 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

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

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

335 'unroll180', 'unrollPI', 

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

337 'yard2m'), 

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

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

340 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

341 utmupsBase=(), # module only 

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

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

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

345 'trilaterate2d2', 'trilaterate3d2'), 

346 vector3dBase=(), # module only 

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

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

349 

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

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

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

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

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

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

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

357 'HeightIDWhaversine as HeightIDW3'), 

358 points=('areaOf as areaof', 

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

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

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

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

363 

364 

365class _ALL_MODS(object): 

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

367 ''' 

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

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

370 

371 def __getattr__(self, name): 

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

373 

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

375 ''' 

376 try: 

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

378 _DOT_(_pygeodesy_, name)) 

379 except ImportError as x: 

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

381 

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

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

384 raise LazyAttributeError(t, txt=_immutable_) 

385 

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

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

388 

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

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

391 

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

393 

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

395 

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

397 ''' 

398 n = module_name 

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

400 n = _DOT_(_pygeodesy_, n) 

401 m = self.getmodule(n) 

402 try: 

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

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

405 except (AttributeError, TypeError) as x: 

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

407 

408 def getmodule(self, name): 

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

410 

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

412 

413 @return: The C{pygeodesy} module. 

414 

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

416 ''' 

417 try: 

418 return _sys.modules[name] 

419 except KeyError: 

420 return import_module(name, _pygeodesy_) 

421 

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

423# try: 

424# if name not in _sys.modules: 

425# _sys.modules[name] = module 

426# except (AttributeError, KeyError): 

427# pass 

428# return module 

429 

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

431 '''Yield the modules imported so far. 

432 ''' 

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

434 yield n, m 

435 

436_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

437 

438__all__ = _ALL_LAZY.lazily 

439__version__ = '23.07.12' 

440 

441 

442def _ALL_OTHER(*objs): 

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

444 ''' 

445 _interns = _ALL_MODS.interns # from pygeodesy import interns 

446 

447 def _interned(o): # get base name 

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

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

450 return getattr(_interns, i, n) 

451 

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

453 

454 

455if _FOR_DOCS: 

456 _ALL_DOCS = _ALL_OTHER 

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

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

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

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

461 # too much internal documentation. 

462else: 

463 def _ALL_DOCS(*unused): 

464 return () 

465 

466 

467def _all_imports(**more): 

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

469 ''' 

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

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

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

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

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

475 imports = _Dict() 

476 _add = imports.add 

477 

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

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

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

481 _add(mod, mod) 

482 for attr in attrs: 

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

484 if as_attr: 

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

486 else: 

487 _add(attr, mod) 

488 return imports 

489 

490 

491def _all_missing2(_all_): 

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

493 ''' 

494 def _diff(one, two): 

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

496 

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

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

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

500 

501 

502def _caller3(up): # in .named 

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

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

505 ''' 

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

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

508 f = _sys._getframe(up + 1) 

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

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

511 f.f_lineno) # line number 

512 

513 

514def _lazy_import2(package_name): # MCCABE 14 

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

516 

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

518 the imports, to help facilitate resolving 

519 relative imports, usually C{__package__}. 

520 

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

522 for easy reference within itself and the callable to 

523 be set to `__getattr__`. 

524 

525 @raise LazyAttributeError: The package, module or attribute 

526 name is invalid or does not exist. 

527 

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

529 or an import failed. 

530 

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

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

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

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

535 without causing a C{ModuleNotFoundError}. 

536 

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

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

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

540 ''' 

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

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

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

544 

545 package, parent = _lazy_init2(_pygeodesy_) 

546 

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

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

549 imports = _all_imports() 

550 

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

552 # only called once for each undefined pygeodesy attribute 

553 if name in imports: 

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

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

556 # note in the _lazy_import2.__doc__ above). 

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

558 if mod not in imports: 

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

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

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

562 pkg = getattr(imported, _p_a_c_k_a_g_e_, None) 

563 if pkg not in packages: # invalid package 

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

565 # _ALL_MODS._imported(mod, imported) 

566 # import the module or module attribute 

567 if attr: 

568 imported = getattr(imported, attr, MISSING) 

569 elif name != mod: 

570 imported = getattr(imported, name, MISSING) 

571 if imported is MISSING: # PYCHOK no cover 

572 t = _DOT_(mod, attr or name) 

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

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

575 

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

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

578 mod = NN 

579 else: # PYCHOK no cover 

580 t = _no_(_module_, _or_, _attribute_) 

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

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

583 

584 setattr(package, name, imported) 

585 if isLazy > 1: 

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

587 if mod and mod != name: 

588 t = NN(t, _from_DOT__, mod) 

589 if isLazy > 2: 

590 try: # see C{_caller3} 

591 _, f, s = _caller3(2) 

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

593 except ValueError: 

594 pass 

595 printf(t) # XXX print 

596 

597 return imported # __getattr__ 

598 

599 return package, __getattr__ # _lazy_import2 

600 

601 

602def _lazy_init2(package_name): 

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

604 

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

606 the imports, to help facilitate resolving 

607 relative imports, usually C{__package__}. 

608 

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

610 for easy reference within itself and its name aka the 

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

612 

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

614 an import failed or the package name is 

615 invalid or does not exist. 

616 

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

618 ''' 

619 global isLazy 

620 

621 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

622 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

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

624 else: 

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

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

627 if isLazy < 1: # not enabled 

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

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

630 isLazy += 1 

631 

632 try: # to initialize in Python 3+ 

633 package = import_module(package_name) 

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

635 if parent != package_name: # assert 

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

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

638 

639 except (AttributeError, ImportError) as x: 

640 isLazy = False # failed 

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

642 

643 return package, parent 

644 

645 

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

647 # from pygeodesy.streprs import pairs 

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

649 

650 

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

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

653 

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

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

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

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

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

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

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

661 and C{B{flush}}. 

662 

663 @return: Number of bytes written. 

664 ''' 

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

666 

667 

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

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

670 

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

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

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

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

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

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

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

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

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

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

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

682 

683 @return: Number of bytes written. 

684 ''' 

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

686 try: 

687 if args: 

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

689 elif kwds: # PYCHOK no cover 

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

691 else: # PYCHOK no cover 

692 t = fmt 

693 except Exception as x: 

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

695 unstr = _ALL_MODS.streprs.unstr 

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

697 nl_nt_prefix_end_file_flush_sep_kwds)) 

698 try: 

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

700 except UnicodeEncodeError: # XXX only Windows 

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

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

703 if fl: 

704 f.flush() 

705 return n 

706 

707 

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

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

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

711 ''' 

712 if nl > 0: 

713 prefix = NN(_NL_ * nl, prefix) 

714 if nt > 0: 

715 end = NN(end, _NL_ * nt) 

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

717 

718 

719if __name__ == '__main__': 

720 

721 from timeit import timeit 

722 

723 def t1(): 

724 from pygeodesy.trf import RefFrame 

725 return RefFrame 

726 

727 def t2(): 

728 return _ALL_MODS.trf.RefFrame 

729 

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

731 

732 t1 = timeit(t1, number=1000000) 

733 t2 = timeit(t2, number=1000000) 

734 v = _Python_(_sys.version) 

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

736 

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

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

739 

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

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

742 

743# % python2 -m pygeodesy.lazily 

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

745 

746# **) MIT License 

747# 

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

749# 

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

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

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

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

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

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

756# 

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

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

759# 

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

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

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

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

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

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

766# OTHER DEALINGS IN THE SOFTWARE.