Coverage for pygeodesy/lazily.py: 90%

151 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-04-02 08:40 -0400

1 

2# -*- coding: utf-8 -*- 

3 

4u'''Lazily import C{pygeodesy} modules and attributes, based on 

5U{lazy_import<https://modutil.ReadTheDocs.io/en/latest/#lazy_import>} 

6from I{Brett Cannon}'s U{modutil<https://PyPI.org/project/modutil>}. 

7 

8C{Lazy import} is I{supported only for }U{Python 3.7+ 

9<https://Snarky.Ca/lazy-importing-in-python-3-7>} and is I{enabled by 

10default in }U{PyGeodesy 18.11.10+<https://PyPI.org/project/PyGeodesy>} 

11I{ and 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_, _module_, _NL_, _no_, _not_, _or_, \ 

30 _pygeodesy_abspath_, _Python_, _QUOTE1_, _QUOTE2_, \ 

31 _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_line_ = 'line' 

44_p_a_c_k_a_g_e_ = '__package__' 

45_pygeodesy_ = 'pygeodesy' 

46_PYGEODESY_LAZY_IMPORT_ = 'PYGEODESY_LAZY_IMPORT' 

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

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

49_sub_packages = 'deprecated', 'geodesicx' 

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

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

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

53 

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

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

56 

57try: 

58 from importlib import import_module 

59except ImportError: # Python 2.6- 

60 

61 def import_module(name, package=None): 

62 raise LazyImportError(name=name, package=package, 

63 txt=_no_(import_module.__name__)) 

64 

65 

66class LazyImportError(ImportError): 

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

68 ''' 

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

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

71 

72 

73class _Dict(dict): 

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

75 ''' 

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

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

78 

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

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

81 ''' 

82 if key in self: 

83 val = self[key] # duplicate OK 

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

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

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

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

88 raise AssertionError(t) 

89 else: 

90 self[key] = value 

91 

92 

93class _NamedEnum_RO(dict): 

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

95 ''' 

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

97 

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

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

100 

101 def __getattr__(self, attr): 

102 try: 

103 return self[attr] 

104 except KeyError: 

105 t = self._DOT_(attr) 

106 raise AttributeError(_COLONSPACE_(t, _doesn_t_exist_)) 

107 

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

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

110 raise TypeError(_COLONSPACE_(t, _immutable_)) 

111 

112 def enums(self): 

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

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

115 yield k, v 

116 

117 

118_ALL_INIT = _pygeodesy_abspath_, _version_ 

119 

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

121_ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY', 

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

123 'AlbersEqualAreaCylindrical', 'AlbersEqualAreaNorth', 'AlbersEqualAreaSouth', 

124 'AlbersError', 'Albers7Tuple'), 

125 azimuthal=('AzimuthalError', 'Azimuthal7Tuple', 

126 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney', 

127 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney', 

128 'LambertEqualArea', 'Orthographic', 'Stereographic', 

129 'equidistant', 'gnomonic'), 

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

131 'isbool', 'isclass', 'iscomplex', 'isfloat', 

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

133 'isscalar', 'issequence', 'isstr', 'issubclassof', 

134 'len2', 'map1', 'map2', 'neg', 'neg_', 

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

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

137 'isBoolean'), 

138 cartesianBase=(), # module only 

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

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

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

142 'EasNorAziRk4Tuple', 'EasNorAziRkEqu6Tuple', 'LatLonAziRk4Tuple'), 

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

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

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

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

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

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

149 'remainder'), 

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

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

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

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

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

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

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

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 '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 geodesicx=('gx', 'gxarea', 'gxline', # modules 

224 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

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

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

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

228 'PGMError', 'GeoidHeight5Tuple'), 

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

230 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

231 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

232 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

233 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

234 'HausdorffVincentys', 'Hausdorff6Tuple', 

235 'hausdorff_', 'randomrangenerator'), 

236 heights=('HeightError', 

237 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

238 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

239 'HeightIDWeuclidean', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 'HeightIDWhaversine', 

240 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 'HeightIDWvincentys', 

241 'HeightCubic', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

242 interns=_interns_a_l_l_, 

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

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

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

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

247 latlonBase=(), # module only 

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

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

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

251 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

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

253 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

254 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 

255 'Ned', 'Ned4Tuple', 'XyzLocal', 'Xyz4Tuple'), 

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

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

258 'nameof', 'notImplemented', 'notOverloaded'), 

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

260 'Destination2Tuple', 'Destination3Tuple', 

261 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

262 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

263 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

264 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

265 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

266 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn4Tuple', 

267 'NearestOn5Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

268 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

269 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

270 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

271 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

272 nvectorBase=(), # module only 

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

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

275 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

276 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

277 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

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

279 'deprecated_class', 'deprecated_function', 'deprecated_method', 

280 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

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

282 'TriAngle4Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

283 'cassini', 'collins5', 'pierlot', 'tienstra7', 

284 'snellius3', 'wildberger3', 

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

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

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

288 sphericalBase=(), # module only 

289 sphericalNvector=(), # module only 

290 sphericalTrigonometry=(), # module only 

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

292 solveBase=(), # module only 

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

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

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

296 'date2epoch', 'epoch2date', 'trfXform'), 

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

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

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

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

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

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

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

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

305 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

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

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

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

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

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

311# 'degrees2grades as degrees2gons', 

312 'fathom2m', 'ft2m', 'furlong2m', 

313 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

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

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

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

317 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

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

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

320 'unroll180', 'unrollPI', 

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

322 'yard2m'), 

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

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

325 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

326 utmupsBase=(), # module only 

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

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

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

330 'trilaterate2d2', 'trilaterate3d2'), 

331 vector3dBase=(), # module only 

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

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

334 

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

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

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

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

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

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

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

342 'HeightIDWhaversine as HeightIDW3'), 

343 points=('areaOf as areaof', 

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

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

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

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

348 

349 

350class _ALL_MODS(object): 

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

352 ''' 

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

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

355 

356 def __getattr__(self, name): 

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

358 

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

360 ''' 

361 try: 

362 n = _DOT_(_pygeodesy_, name) 

363 return self.getmodule(n) 

364 except ImportError as x: 

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

366 

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

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

369 raise AttributeError(_COLONSPACE_(t, _immutable_)) 

370 

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

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

373 

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

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

376 

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

378 

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

380 

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

382 ''' 

383 n = module_name 

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

385 n = _DOT_(_pygeodesy_, n) 

386 m = self.getmodule(n) 

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

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

389 

390 def getmodule(self, name): 

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

392 

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

394 

395 @return: The C{pygeodesy} module. 

396 

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

398 ''' 

399 try: 

400 return _sys.modules[name] 

401 except KeyError: 

402 return import_module(name, _pygeodesy_) 

403 

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

405# try: 

406# if name not in _sys.modules: 

407# _sys.modules[name] = module 

408# except (AttributeError, KeyError): 

409# pass 

410# return module 

411 

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

413 '''Yield the modules imported so far. 

414 ''' 

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

416 yield n, m 

417 

418_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

419 

420__all__ = _ALL_LAZY.lazily 

421__version__ = '23.03.30' 

422 

423 

424def _ALL_OTHER(*objs): 

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

426 ''' 

427 _interns = _ALL_MODS.interns # from pygeodesy import interns 

428 

429 def _interned(o): # get base name 

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

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

432 return getattr(_interns, i, n) 

433 

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

435 

436 

437if _FOR_DOCS: 

438 _ALL_DOCS = _ALL_OTHER 

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

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

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

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

443 # too much internal documentation. 

444else: 

445 def _ALL_DOCS(*unused): 

446 return () 

447 

448 

449def _all_imports(**more): 

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

451 ''' 

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

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

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

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

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

457 imports = _Dict() 

458 _add = imports.add 

459 

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

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

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

463 _add(mod, mod) 

464 for attr in attrs: 

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

466 if as_attr: 

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

468 else: 

469 _add(attr, mod) 

470 return imports 

471 

472 

473def _all_missing2(_all_): 

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

475 ''' 

476 def _diff(one, two): 

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

478 

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

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

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

482 

483 

484def _caller3(up): # in .named 

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

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

487 ''' 

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

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

490 f = _sys._getframe(up + 1) 

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

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

493 f.f_lineno) # line number 

494 

495 

496def _lazy_import2(package_name): # MCCABE 14 

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

498 

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

500 the imports, to help facilitate resolving 

501 relative imports, usually C{__package__}. 

502 

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

504 for easy reference within itself and the callable to 

505 be set to `__getattr__`. 

506 

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

508 an import failed or the package name or 

509 module name or attribute name is invalid 

510 or does not exist. 

511 

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

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

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

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

516 without causing a C{ModuleNotFoundError}. 

517 

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

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

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

521 ''' 

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

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

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

525 

526 package, parent = _lazy_init2(_pygeodesy_) 

527 

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

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

530 imports = _all_imports() 

531 

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

533 # only called once for each undefined pygeodesy attribute 

534 if name in imports: 

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

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

537 # note in the _lazy_import2.__doc__ above). 

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

539 if mod not in imports: 

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

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

542 pkg = getattr(imported, _p_a_c_k_a_g_e_, None) 

543 if pkg not in packages: # invalid package 

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

545 # _ALL_MODS._imported(mod, imported) 

546 # import the module or module attribute 

547 if attr: 

548 imported = getattr(imported, attr, MISSING) 

549 elif name != mod: 

550 imported = getattr(imported, name, MISSING) 

551 if imported is MISSING: # PYCHOK no cover 

552 t = _DOT_(mod, attr or name) 

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

554 

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

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

557 mod = NN 

558 else: # PYCHOK no cover 

559 t = _no_(_module_, _or_, _attribute_) 

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

561 

562 setattr(package, name, imported) 

563 if isLazy > 1: 

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

565 if mod and mod != name: 

566 t = NN(t, _from_DOT__, mod) 

567 if isLazy > 2: 

568 try: # see C{_caller3} 

569 _, f, s = _caller3(2) 

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

571 except ValueError: 

572 pass 

573 print(t) # XXX printf 

574 

575 return imported # __getattr__ 

576 

577 return package, __getattr__ # _lazy_import2 

578 

579 

580def _lazy_init2(package_name): 

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

582 

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

584 the imports, to help facilitate resolving 

585 relative imports, usually C{__package__}. 

586 

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

588 for easy reference within itself and its name aka the 

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

590 

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

592 an import failed or the package name is 

593 invalid or does not exist. 

594 

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

596 ''' 

597 global isLazy 

598 

599 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

600 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

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

602 else: 

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

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

605 if isLazy < 1: # not enabled 

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

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

608 isLazy += 1 

609 

610 try: # to initialize in Python 3+ 

611 package = import_module(package_name) 

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

613 if parent != package_name: # assert 

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

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

616 

617 except (AttributeError, ImportError) as x: 

618 isLazy = False # failed 

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

620 

621 return package, parent 

622 

623 

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

625 # from pygeodesy.streprs import pairs 

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

627 

628 

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

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

631 

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

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

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

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

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

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

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

639 and C{B{flush}}. 

640 

641 @return: Number of bytes written. 

642 ''' 

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

644 

645 

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

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

648 

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

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

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

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

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

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

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

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

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

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

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

660 

661 @return: Number of bytes written. 

662 ''' 

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

664 try: 

665 if args: 

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

667 elif kwds: # PYCHOK no cover 

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

669 else: # PYCHOK no cover 

670 t = fmt 

671 except Exception as x: 

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

673 unstr = _ALL_MODS.streprs.unstr 

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

675 nl_nt_prefix_end_file_flush_sep_kwds)) 

676 try: 

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

678 except UnicodeEncodeError: # XXX only Windows 

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

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

681 if fl: 

682 f.flush() 

683 return n 

684 

685 

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

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

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

689 ''' 

690 if nl > 0: 

691 prefix = NN(_NL_ * nl, prefix) 

692 if nt > 0: 

693 end = NN(end, _NL_ * nt) 

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

695 

696 

697if __name__ == '__main__': 

698 

699 from timeit import timeit 

700 

701 def t1(): 

702 from pygeodesy.trf import RefFrame 

703 return RefFrame 

704 

705 def t2(): 

706 return _ALL_MODS.trf.RefFrame 

707 

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

709 

710 t1 = timeit(t1, number=1000000) 

711 t2 = timeit(t2, number=1000000) 

712 v = _Python_(_sys.version) 

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

714 

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

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

717 

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

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

720 

721# % python2 -m pygeodesy.lazily 

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

723 

724# **) MIT License 

725# 

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

727# 

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

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

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

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

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

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

734# 

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

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

737# 

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

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

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

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

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

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

744# OTHER DEALINGS IN THE SOFTWARE.