Coverage for pygeodesy/lazily.py: 90%

149 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-06-07 08:37 -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 .basics._sizeof 

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', 'PI_2', 'PI3', 'PI_3', 'PI3_2', 'PI4', 'PI_4', 

145 'R_FM', 'R_GM', 'R_KM', 'R_M', 'R_MA', 'R_MB', 'R_NM', 'R_QM', 'R_SM', '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', 

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

190 'crosserrors', 'exception_chaining', 'isError', 'itemsorted', 

191 'limiterrors', 'rangerrors'), 

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

193 'parseETM5', 'toEtm8'), 

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

195 'cbrt', 'cbrt2', 'euclid', 'euclid_', 

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

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

198 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder', 

199 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_', 

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

201 formy=('Radical2Tuple', 

202 'antipode', 'antipode_', 'bearing', 'bearing_', 

203 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_', 

204 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_', 

205 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_', 

206 'excessAbc_', 'excessGirard_', 'excessLHuilier_', 

207 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_', 

208 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_', 

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

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

211 'latlon2n_xyz', 'normal', 'normal_', 'n_xyz2latlon', 'n_xyz2philam', 

212 'opposing', 'opposing_', 'philam2n_xyz', 

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

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

215 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert', 

216 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular', 

217 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar', 

218 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas', 

219 'FrechetVincentys', 'Frechet6Tuple', 

220 'frechet_'), 

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

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

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

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

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

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

227 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'), 

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

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

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

231 'PGMError', 'GeoidHeight5Tuple'), 

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

233 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert', 

234 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular', 

235 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar', 

236 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas', 

237 'HausdorffVincentys', 'Hausdorff6Tuple', 

238 'hausdorff_', 'randomrangenerator'), 

239 heights=('HeightCubic', 'HeightError', 

240 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert', 

241 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular', 

242 'HeightIDWeuclidean', 'HeightIDWexact', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 

243 'HeightIDWhaversine', 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 

244 'HeightIDWvincentys', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'), 

245 interns=_interns_a_l_l_, 

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

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

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

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

250 latlonBase=(), # module only 

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

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

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

254 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'), 

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

256 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple', 

257 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 

258 'Ned', 'Ned4Tuple', 'XyzLocal', 'Xyz4Tuple'), 

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

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

261 'nameof', 'notImplemented', 'notOverloaded'), 

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

263 'Destination2Tuple', 'Destination3Tuple', 

264 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple', 

265 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple', 

266 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple', 

267 'LatLonDatum3Tuple', 'LatLonDatum5Tuple', 

268 'LatLonPrec3Tuple', 'LatLonPrec5Tuple', 

269 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn4Tuple', 

270 'NearestOn5Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple', 

271 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple', 

272 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple', 

273 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple', 

274 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'), 

275 nvectorBase=(), # module only 

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

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

278 _areaOf_, 'boundsOf', 'centroidOf', 'fractional', 

279 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_, 

280 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'), 

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

282 'deprecated_class', 'deprecated_function', 'deprecated_method', 

283 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'), 

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

285 'TriAngle4Tuple', 'TriSide2Tuple', 'TriSide4Tuple', 

286 'cassini', 'collins5', 'pierlot', 'pierlotx', 'tienstra7', 

287 'snellius3', 'wildberger3', 

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

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

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

291 sphericalBase=(), # module only 

292 sphericalNvector=(), # module only 

293 sphericalTrigonometry=(), # module only 

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

295 solveBase=(), # module only 

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

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

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

299 'date2epoch', 'epoch2date', 'trfXform'), 

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

301 'JacobiConformal', 'JacobiConformalSpherical', 

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

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

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

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

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

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

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

309 'Radius_', 'Scalar', 'Scalar_', 'Zone'), 

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

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

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

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

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

315# 'degrees2grades as degrees2gons', 

316 'fathom2m', 'ft2m', 'furlong2m', 

317 'grades', 'grades400', 'grades2degrees', 'grades2radians', 

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

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

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

321 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m', 

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

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

324 'unroll180', 'unrollPI', 

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

326 'yard2m'), 

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

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

329 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'), 

330 utmupsBase=(), # module only 

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

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

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

334 'trilaterate2d2', 'trilaterate3d2'), 

335 vector3dBase=(), # module only 

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

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

338 

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

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

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

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

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

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

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

346 'HeightIDWhaversine as HeightIDW3'), 

347 points=('areaOf as areaof', 

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

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

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

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

352 

353 

354class _ALL_MODS(object): 

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

356 ''' 

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

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

359 

360 def __getattr__(self, name): 

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

362 

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

364 ''' 

365 try: 

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

367 _DOT_(_pygeodesy_, name)) 

368 except ImportError as x: 

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

370 

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

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

373 raise AttributeError(_COLONSPACE_(t, _immutable_)) 

374 

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

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

377 

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

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

380 

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

382 

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

384 

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

386 ''' 

387 n = module_name 

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

389 n = _DOT_(_pygeodesy_, n) 

390 m = self.getmodule(n) 

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

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

393 

394 def getmodule(self, name): 

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

396 

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

398 

399 @return: The C{pygeodesy} module. 

400 

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

402 ''' 

403 try: 

404 return _sys.modules[name] 

405 except KeyError: 

406 return import_module(name, _pygeodesy_) 

407 

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

409# try: 

410# if name not in _sys.modules: 

411# _sys.modules[name] = module 

412# except (AttributeError, KeyError): 

413# pass 

414# return module 

415 

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

417 '''Yield the modules imported so far. 

418 ''' 

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

420 yield n, m 

421 

422_ALL_MODS = _ALL_MODS() # PYCHOK singleton 

423 

424__all__ = _ALL_LAZY.lazily 

425__version__ = '23.06.02' 

426 

427 

428def _ALL_OTHER(*objs): 

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

430 ''' 

431 _interns = _ALL_MODS.interns # from pygeodesy import interns 

432 

433 def _interned(o): # get base name 

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

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

436 return getattr(_interns, i, n) 

437 

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

439 

440 

441if _FOR_DOCS: 

442 _ALL_DOCS = _ALL_OTHER 

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

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

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

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

447 # too much internal documentation. 

448else: 

449 def _ALL_DOCS(*unused): 

450 return () 

451 

452 

453def _all_imports(**more): 

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

455 ''' 

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

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

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

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

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

461 imports = _Dict() 

462 _add = imports.add 

463 

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

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

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

467 _add(mod, mod) 

468 for attr in attrs: 

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

470 if as_attr: 

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

472 else: 

473 _add(attr, mod) 

474 return imports 

475 

476 

477def _all_missing2(_all_): 

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

479 ''' 

480 def _diff(one, two): 

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

482 

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

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

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

486 

487 

488def _caller3(up): # in .named 

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

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

491 ''' 

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

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

494 f = _sys._getframe(up + 1) 

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

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

497 f.f_lineno) # line number 

498 

499 

500def _lazy_import2(package_name): # MCCABE 14 

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

502 

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

504 the imports, to help facilitate resolving 

505 relative imports, usually C{__package__}. 

506 

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

508 for easy reference within itself and the callable to 

509 be set to `__getattr__`. 

510 

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

512 an import failed or the package name or 

513 module name or attribute name is invalid 

514 or does not exist. 

515 

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

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

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

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

520 without causing a C{ModuleNotFoundError}. 

521 

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

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

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

525 ''' 

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

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

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

529 

530 package, parent = _lazy_init2(_pygeodesy_) 

531 

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

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

534 imports = _all_imports() 

535 

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

537 # only called once for each undefined pygeodesy attribute 

538 if name in imports: 

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

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

541 # note in the _lazy_import2.__doc__ above). 

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

543 if mod not in imports: 

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

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

546 pkg = getattr(imported, _p_a_c_k_a_g_e_, None) 

547 if pkg not in packages: # invalid package 

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

549 # _ALL_MODS._imported(mod, imported) 

550 # import the module or module attribute 

551 if attr: 

552 imported = getattr(imported, attr, MISSING) 

553 elif name != mod: 

554 imported = getattr(imported, name, MISSING) 

555 if imported is MISSING: # PYCHOK no cover 

556 t = _DOT_(mod, attr or name) 

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

558 

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

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

561 mod = NN 

562 else: # PYCHOK no cover 

563 t = _no_(_module_, _or_, _attribute_) 

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

565 

566 setattr(package, name, imported) 

567 if isLazy > 1: 

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

569 if mod and mod != name: 

570 t = NN(t, _from_DOT__, mod) 

571 if isLazy > 2: 

572 try: # see C{_caller3} 

573 _, f, s = _caller3(2) 

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

575 except ValueError: 

576 pass 

577 printf(t) # XXX print 

578 

579 return imported # __getattr__ 

580 

581 return package, __getattr__ # _lazy_import2 

582 

583 

584def _lazy_init2(package_name): 

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

586 

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

588 the imports, to help facilitate resolving 

589 relative imports, usually C{__package__}. 

590 

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

592 for easy reference within itself and its name aka the 

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

594 

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

596 an import failed or the package name is 

597 invalid or does not exist. 

598 

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

600 ''' 

601 global isLazy 

602 

603 z = _getenv(_PYGEODESY_LAZY_IMPORT_, None) 

604 if z is None: # _PYGEODESY_LAZY_IMPORT_ not set 

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

606 else: 

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

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

609 if isLazy < 1: # not enabled 

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

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

612 isLazy += 1 

613 

614 try: # to initialize in Python 3+ 

615 package = import_module(package_name) 

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

617 if parent != package_name: # assert 

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

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

620 

621 except (AttributeError, ImportError) as x: 

622 isLazy = False # failed 

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

624 

625 return package, parent 

626 

627 

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

629 # from pygeodesy.streprs import pairs 

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

631 

632 

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

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

635 

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

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

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

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

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

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

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

643 and C{B{flush}}. 

644 

645 @return: Number of bytes written. 

646 ''' 

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

648 

649 

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

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

652 

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

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

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

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

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

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

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

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

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

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

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

664 

665 @return: Number of bytes written. 

666 ''' 

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

668 try: 

669 if args: 

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

671 elif kwds: # PYCHOK no cover 

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

673 else: # PYCHOK no cover 

674 t = fmt 

675 except Exception as x: 

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

677 unstr = _ALL_MODS.streprs.unstr 

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

679 nl_nt_prefix_end_file_flush_sep_kwds)) 

680 try: 

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

682 except UnicodeEncodeError: # XXX only Windows 

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

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

685 if fl: 

686 f.flush() 

687 return n 

688 

689 

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

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

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

693 ''' 

694 if nl > 0: 

695 prefix = NN(_NL_ * nl, prefix) 

696 if nt > 0: 

697 end = NN(end, _NL_ * nt) 

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

699 

700 

701if __name__ == '__main__': 

702 

703 from timeit import timeit 

704 

705 def t1(): 

706 from pygeodesy.trf import RefFrame 

707 return RefFrame 

708 

709 def t2(): 

710 return _ALL_MODS.trf.RefFrame 

711 

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

713 

714 t1 = timeit(t1, number=1000000) 

715 t2 = timeit(t2, number=1000000) 

716 v = _Python_(_sys.version) 

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

718 

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

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

721 

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

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

724 

725# % python2 -m pygeodesy.lazily 

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

727 

728# **) MIT License 

729# 

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

731# 

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

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

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

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

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

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

738# 

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

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

741# 

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

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

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

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

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

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

748# OTHER DEALINGS IN THE SOFTWARE.