Coverage for pygeodesy/lazily.py: 90%

150 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-04-23 16:38 -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', 'float0', 'fStr', 'fStrzs', 'hypot3', 

158 'inStr', 'isDEPRECATED', 'isenclosedby', 'istuplist', 

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

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

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

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

165 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint', 

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

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

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

169 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'), 

170 elevations=('Elevation2Tuple', 'GeoidHeight2Tuple', 

171 'elevation2', 'geoidHeight2'), 

172 ellipsoidalBase=(), # module only 

173 ellipsoidalBaseDI=(), # module only 

174 ellipsoidalExact=(), # module only 

175 ellipsoidalGeodSolve=(), # module only 

176 ellipsoidalKarney=(), # module only 

177 ellipsoidalNvector=(), # module only 

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

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

180 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids', 

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

182 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a', 

183 'e2f', 'e22f', 

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

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

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

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

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

189 'TRFError', 'TriangleError', 'UnitError', 'VectorError', 

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

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

192 'parseETM5', 'toEtm8'), 

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

194 'cbrt', 'cbrt2', 'euclid', 'euclid_', 

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

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

197 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder', 

198 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_', 

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

200 formy=('Radical2Tuple', 

201 'antipode', 'antipode_', 'bearing', 'bearing_', 

202 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_', 

203 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_', 

204 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_', 

205 'excessAbc_', 'excessGirard_', 'excessLHuilier_', 

206 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_', 

207 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_', 

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

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

210 'latlon2n_xyz', 'normal', 'normal_', 'n_xyz2latlon', 'n_xyz2philam', 

211 'opposing', 'opposing_', 'philam2n_xyz', 

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

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

214 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert', 

215 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular', 

216 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar', 

217 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas', 

218 'FrechetVincentys', 'Frechet6Tuple', 

219 'frechet_'), 

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

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

222 'fsum', 'fsum_', 'fsum1', 'fsum1_',), 

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

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

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

226 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

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

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

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

230 'PGMError', 'GeoidHeight5Tuple'), 

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

232 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

233 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

234 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

235 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

236 'HausdorffVincentys', 'Hausdorff6Tuple', 

237 'hausdorff_', 'randomrangenerator'), 

238 heights=('HeightError', 

239 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

240 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

241 'HeightIDWeuclidean', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 'HeightIDWhaversine', 

242 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 'HeightIDWvincentys', 

243 'HeightCubic', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

244 interns=_interns_a_l_l_, 

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

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

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

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

249 latlonBase=(), # module only 

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

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

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

253 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

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

255 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

256 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 

257 'Ned', 'Ned4Tuple', 'XyzLocal', 'Xyz4Tuple'), 

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

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

260 'nameof', 'notImplemented', 'notOverloaded'), 

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

262 'Destination2Tuple', 'Destination3Tuple', 

263 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

264 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

265 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

266 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

267 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

268 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn4Tuple', 

269 'NearestOn5Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

270 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

271 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

272 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

273 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

274 nvectorBase=(), # module only 

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

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

277 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

278 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

279 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

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

281 'deprecated_class', 'deprecated_function', 'deprecated_method', 

282 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

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

284 'TriAngle4Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

285 'cassini', 'collins5', 'pierlot', 'tienstra7', 

286 'snellius3', 'wildberger3', 

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

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

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

290 sphericalBase=(), # module only 

291 sphericalNvector=(), # module only 

292 sphericalTrigonometry=(), # module only 

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

294 solveBase=(), # module only 

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

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

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

298 'date2epoch', 'epoch2date', 'trfXform'), 

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

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

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

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

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

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

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

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

307 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

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

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

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

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

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

313# 'degrees2grades as degrees2gons', 

314 'fathom2m', 'ft2m', 'furlong2m', 

315 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

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

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

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

319 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

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

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

322 'unroll180', 'unrollPI', 

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

324 'yard2m'), 

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

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

327 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

328 utmupsBase=(), # module only 

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

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

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

332 'trilaterate2d2', 'trilaterate3d2'), 

333 vector3dBase=(), # module only 

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

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

336 

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

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

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

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

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

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

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

344 'HeightIDWhaversine as HeightIDW3'), 

345 points=('areaOf as areaof', 

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

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

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

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

350 

351 

352class _ALL_MODS(object): 

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

354 ''' 

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

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

357 

358 def __getattr__(self, name): 

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

360 

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

362 ''' 

363 try: 

364 n = _DOT_(_pygeodesy_, name) 

365 return self.getmodule(n) 

366 except ImportError as x: 

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

368 

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

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

371 raise AttributeError(_COLONSPACE_(t, _immutable_)) 

372 

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

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

375 

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

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

378 

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

380 

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

382 

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

384 ''' 

385 n = module_name 

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

387 n = _DOT_(_pygeodesy_, n) 

388 m = self.getmodule(n) 

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

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

391 

392 def getmodule(self, name): 

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

394 

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

396 

397 @return: The C{pygeodesy} module. 

398 

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

400 ''' 

401 try: 

402 return _sys.modules[name] 

403 except KeyError: 

404 return import_module(name, _pygeodesy_) 

405 

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

407# try: 

408# if name not in _sys.modules: 

409# _sys.modules[name] = module 

410# except (AttributeError, KeyError): 

411# pass 

412# return module 

413 

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

415 '''Yield the modules imported so far. 

416 ''' 

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

418 yield n, m 

419 

420_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

421 

422__all__ = _ALL_LAZY.lazily 

423__version__ = '23.04.14' 

424 

425 

426def _ALL_OTHER(*objs): 

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

428 ''' 

429 _interns = _ALL_MODS.interns # from pygeodesy import interns 

430 

431 def _interned(o): # get base name 

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

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

434 return getattr(_interns, i, n) 

435 

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

437 

438 

439if _FOR_DOCS: 

440 _ALL_DOCS = _ALL_OTHER 

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

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

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

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

445 # too much internal documentation. 

446else: 

447 def _ALL_DOCS(*unused): 

448 return () 

449 

450 

451def _all_imports(**more): 

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

453 ''' 

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

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

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

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

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

459 imports = _Dict() 

460 _add = imports.add 

461 

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

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

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

465 _add(mod, mod) 

466 for attr in attrs: 

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

468 if as_attr: 

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

470 else: 

471 _add(attr, mod) 

472 return imports 

473 

474 

475def _all_missing2(_all_): 

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

477 ''' 

478 def _diff(one, two): 

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

480 

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

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

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

484 

485 

486def _caller3(up): # in .named 

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

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

489 ''' 

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

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

492 f = _sys._getframe(up + 1) 

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

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

495 f.f_lineno) # line number 

496 

497 

498def _lazy_import2(package_name): # MCCABE 14 

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

500 

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

502 the imports, to help facilitate resolving 

503 relative imports, usually C{__package__}. 

504 

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

506 for easy reference within itself and the callable to 

507 be set to `__getattr__`. 

508 

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

510 an import failed or the package name or 

511 module name or attribute name is invalid 

512 or does not exist. 

513 

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

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

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

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

518 without causing a C{ModuleNotFoundError}. 

519 

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

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

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

523 ''' 

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

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

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

527 

528 package, parent = _lazy_init2(_pygeodesy_) 

529 

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

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

532 imports = _all_imports() 

533 

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

535 # only called once for each undefined pygeodesy attribute 

536 if name in imports: 

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

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

539 # note in the _lazy_import2.__doc__ above). 

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

541 if mod not in imports: 

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

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

544 pkg = getattr(imported, _p_a_c_k_a_g_e_, None) 

545 if pkg not in packages: # invalid package 

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

547 # _ALL_MODS._imported(mod, imported) 

548 # import the module or module attribute 

549 if attr: 

550 imported = getattr(imported, attr, MISSING) 

551 elif name != mod: 

552 imported = getattr(imported, name, MISSING) 

553 if imported is MISSING: # PYCHOK no cover 

554 t = _DOT_(mod, attr or name) 

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

556 

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

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

559 mod = NN 

560 else: # PYCHOK no cover 

561 t = _no_(_module_, _or_, _attribute_) 

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

563 

564 setattr(package, name, imported) 

565 if isLazy > 1: 

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

567 if mod and mod != name: 

568 t = NN(t, _from_DOT__, mod) 

569 if isLazy > 2: 

570 try: # see C{_caller3} 

571 _, f, s = _caller3(2) 

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

573 except ValueError: 

574 pass 

575 print(t) # XXX printf 

576 

577 return imported # __getattr__ 

578 

579 return package, __getattr__ # _lazy_import2 

580 

581 

582def _lazy_init2(package_name): 

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

584 

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

586 the imports, to help facilitate resolving 

587 relative imports, usually C{__package__}. 

588 

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

590 for easy reference within itself and its name aka the 

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

592 

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

594 an import failed or the package name is 

595 invalid or does not exist. 

596 

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

598 ''' 

599 global isLazy 

600 

601 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

602 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

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

604 else: 

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

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

607 if isLazy < 1: # not enabled 

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

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

610 isLazy += 1 

611 

612 try: # to initialize in Python 3+ 

613 package = import_module(package_name) 

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

615 if parent != package_name: # assert 

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

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

618 

619 except (AttributeError, ImportError) as x: 

620 isLazy = False # failed 

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

622 

623 return package, parent 

624 

625 

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

627 # from pygeodesy.streprs import pairs 

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

629 

630 

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

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

633 

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

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

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

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

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

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

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

641 and C{B{flush}}. 

642 

643 @return: Number of bytes written. 

644 ''' 

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

646 

647 

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

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

650 

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

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

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

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

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

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

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

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

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

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

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

662 

663 @return: Number of bytes written. 

664 ''' 

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

666 try: 

667 if args: 

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

669 elif kwds: # PYCHOK no cover 

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

671 else: # PYCHOK no cover 

672 t = fmt 

673 except Exception as x: 

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

675 unstr = _ALL_MODS.streprs.unstr 

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

677 nl_nt_prefix_end_file_flush_sep_kwds)) 

678 try: 

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

680 except UnicodeEncodeError: # XXX only Windows 

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

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

683 if fl: 

684 f.flush() 

685 return n 

686 

687 

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

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

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

691 ''' 

692 if nl > 0: 

693 prefix = NN(_NL_ * nl, prefix) 

694 if nt > 0: 

695 end = NN(end, _NL_ * nt) 

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

697 

698 

699if __name__ == '__main__': 

700 

701 from timeit import timeit 

702 

703 def t1(): 

704 from pygeodesy.trf import RefFrame 

705 return RefFrame 

706 

707 def t2(): 

708 return _ALL_MODS.trf.RefFrame 

709 

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

711 

712 t1 = timeit(t1, number=1000000) 

713 t2 = timeit(t2, number=1000000) 

714 v = _Python_(_sys.version) 

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

716 

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

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

719 

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

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

722 

723# % python2 -m pygeodesy.lazily 

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

725 

726# **) MIT License 

727# 

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

729# 

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

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

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

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

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

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

736# 

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

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

739# 

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

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

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

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

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

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

746# OTHER DEALINGS IN THE SOFTWARE.