Coverage for pygeodesy/lazily.py: 90%

150 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-04-12 11:45 -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 later}. 

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

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

26 _areaOf_, _attribute_, _by_, _COLONSPACE_, \ 

27 _COMMASPACE_, _doesn_t_exist_, _DOT_, _enabled_, \ 

28 _EQUALSPACED_, _from_, _immutable_, _isclockwise_, \ 

29 _ispolar_, _line_, _module_, _NL_, _no_, _not_, \ 

30 _or_, _pygeodesy_abspath_, _Python_, _QUOTE1_, \ 

31 _QUOTE2_, _SPACE_, _UNDER_, _version_, _dunder_nameof 

32 

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

34from os.path import basename as _basename 

35import sys as _sys # in .fsums.Fsum.__sizeof__, .props 

36 

37_a_l_l_ = '__all__' 

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

39_from_DOT__ = _SPACE_(NN, _from_, _DOT_) 

40_imports_ = 'imports' 

41_lazily_ = 'lazily' 

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

43_p_a_c_k_a_g_e_ = '__package__' 

44_pygeodesy_ = 'pygeodesy' 

45_PYGEODESY_LAZY_IMPORT_ = 'PYGEODESY_LAZY_IMPORT' 

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

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

48_sub_packages = 'deprecated', 'geodesicx' 

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

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

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

52 

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

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

55 

56try: 

57 from importlib import import_module 

58except ImportError: # Python 2.6- 

59 

60 def import_module(name, package=None): 

61 raise LazyImportError(name=name, package=package, 

62 txt=_no_(import_module.__name__)) 

63 

64 

65class LazyImportError(ImportError): 

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

67 ''' 

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

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

70 

71 

72class _Dict(dict): 

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

74 ''' 

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

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

77 

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

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

80 ''' 

81 if key in self: 

82 val = self[key] # duplicate OK 

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

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

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

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

87 raise AssertionError(t) 

88 else: 

89 self[key] = value 

90 

91 

92class _NamedEnum_RO(dict): 

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

94 ''' 

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

96 

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

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

99 

100 def __getattr__(self, attr): 

101 try: 

102 return self[attr] 

103 except KeyError: 

104 t = self._DOT_(attr) 

105 raise AttributeError(_COLONSPACE_(t, _doesn_t_exist_)) 

106 

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

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

109 raise TypeError(_COLONSPACE_(t, _immutable_)) 

110 

111 def enums(self): 

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

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

114 yield k, v 

115 

116 

117_ALL_INIT = _pygeodesy_abspath_, _version_ 

118 

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

120_ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY', 

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

122 'AlbersEqualAreaCylindrical', 'AlbersEqualAreaNorth', 'AlbersEqualAreaSouth', 

123 'AlbersError', 'Albers7Tuple'), 

124 azimuthal=('AzimuthalError', 'Azimuthal7Tuple', 

125 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney', 

126 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney', 

127 'LambertEqualArea', 'Orthographic', 'Stereographic', 

128 'equidistant', 'gnomonic'), 

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

130 'isbool', 'isclass', 'iscomplex', 'isfloat', 

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

132 'isscalar', 'issequence', 'isstr', 'issubclassof', 

133 'len2', 'map1', 'map2', 'neg', 'neg_', 

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

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

136 'isBoolean'), 

137 cartesianBase=(), # module only 

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

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

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

141 'EasNorAziRk4Tuple', 'EasNorAziRkEqu6Tuple', 'LatLonAziRk4Tuple'), 

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

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

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

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

146 'float_', 'float0', 'isclose', 'isfinite', 'isinf', 'isint0', 

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

148 'remainder'), 

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

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

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

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

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

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

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

156 'excessAbc', 'excessGirard', 'excessLHuilier', 

157 'false2f', 'falsed2f', 'fStr', 'fStrzs', 'hypot3', 'inStr', 'isDEPRECATED', 'isenclosedby', 'istuplist', 

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

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

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

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

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

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

164 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint', 

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

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

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

168 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'), 

169 elevations=('Elevation2Tuple', 'GeoidHeight2Tuple', 

170 'elevation2', 'geoidHeight2'), 

171 ellipsoidalBase=(), # module only 

172 ellipsoidalBaseDI=(), # module only 

173 ellipsoidalExact=(), # module only 

174 ellipsoidalGeodSolve=(), # module only 

175 ellipsoidalKarney=(), # module only 

176 ellipsoidalNvector=(), # module only 

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

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

179 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids', 

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

181 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a', 

182 'e2f', 'e22f', 

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

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

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

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

187 'MGRSError', 'ParseError', 'PointsError', 'RangeError', 'SciPyError', 'SciPyWarning', 

188 'TRFError', 'TriangleError', 'UnitError', 'VectorError', 

189 'crosserrors', 'exception_chaining', 'isError', 'itemsorted', 'limiterrors', 'rangerrors'), 

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

191 'parseETM5', 'toEtm8'), 

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

193 'cbrt', 'cbrt2', 'euclid', 'euclid_', 

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

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

196 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder', 

197 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_', 

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

199 formy=('Radical2Tuple', 

200 'antipode', 'antipode_', 'bearing', 'bearing_', 

201 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_', 

202 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_', 

203 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_', 

204 'excessAbc_', 'excessGirard_', 'excessLHuilier_', 

205 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_', 

206 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_', 

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

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

209 'latlon2n_xyz', 'normal', 'normal_', 'n_xyz2latlon', 'n_xyz2philam', 

210 'opposing', 'opposing_', 'philam2n_xyz', 

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

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

213 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert', 

214 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular', 

215 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar', 

216 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas', 

217 'FrechetVincentys', 'Frechet6Tuple', 

218 'frechet_'), 

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

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

221 'fsum', 'fsum_', 'fsum1', 'fsum1_',), 

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

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

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

225 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

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

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

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

229 'PGMError', 'GeoidHeight5Tuple'), 

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

231 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

232 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

233 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

234 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

235 'HausdorffVincentys', 'Hausdorff6Tuple', 

236 'hausdorff_', 'randomrangenerator'), 

237 heights=('HeightError', 

238 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

239 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

240 'HeightIDWeuclidean', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 'HeightIDWhaversine', 

241 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 'HeightIDWvincentys', 

242 'HeightCubic', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

243 interns=_interns_a_l_l_, 

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

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

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

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

248 latlonBase=(), # module only 

249 lazily=('LazyImportError', 'isLazy', 'print_', 'printf'), 

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

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

252 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

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

254 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

255 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 

256 'Ned', 'Ned4Tuple', 'XyzLocal', 'Xyz4Tuple'), 

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

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

259 'nameof', 'notImplemented', 'notOverloaded'), 

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

261 'Destination2Tuple', 'Destination3Tuple', 

262 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

263 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

264 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

265 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

266 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

267 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn4Tuple', 

268 'NearestOn5Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

269 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

270 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

271 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

272 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

273 nvectorBase=(), # module only 

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

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

276 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

277 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

278 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

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

280 'deprecated_class', 'deprecated_function', 'deprecated_method', 

281 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

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

283 'TriAngle4Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

284 'cassini', 'collins5', 'pierlot', 'tienstra7', 

285 'snellius3', 'wildberger3', 

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

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

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

289 sphericalBase=(), # module only 

290 sphericalNvector=(), # module only 

291 sphericalTrigonometry=(), # module only 

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

293 solveBase=(), # module only 

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

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

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

297 'date2epoch', 'epoch2date', 'trfXform'), 

298 triaxials=('BetaOmega2Tuple', 'BetaOmega3Tuple', 'Jacobi2Tuple', 'JacobiConformal', 

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

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

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

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

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

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

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

306 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

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

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

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

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

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

312# 'degrees2grades as degrees2gons', 

313 'fathom2m', 'ft2m', 'furlong2m', 

314 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

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

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

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

318 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

319 'sincos2', 'sincos2_', 'sincos2d', 'sincos2d_', 'sincostan3', 'SM2m', 

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

321 'unroll180', 'unrollPI', 

322 'wrap90', 'wrap180', 'wrap360', 'wrapPI_2','wrapPI', 'wrapPI2', 

323 'yard2m'), 

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

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

326 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

327 utmupsBase=(), # module only 

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

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

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

331 'trilaterate2d2', 'trilaterate3d2'), 

332 vector3dBase=(), # module only 

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

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

335 

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

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

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

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

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

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

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

343 'HeightIDWhaversine as HeightIDW3'), 

344 points=('areaOf as areaof', 

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

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

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

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

349 

350 

351class _ALL_MODS(object): 

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

353 ''' 

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

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

356 

357 def __getattr__(self, name): 

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

359 

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

361 ''' 

362 try: 

363 n = _DOT_(_pygeodesy_, name) 

364 return self.getmodule(n) 

365 except ImportError as x: 

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

367 

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

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

370 raise AttributeError(_COLONSPACE_(t, _immutable_)) 

371 

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

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

374 

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

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

377 

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

379 

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

381 

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

383 ''' 

384 n = module_name 

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

386 n = _DOT_(_pygeodesy_, n) 

387 m = self.getmodule(n) 

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

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

390 

391 def getmodule(self, name): 

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

393 

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

395 

396 @return: The C{pygeodesy} module. 

397 

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

399 ''' 

400 try: 

401 return _sys.modules[name] 

402 except KeyError: 

403 return import_module(name, _pygeodesy_) 

404 

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

406# try: 

407# if name not in _sys.modules: 

408# _sys.modules[name] = module 

409# except (AttributeError, KeyError): 

410# pass 

411# return module 

412 

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

414 '''Yield the modules imported so far. 

415 ''' 

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

417 yield n, m 

418 

419_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

420 

421__all__ = _ALL_LAZY.lazily 

422__version__ = '23.04.11' 

423 

424 

425def _ALL_OTHER(*objs): 

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

427 ''' 

428 _interns = _ALL_MODS.interns # from pygeodesy import interns 

429 

430 def _interned(o): # get base name 

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

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

433 return getattr(_interns, i, n) 

434 

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

436 

437 

438if _FOR_DOCS: 

439 _ALL_DOCS = _ALL_OTHER 

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

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

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

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

444 # too much internal documentation. 

445else: 

446 def _ALL_DOCS(*unused): 

447 return () 

448 

449 

450def _all_imports(**more): 

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

452 ''' 

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

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

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

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

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

458 imports = _Dict() 

459 _add = imports.add 

460 

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

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

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

464 _add(mod, mod) 

465 for attr in attrs: 

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

467 if as_attr: 

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

469 else: 

470 _add(attr, mod) 

471 return imports 

472 

473 

474def _all_missing2(_all_): 

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

476 ''' 

477 def _diff(one, two): 

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

479 

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

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

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

483 

484 

485def _caller3(up): # in .named 

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

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

488 ''' 

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

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

491 f = _sys._getframe(up + 1) 

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

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

494 f.f_lineno) # line number 

495 

496 

497def _lazy_import2(package_name): # MCCABE 14 

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

499 

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

501 the imports, to help facilitate resolving 

502 relative imports, usually C{__package__}. 

503 

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

505 for easy reference within itself and the callable to 

506 be set to `__getattr__`. 

507 

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

509 an import failed or the package name or 

510 module name or attribute name is invalid 

511 or does not exist. 

512 

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

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

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

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

517 without causing a C{ModuleNotFoundError}. 

518 

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

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

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

522 ''' 

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

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

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

526 

527 package, parent = _lazy_init2(_pygeodesy_) 

528 

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

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

531 imports = _all_imports() 

532 

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

534 # only called once for each undefined pygeodesy attribute 

535 if name in imports: 

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

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

538 # note in the _lazy_import2.__doc__ above). 

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

540 if mod not in imports: 

541 raise LazyImportError(_no_(_module_), txt=_DOT_(parent, mod)) 

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

543 pkg = getattr(imported, _p_a_c_k_a_g_e_, None) 

544 if pkg not in packages: # invalid package 

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

546 # _ALL_MODS._imported(mod, imported) 

547 # import the module or module attribute 

548 if attr: 

549 imported = getattr(imported, attr, MISSING) 

550 elif name != mod: 

551 imported = getattr(imported, name, MISSING) 

552 if imported is MISSING: # PYCHOK no cover 

553 t = _DOT_(mod, attr or name) 

554 raise LazyImportError(_no_(_attribute_), txt=t) 

555 

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

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

558 mod = NN 

559 else: # PYCHOK no cover 

560 t = _no_(_module_, _or_, _attribute_) 

561 raise LazyImportError(t, txt=_DOT_(parent, name)) 

562 

563 setattr(package, name, imported) 

564 if isLazy > 1: 

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

566 if mod and mod != name: 

567 t = NN(t, _from_DOT__, mod) 

568 if isLazy > 2: 

569 try: # see C{_caller3} 

570 _, f, s = _caller3(2) 

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

572 except ValueError: 

573 pass 

574 print(t) # XXX printf 

575 

576 return imported # __getattr__ 

577 

578 return package, __getattr__ # _lazy_import2 

579 

580 

581def _lazy_init2(package_name): 

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

583 

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

585 the imports, to help facilitate resolving 

586 relative imports, usually C{__package__}. 

587 

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

589 for easy reference within itself and its name aka the 

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

591 

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

593 an import failed or the package name is 

594 invalid or does not exist. 

595 

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

597 ''' 

598 global isLazy 

599 

600 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

601 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

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

603 else: 

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

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

606 if isLazy < 1: # not enabled 

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

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

609 isLazy += 1 

610 

611 try: # to initialize in Python 3+ 

612 package = import_module(package_name) 

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

614 if parent != package_name: # assert 

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

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

617 

618 except (AttributeError, ImportError) as x: 

619 isLazy = False # failed 

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

621 

622 return package, parent 

623 

624 

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

626 # from pygeodesy.streprs import pairs 

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

628 

629 

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

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

632 

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

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

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

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

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

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

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

640 and C{B{flush}}. 

641 

642 @return: Number of bytes written. 

643 ''' 

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

645 

646 

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

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

649 

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

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

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

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

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

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

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

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

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

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

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

661 

662 @return: Number of bytes written. 

663 ''' 

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

665 try: 

666 if args: 

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

668 elif kwds: # PYCHOK no cover 

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

670 else: # PYCHOK no cover 

671 t = fmt 

672 except Exception as x: 

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

674 unstr = _ALL_MODS.streprs.unstr 

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

676 nl_nt_prefix_end_file_flush_sep_kwds)) 

677 try: 

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

679 except UnicodeEncodeError: # XXX only Windows 

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

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

682 if fl: 

683 f.flush() 

684 return n 

685 

686 

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

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

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

690 ''' 

691 if nl > 0: 

692 prefix = NN(_NL_ * nl, prefix) 

693 if nt > 0: 

694 end = NN(end, _NL_ * nt) 

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

696 

697 

698if __name__ == '__main__': 

699 

700 from timeit import timeit 

701 

702 def t1(): 

703 from pygeodesy.trf import RefFrame 

704 return RefFrame 

705 

706 def t2(): 

707 return _ALL_MODS.trf.RefFrame 

708 

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

710 

711 t1 = timeit(t1, number=1000000) 

712 t2 = timeit(t2, number=1000000) 

713 v = _Python_(_sys.version) 

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

715 

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

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

718 

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

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

721 

722# % python2 -m pygeodesy.lazily 

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

724 

725# **) MIT License 

726# 

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

728# 

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

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

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

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

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

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

735# 

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

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

738# 

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

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

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

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

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

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

745# OTHER DEALINGS IN THE SOFTWARE.