Coverage for pygeodesy/lazily.py: 91%

154 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-10-04 12:08 -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_, _pygeodesy_abspath_, _Python_, \ 

35 _QUOTE1_, _QUOTE2_, _SPACE_, _sub_packages, \ 

36 _UNDER_, _version_, _dunder_nameof 

37 

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

39from os.path import basename as _basename 

40import sys as _sys # in .basics._sizeof 

41 

42_a_l_l_ = '__all__' # in .__init__ 

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

44_from_DOT__ = _SPACE_(NN, _from_, _DOT_) 

45_imports_ = 'imports' 

46_lazily_ = 'lazily' 

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

48_p_a_c_k_a_g_e_ = '__package__' 

49_PYGEODESY_GEOCONVERT_ = 'PYGEODESY_GEOCONVERT' # PYCHOK .mgrs, test.bases 

50_PYGEODESY_GEODSOLVE_ = 'PYGEODESY_GEODSOLVE' # PYCHOK .geodsolve, test.bases 

51_PYGEODESY_LAZY_IMPORT_ = 'PYGEODESY_LAZY_IMPORT' 

52_PYGEODESY_RHUMBSOLVE_ = 'PYGEODESY_RHUMBSOLVE' # PYCHOK .rhumbsolve, test.bases 

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

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

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

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

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

58 

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

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

61 

62try: 

63 from importlib import import_module 

64except ImportError: # Python 2.6- 

65 

66 def import_module(name, package=None): 

67 raise LazyImportError(name=name, package=package, 

68 txt=_no_(import_module.__name__)) 

69 

70 

71class LazyAttributeError(AttributeError): 

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

73 ''' 

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

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

76 

77 

78class LazyImportError(ImportError): 

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

80 ''' 

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

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

83 

84 

85class _Dict(dict): 

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

87 ''' 

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

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

90 

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

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

93 ''' 

94 if key in self: 

95 val = self[key] # duplicate OK 

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

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

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

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

100 raise AssertionError(t) 

101 else: 

102 self[key] = value 

103 

104 

105class _NamedEnum_RO(dict): 

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

107 ''' 

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

109 

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

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

112 

113 def __getattr__(self, attr): 

114 try: 

115 return self[attr] 

116 except KeyError: 

117 t = self._DOT_(attr) 

118 raise LazyAttributeError(t, txt=_doesn_t_exist_) 

119 

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

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

122 raise LazyAttributeError(t, txt=_immutable_) 

123 

124 def enums(self): 

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

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

127 yield k, v 

128 

129 

130_ALL_INIT = _pygeodesy_abspath_, _version_ 

131 

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

133_ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY', 

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

135 'AlbersEqualAreaCylindrical', 'AlbersEqualAreaNorth', 'AlbersEqualAreaSouth', 

136 'AlbersError', 'Albers7Tuple'), 

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

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

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

140 azimuthal=('AzimuthalError', 'Azimuthal7Tuple', 

141 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney', 

142 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney', 

143 'LambertEqualArea', 'Orthographic', 'Stereographic', 

144 'equidistant', 'gnomonic'), 

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

146 'int1s', 'isbool', 'isclass', 'iscomplex', 'isfloat', 

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

148 'isscalar', 'issequence', 'isstr', 'issubclassof', 

149 'len2', 'map1', 'map2', 'neg', 'neg_', 

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

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

152 'isBoolean'), 

153 cartesianBase=(), # module only 

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

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

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

157 'EasNorAziRk4Tuple', 'EasNorAziRkEqu6Tuple', 'LatLonAziRk4Tuple'), 

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

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

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

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

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

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

164 'remainder'), 

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

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

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

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

169 'LatLonExact4Tuple', 'Ned3Tuple', 'RefFrameError', 'Rhumb7Tuple', 'Transform7Tuple', 'TriAngle4Tuple', 'UtmUps4Tuple', 

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

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

172 'excessAbc', 'excessGirard', 'excessLHuilier', 

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

174 'inStr', 'isDEPRECATED', 'isenclosedby', 'istuplist', 

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

176 'scalar', 'simplify2', 'tienstra', 'toUtm', 'triAngle4', 'unsign0', 'unStr', 'utmZoneBand2'), 

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

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

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

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

181 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint', 

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

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

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

185 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'), 

186 elevations=('Elevation2Tuple', 'GeoidHeight2Tuple', 

187 'elevation2', 'geoidHeight2'), 

188 ellipsoidalBase=(), # module only 

189 ellipsoidalBaseDI=(), # module only 

190 ellipsoidalExact=(), # module only 

191 ellipsoidalGeodSolve=(), # module only 

192 ellipsoidalKarney=(), # module only 

193 ellipsoidalNvector=(), # module only 

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

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

196 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids', 

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

198 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a', 

199 'e2f', 'e22f', 

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

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

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

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

204 'NumPyError', 'LenError', 'LimitError', 'MGRSError', 

205 'ParseError', 'PointsError', 'RangeError', 'RhumbError', 

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

207 'crosserrors', 'exception_chaining', 'isError', 'itemsorted', 

208 'limiterrors', 'rangerrors'), 

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

210 'parseETM5', 'toEtm8'), 

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

212 'bqrt', 'cbrt', 'cbrt2', 'euclid', 'euclid_', 

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

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

215 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder', 

216 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_', 

217 'norm2', 'norm_', 'sqrt0', 'sqrt3', 'sqrt_a', 'zcrt', 'zqrt'), 

218 formy=('Radical2Tuple', 

219 'antipode', 'antipode_', 'bearing', 'bearing_', 

220 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_', 

221 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_', 

222 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_', 

223 'excessAbc_', 'excessCagnoli_', 'excessGirard_', 'excessLHuilier_', 

224 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_', 

225 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_', 

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

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

228 'latlon2n_xyz', 'normal', 'normal_', 'n_xyz2latlon', 'n_xyz2philam', 

229 'opposing', 'opposing_', 'philam2n_xyz', 

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

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

232 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert', 

233 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular', 

234 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar', 

235 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas', 

236 'FrechetVincentys', 'Frechet6Tuple', 

237 'frechet_'), 

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

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

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

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

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

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

244 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

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

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

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

248 'PGMError', 'GeoidHeight5Tuple'), 

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

250 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

251 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

252 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

253 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

254 'HausdorffVincentys', 'Hausdorff6Tuple', 

255 'hausdorff_', 'randomrangenerator'), 

256 heights=('HeightCubic', 'HeightError', 

257 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

258 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

259 'HeightIDWeuclidean', 'HeightIDWexact', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 

260 'HeightIDWhaversine', 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 

261 'HeightIDWvincentys', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

262 interns=_interns_a_l_l_, 

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

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

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

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

267 latlonBase=(), # module only 

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

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

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

271 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

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

273 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

274 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 'Los', 

275 'Ned', 'Ned4Tuple', 'Uvw', 'Uvw3Tuple', 'XyzLocal', 'Xyz4Tuple'), 

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

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

278 'nameof', 'notImplemented', 'notOverloaded'), 

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

280 'Destination2Tuple', 'Destination3Tuple', 

281 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

282 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

283 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

284 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

285 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

286 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn4Tuple', 

287 'NearestOn5Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

288 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

289 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

290 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

291 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

292 nvectorBase=(), # module only 

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

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

295 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

296 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

297 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

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

299 'deprecated_class', 'deprecated_function', 'deprecated_method', 

300 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

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

302 'TriAngle5Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

303 'cassini', 'collins5', 'pierlot', 'pierlotx', 'tienstra7', 

304 'snellius3', 'wildberger3', 

305 'triAngle', 'triAngle5', 'triArea', 'triSide', 'triSide2', 'triSide4'), 

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

307 rhumbBase=(), # module only 

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

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

310 sphericalBase=(), # module only 

311 sphericalNvector=(), # module only 

312 sphericalTrigonometry=(), # module only 

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

314 solveBase=(), # module only 

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

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

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

318 'date2epoch', 'epoch2date', 'trfXform'), 

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

320 'JacobiConformal', 'JacobiConformalSpherical', 

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

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

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

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

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

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

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

328 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

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

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

331 utily=('acos1', 'acre2ha', 'acre2m2', 'asin1', 'atan1', 'atan1d', 'atan2b', 'atan2d', 

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

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

334# 'degrees2grades as degrees2gons', 

335 'fathom2m', 'ft2m', 'furlong2m', 

336 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

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

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

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

340 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

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

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

343 'unroll180', 'unrollPI', 

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

345 'yard2m'), 

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

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

348 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

349 utmupsBase=(), # module only 

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

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

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

353 'trilaterate2d2', 'trilaterate3d2'), 

354 vector3dBase=(), # module only 

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

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

357 

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

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

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

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

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

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

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

365 'HeightIDWhaversine as HeightIDW3'), 

366 points=('areaOf as areaof', 

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

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

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

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

371 

372 

373class _ALL_MODS(object): 

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

375 ''' 

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

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

378 

379 def __getattr__(self, name): 

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

381 

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

383 ''' 

384 try: 

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

386 _DOT_(_pygeodesy_, name)) 

387 except ImportError as x: 

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

389 

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

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

392 raise LazyAttributeError(t, txt=_immutable_) 

393 

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

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

396 

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

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

399 

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

401 

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

403 

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

405 ''' 

406 n = module_name 

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

408 n = _DOT_(_pygeodesy_, n) 

409 m = self.getmodule(n) 

410 try: 

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

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

413 except (AttributeError, TypeError) as x: 

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

415 

416 def getmodule(self, name): 

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

418 

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

420 

421 @return: The C{pygeodesy} module. 

422 

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

424 ''' 

425 try: 

426 return _sys.modules[name] 

427 except KeyError: 

428 return import_module(name, _pygeodesy_) 

429 

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

431# try: 

432# if name not in _sys.modules: 

433# _sys.modules[name] = module 

434# except (AttributeError, KeyError): 

435# pass 

436# return module 

437 

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

439 '''Yield the modules imported so far. 

440 ''' 

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

442 yield n, m 

443 

444_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

445 

446__all__ = _ALL_LAZY.lazily 

447__version__ = '23.10.02' 

448 

449 

450def _ALL_OTHER(*objs): 

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

452 ''' 

453 _interns = _ALL_MODS.interns # from pygeodesy import interns 

454 

455 def _interned(o): # get base name 

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

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

458 return getattr(_interns, i, n) 

459 

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

461 

462 

463if _FOR_DOCS: 

464 _ALL_DOCS = _ALL_OTHER 

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

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

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

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

469 # too much internal documentation. 

470else: 

471 def _ALL_DOCS(*unused): 

472 return () 

473 

474 

475def _all_imports(**more): 

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

477 ''' 

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

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

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

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

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

483 imports = _Dict() 

484 _add = imports.add 

485 

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

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

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

489 _add(mod, mod) 

490 for attr in attrs: 

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

492 if as_attr: 

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

494 else: 

495 _add(attr, mod) 

496 return imports 

497 

498 

499def _all_missing2(_all_): 

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

501 ''' 

502 def _diff(one, two): 

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

504 

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

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

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

508 

509 

510def _caller3(up): # in .named 

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

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

513 ''' 

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

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

516 f = _sys._getframe(up + 1) 

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

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

519 f.f_lineno) # line number 

520 

521 

522def _lazy_import2(package_name): # MCCABE 14 

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

524 

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

526 the imports, to help facilitate resolving 

527 relative imports, usually C{__package__}. 

528 

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

530 for easy reference within itself and the callable to 

531 be set to `__getattr__`. 

532 

533 @raise LazyAttributeError: The package, module or attribute 

534 name is invalid or does not exist. 

535 

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

537 or an import failed. 

538 

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

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

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

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

543 without causing a C{ModuleNotFoundError}. 

544 

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

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

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

548 ''' 

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

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

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

552 

553 package, parent = _lazy_init2(_pygeodesy_) 

554 

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

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

557 imports = _all_imports() 

558 

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

560 # only called once for each undefined pygeodesy attribute 

561 if name in imports: 

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

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

564 # note in the _lazy_import2.__doc__ above). 

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

566 if mod not in imports: 

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

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

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

570 pkg = getattr(imported, _p_a_c_k_a_g_e_, None) 

571 if pkg not in packages: # invalid package 

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

573 # _ALL_MODS._imported(mod, imported) 

574 # import the module or module attribute 

575 if attr: 

576 imported = getattr(imported, attr, MISSING) 

577 elif name != mod: 

578 imported = getattr(imported, name, MISSING) 

579 if imported is MISSING: # PYCHOK no cover 

580 t = _DOT_(mod, attr or name) 

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

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

583 

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

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

586 mod = NN 

587 else: # PYCHOK no cover 

588 t = _no_(_module_, _or_, _attribute_) 

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

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

591 

592 setattr(package, name, imported) 

593 if isLazy > 1: 

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

595 if mod and mod != name: 

596 t = NN(t, _from_DOT__, mod) 

597 if isLazy > 2: 

598 try: # see C{_caller3} 

599 _, f, s = _caller3(2) 

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

601 except ValueError: 

602 pass 

603 printf(t) # XXX print 

604 

605 return imported # __getattr__ 

606 

607 return package, __getattr__ # _lazy_import2 

608 

609 

610def _lazy_init2(package_name): 

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

612 

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

614 the imports, to help facilitate resolving 

615 relative imports, usually C{__package__}. 

616 

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

618 for easy reference within itself and its name aka the 

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

620 

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

622 an import failed or the package name is 

623 invalid or does not exist. 

624 

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

626 ''' 

627 global isLazy 

628 

629 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

630 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

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

632 else: 

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

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

635 if isLazy < 1: # not enabled 

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

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

638 isLazy += 1 

639 

640 try: # to initialize in Python 3+ 

641 package = import_module(package_name) 

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

643 if parent != package_name: # assert 

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

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

646 

647 except (AttributeError, ImportError) as x: 

648 isLazy = False # failed 

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

650 

651 return package, parent 

652 

653 

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

655 # from pygeodesy.streprs import pairs 

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

657 

658 

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

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

661 

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

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

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

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

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

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

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

669 and C{B{flush}}. 

670 

671 @return: Number of bytes written. 

672 ''' 

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

674 

675 

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

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

678 

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

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

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

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

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

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

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

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

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

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

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

690 

691 @return: Number of bytes written. 

692 ''' 

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

694 try: 

695 if args: 

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

697 elif kwds: # PYCHOK no cover 

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

699 else: # PYCHOK no cover 

700 t = fmt 

701 except Exception as x: 

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

703 unstr = _ALL_MODS.streprs.unstr 

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

705 nl_nt_prefix_end_file_flush_sep_kwds)) 

706 try: 

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

708 except UnicodeEncodeError: # XXX only Windows 

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

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

711 if fl: 

712 f.flush() 

713 return n 

714 

715 

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

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

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

719 ''' 

720 if nl > 0: 

721 prefix = NN(_NL_ * nl, prefix) 

722 if nt > 0: 

723 end = NN(end, _NL_ * nt) 

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

725 

726 

727if __name__ == '__main__': 

728 

729 from timeit import timeit 

730 

731 def t1(): 

732 from pygeodesy.trf import RefFrame 

733 return RefFrame 

734 

735 def t2(): 

736 return _ALL_MODS.trf.RefFrame 

737 

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

739 

740 t1 = timeit(t1, number=1000000) 

741 t2 = timeit(t2, number=1000000) 

742 v = _Python_(_sys.version) 

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

744 

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

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

747 

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

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

750 

751# % python2 -m pygeodesy.lazily 

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

753 

754# **) MIT License 

755# 

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

757# 

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

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

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

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

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

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

764# 

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

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

767# 

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

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

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

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

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

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

774# OTHER DEALINGS IN THE SOFTWARE.