Coverage for pygeodesy/lazily.py: 90%

153 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-08-07 07:28 -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 = 'auxilats', '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 auxilats=(), # module only 

136 azimuthal=('AzimuthalError', 'Azimuthal7Tuple', 

137 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney', 

138 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney', 

139 'LambertEqualArea', 'Orthographic', 'Stereographic', 

140 'equidistant', 'gnomonic'), 

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

142 'isbool', 'isclass', 'iscomplex', 'isfloat', 

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

144 'isscalar', 'issequence', 'isstr', 'issubclassof', 

145 'len2', 'map1', 'map2', 'neg', 'neg_', 

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

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

148 'isBoolean'), 

149 cartesianBase=(), # module only 

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

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

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

153 'EasNorAziRk4Tuple', 'EasNorAziRkEqu6Tuple', 'LatLonAziRk4Tuple'), 

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

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

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

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

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

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

160 'remainder'), 

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

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

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

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

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

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

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

168 'excessAbc', 'excessGirard', 'excessLHuilier', 

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

170 'inStr', 'isDEPRECATED', 'isenclosedby', 'istuplist', 

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

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

173 dms=('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 'F_D__', 'F_DM__', 'F_DMS__', 'F_DEG__', 'F_MIN__', 'F_SEC__', 'F_D60__', 'F__E__', 'F__F__', 'F__G__', 'F_RAD__', 

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

177 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint', 

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

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

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

181 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'), 

182 elevations=('Elevation2Tuple', 'GeoidHeight2Tuple', 

183 'elevation2', 'geoidHeight2'), 

184 ellipsoidalBase=(), # module only 

185 ellipsoidalBaseDI=(), # module only 

186 ellipsoidalExact=(), # module only 

187 ellipsoidalGeodSolve=(), # module only 

188 ellipsoidalKarney=(), # module only 

189 ellipsoidalNvector=(), # module only 

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

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

192 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids', 

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

194 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a', 

195 'e2f', 'e22f', 

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

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

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

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

200 'NumPyError', 'LenError', 'LimitError', 'MGRSError', 

201 'ParseError', 'PointsError', 'RangeError', 'RhumbError', 

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

203 'crosserrors', 'exception_chaining', 'isError', 'itemsorted', 

204 'limiterrors', 'rangerrors'), 

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

206 'parseETM5', 'toEtm8'), 

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

208 'cbrt', 'cbrt2', 'euclid', 'euclid_', 

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

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

211 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder', 

212 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_', 

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

214 formy=('Radical2Tuple', 

215 'antipode', 'antipode_', 'bearing', 'bearing_', 

216 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_', 

217 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_', 

218 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_', 

219 'excessAbc_', 'excessGirard_', 'excessLHuilier_', 

220 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_', 

221 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_', 

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

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

224 'latlon2n_xyz', 'normal', 'normal_', 'n_xyz2latlon', 'n_xyz2philam', 

225 'opposing', 'opposing_', 'philam2n_xyz', 

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

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

228 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert', 

229 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular', 

230 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar', 

231 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas', 

232 'FrechetVincentys', 'Frechet6Tuple', 

233 'frechet_'), 

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

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

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

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

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

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

240 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

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

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

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

244 'PGMError', 'GeoidHeight5Tuple'), 

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

246 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

247 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

248 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

249 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

250 'HausdorffVincentys', 'Hausdorff6Tuple', 

251 'hausdorff_', 'randomrangenerator'), 

252 heights=('HeightCubic', 'HeightError', 

253 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

254 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

255 'HeightIDWeuclidean', 'HeightIDWexact', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 

256 'HeightIDWhaversine', 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 

257 'HeightIDWvincentys', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

258 interns=_interns_a_l_l_, 

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

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

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

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

263 latlonBase=(), # module only 

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

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

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

267 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

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

269 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

270 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 

271 'Ned', 'Ned4Tuple', 'XyzLocal', 'Xyz4Tuple'), 

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

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

274 'nameof', 'notImplemented', 'notOverloaded'), 

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

276 'Destination2Tuple', 'Destination3Tuple', 

277 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

278 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

279 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

280 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

281 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

282 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn4Tuple', 

283 'NearestOn5Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

284 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

285 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

286 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

287 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

288 nvectorBase=(), # module only 

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

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

291 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

292 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

293 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

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

295 'deprecated_class', 'deprecated_function', 'deprecated_method', 

296 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

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

298 'TriAngle4Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

299 'cassini', 'collins5', 'pierlot', 'pierlotx', 'tienstra7', 

300 'snellius3', 'wildberger3', 

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

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

303 rhumbBase=(), # module only 

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

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

306 sphericalBase=(), # module only 

307 sphericalNvector=(), # module only 

308 sphericalTrigonometry=(), # module only 

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

310 solveBase=(), # module only 

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

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

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

314 'date2epoch', 'epoch2date', 'trfXform'), 

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

316 'JacobiConformal', 'JacobiConformalSpherical', 

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

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

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

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

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

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

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

324 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

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

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

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

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

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

330# 'degrees2grades as degrees2gons', 

331 'fathom2m', 'ft2m', 'furlong2m', 

332 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

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

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

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

336 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

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

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

339 'unroll180', 'unrollPI', 

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

341 'yard2m'), 

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

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

344 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

345 utmupsBase=(), # module only 

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

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

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

349 'trilaterate2d2', 'trilaterate3d2'), 

350 vector3dBase=(), # module only 

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

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

353 

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

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

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

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

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

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

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

361 'HeightIDWhaversine as HeightIDW3'), 

362 points=('areaOf as areaof', 

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

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

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

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

367 

368 

369class _ALL_MODS(object): 

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

371 ''' 

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

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

374 

375 def __getattr__(self, name): 

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

377 

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

379 ''' 

380 try: 

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

382 _DOT_(_pygeodesy_, name)) 

383 except ImportError as x: 

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

385 

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

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

388 raise LazyAttributeError(t, txt=_immutable_) 

389 

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

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

392 

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

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

395 

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

397 

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

399 

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

401 ''' 

402 n = module_name 

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

404 n = _DOT_(_pygeodesy_, n) 

405 m = self.getmodule(n) 

406 try: 

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

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

409 except (AttributeError, TypeError) as x: 

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

411 

412 def getmodule(self, name): 

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

414 

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

416 

417 @return: The C{pygeodesy} module. 

418 

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

420 ''' 

421 try: 

422 return _sys.modules[name] 

423 except KeyError: 

424 return import_module(name, _pygeodesy_) 

425 

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

427# try: 

428# if name not in _sys.modules: 

429# _sys.modules[name] = module 

430# except (AttributeError, KeyError): 

431# pass 

432# return module 

433 

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

435 '''Yield the modules imported so far. 

436 ''' 

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

438 yield n, m 

439 

440_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

441 

442__all__ = _ALL_LAZY.lazily 

443__version__ = '23.08.05' 

444 

445 

446def _ALL_OTHER(*objs): 

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

448 ''' 

449 _interns = _ALL_MODS.interns # from pygeodesy import interns 

450 

451 def _interned(o): # get base name 

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

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

454 return getattr(_interns, i, n) 

455 

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

457 

458 

459if _FOR_DOCS: 

460 _ALL_DOCS = _ALL_OTHER 

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

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

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

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

465 # too much internal documentation. 

466else: 

467 def _ALL_DOCS(*unused): 

468 return () 

469 

470 

471def _all_imports(**more): 

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

473 ''' 

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

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

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

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

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

479 imports = _Dict() 

480 _add = imports.add 

481 

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

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

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

485 _add(mod, mod) 

486 for attr in attrs: 

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

488 if as_attr: 

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

490 else: 

491 _add(attr, mod) 

492 return imports 

493 

494 

495def _all_missing2(_all_): 

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

497 ''' 

498 def _diff(one, two): 

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

500 

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

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

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

504 

505 

506def _caller3(up): # in .named 

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

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

509 ''' 

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

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

512 f = _sys._getframe(up + 1) 

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

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

515 f.f_lineno) # line number 

516 

517 

518def _lazy_import2(package_name): # MCCABE 14 

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

520 

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

522 the imports, to help facilitate resolving 

523 relative imports, usually C{__package__}. 

524 

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

526 for easy reference within itself and the callable to 

527 be set to `__getattr__`. 

528 

529 @raise LazyAttributeError: The package, module or attribute 

530 name is invalid or does not exist. 

531 

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

533 or an import failed. 

534 

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

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

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

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

539 without causing a C{ModuleNotFoundError}. 

540 

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

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

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

544 ''' 

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

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

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

548 

549 package, parent = _lazy_init2(_pygeodesy_) 

550 

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

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

553 imports = _all_imports() 

554 

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

556 # only called once for each undefined pygeodesy attribute 

557 if name in imports: 

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

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

560 # note in the _lazy_import2.__doc__ above). 

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

562 if mod not in imports: 

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

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

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

566 pkg = getattr(imported, _p_a_c_k_a_g_e_, None) 

567 if pkg not in packages: # invalid package 

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

569 # _ALL_MODS._imported(mod, imported) 

570 # import the module or module attribute 

571 if attr: 

572 imported = getattr(imported, attr, MISSING) 

573 elif name != mod: 

574 imported = getattr(imported, name, MISSING) 

575 if imported is MISSING: # PYCHOK no cover 

576 t = _DOT_(mod, attr or name) 

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

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

579 

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

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

582 mod = NN 

583 else: # PYCHOK no cover 

584 t = _no_(_module_, _or_, _attribute_) 

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

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

587 

588 setattr(package, name, imported) 

589 if isLazy > 1: 

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

591 if mod and mod != name: 

592 t = NN(t, _from_DOT__, mod) 

593 if isLazy > 2: 

594 try: # see C{_caller3} 

595 _, f, s = _caller3(2) 

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

597 except ValueError: 

598 pass 

599 printf(t) # XXX print 

600 

601 return imported # __getattr__ 

602 

603 return package, __getattr__ # _lazy_import2 

604 

605 

606def _lazy_init2(package_name): 

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

608 

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

610 the imports, to help facilitate resolving 

611 relative imports, usually C{__package__}. 

612 

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

614 for easy reference within itself and its name aka the 

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

616 

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

618 an import failed or the package name is 

619 invalid or does not exist. 

620 

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

622 ''' 

623 global isLazy 

624 

625 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

626 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

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

628 else: 

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

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

631 if isLazy < 1: # not enabled 

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

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

634 isLazy += 1 

635 

636 try: # to initialize in Python 3+ 

637 package = import_module(package_name) 

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

639 if parent != package_name: # assert 

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

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

642 

643 except (AttributeError, ImportError) as x: 

644 isLazy = False # failed 

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

646 

647 return package, parent 

648 

649 

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

651 # from pygeodesy.streprs import pairs 

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

653 

654 

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

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

657 

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

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

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

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

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

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

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

665 and C{B{flush}}. 

666 

667 @return: Number of bytes written. 

668 ''' 

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

670 

671 

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

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

674 

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

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

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

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

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

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

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

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

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

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

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

686 

687 @return: Number of bytes written. 

688 ''' 

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

690 try: 

691 if args: 

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

693 elif kwds: # PYCHOK no cover 

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

695 else: # PYCHOK no cover 

696 t = fmt 

697 except Exception as x: 

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

699 unstr = _ALL_MODS.streprs.unstr 

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

701 nl_nt_prefix_end_file_flush_sep_kwds)) 

702 try: 

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

704 except UnicodeEncodeError: # XXX only Windows 

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

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

707 if fl: 

708 f.flush() 

709 return n 

710 

711 

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

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

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

715 ''' 

716 if nl > 0: 

717 prefix = NN(_NL_ * nl, prefix) 

718 if nt > 0: 

719 end = NN(end, _NL_ * nt) 

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

721 

722 

723if __name__ == '__main__': 

724 

725 from timeit import timeit 

726 

727 def t1(): 

728 from pygeodesy.trf import RefFrame 

729 return RefFrame 

730 

731 def t2(): 

732 return _ALL_MODS.trf.RefFrame 

733 

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

735 

736 t1 = timeit(t1, number=1000000) 

737 t2 = timeit(t2, number=1000000) 

738 v = _Python_(_sys.version) 

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

740 

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

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

743 

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

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

746 

747# % python2 -m pygeodesy.lazily 

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

749 

750# **) MIT License 

751# 

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

753# 

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

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

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

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

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

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

760# 

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

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

763# 

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

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

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

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

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

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

770# OTHER DEALINGS IN THE SOFTWARE.