Coverage for pygeodesy/lazily.py: 98%
201 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-06 12:20 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-06 12:20 -0500
2# -*- coding: utf-8 -*-
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>}.
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}.
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.
21@note: C{Lazy import} applies only to top-level modules of C{pygeodesy}.
22 The C{lazy import} of a top-level module invariably loads all
23 sub-modules imported by that top-level module.
25@note: C{Lazy import} raises a L{LazyAttributeError} or L{LazyImportError}
26 depending on the cause of the error and such errors can occur late,
27 after all initial imports.
28'''
30from pygeodesy import internals as _internals, interns as _interns, \
31 _isfrozen # DON'T _lazy_import2
32# from pygeodesy.errors import _error_init, _xkwds_item2 # _ALL_MODS
33from pygeodesy.internals import _caller3, _DUNDER_nameof, _getPYGEODESY, _headof, \
34 _is_DUNDER_main, printf, _tailof, _versions
35from pygeodesy.interns import NN, _attribute_, _by_, _COLONSPACE_, _COMMASPACE_, \
36 _doesn_t_exist_, _DOT_, _DUNDER_all_, _EQUALSPACED_, \
37 _from_, _HASH_, _immutable_, _line_, _module_, _no_, \
38 _not_, _or_, _pygeodesy_abspath_, _pygeodesy_, _sys, \
39 _SUB_PACKAGES, _UNDER_, _version_, _intern # function
40try:
41 from importlib import import_module
42except ImportError as x: # Python 2.6-
43 raise ImportError(_COLONSPACE_(x, _versions()))
44# import sys as _sys # from .interns
46_a0 = () # PYCHOK empty tuple
47_asSPACED_ = ' as '
48_FOR_DOCS = _getPYGEODESY('FOR_DOCS') # for epydoc ...
49_init__all__ = _FOR_DOCS or _getPYGEODESY('_init__all__', _DUNDER_all_) == _DUNDER_all_ # PYCHOK exported
50_lazily_ = 'lazily'
51_PYTHON_X_DEV = getattr(_sys.flags, 'dev_mode', False) # PYCHOK Python 3.2+
52_unlazy = _unLazy0 = _isfrozen or _internals._MODS.sys_version_info2 < (3, 7) # PYCHOK mod.__getattr__ 3.7+
53_WARNINGS_X_DEV = _getPYGEODESY('WARNINGS') and (_PYTHON_X_DEV or bool(_sys.warnoptions)) # PYCHOK .props
55# @module_property[_RO?] <https://GitHub.com/jtushman/proxy_tools/> <https://discuss.Python.org/t/47379>
56isLazy = None # see @var isLazy in .__init__
59class LazyAttributeError(AttributeError):
60 '''Raised if a C{lazily imported} attribute is missing or invalid.
61 '''
62 def __init__(self, *args, **kwds):
63 _ALL_MODS.errors._error_init(AttributeError, self, args, **kwds)
66class LazyImportError(ImportError):
67 '''Raised if C{lazy import} is not supported, disabled or failed some other way.
68 '''
69 def __init__(self, *args, **kwds):
70 _ALL_MODS.errors._error_init(ImportError, self, args, **kwds)
73class _Dict(dict):
74 '''(INTERNAL) Imports C{dict}.
75 '''
76 _name = NN
78 def __getattr__(self, attr):
79 try:
80 return self[attr]
81 except KeyError:
82 return dict.__getattr__(self, attr)
84# def __setattr__(self, attr, value):
85# if attr in self:
86# self[attr] = value
87# else:
88# dict.__setattr__(self, attr, value)
90 def add(self, name, mod_, *subs):
91 '''Add a C{[name] = mod_} item.
93 @raise AssertionError: The B{C{name}} already exists
94 with a different B{C{mod_}}.
95 '''
96 if name in self: # PYCHOK no cover
97 sub = self[name] # duplicate OK
98 if sub != mod_ and sub not in subs:
99 t = _DOT_(self._name, name)
100 t = _COLONSPACE_(t, repr(sub))
101 t = _COMMASPACE_(t, _not_(repr(mod_)))
102 raise AssertionError(t)
103 else:
104 self[name] = mod_
106 def _NAME(self, which):
107 self._name = _intern(_DUNDER_nameof(which).upper())
110class _NamedEnum_RO(dict):
111 '''(INTERNAL) C{Read_Only} enum-like C{dict} sub-class.
112 '''
113# _name = NN # also first kwd, __init__(_name=...)
115 def _DOT_(self, attr): # PYCHOK no cover
116 return _DOT_(self._name, attr) # PYCHOK _name
118 def __getattr__(self, attr):
119 try:
120 return self[attr]
121 except KeyError:
122 t = self._DOT_(attr)
123 raise LazyAttributeError(t, txt=_doesn_t_exist_)
125 def __setattr__(self, attr, value): # PYCHOK no cover
126 t = _EQUALSPACED_(self._DOT_(attr), repr(value))
127 raise LazyAttributeError(_immutable_, txt=t)
129 def enums(self):
130 # Yield all C{(mod_, tuple)} pairs
131 for m, t in dict.items(self):
132 n = m.replace(_UNDER_, _DOT_)
133 if n != m:
134 if m.startswith(_UNDER_):
135 continue # skip _name= ...
136 u = m.rstrip(_UNDER_)
137 if u != m:
138 u = len(u)
139 n = n[:u] + m[u:]
140 yield n, t
142 def fill_D(self, _D, which):
143 # Fill C{_Dict _D}.
144 _D._NAME(which)
145 _a = _D.add
146 for m, t in self.enums():
147 _a(m, _DOT_(m, NN, NN)) # import module
148 for a in t:
149 a, _, as_ = a.partition(_asSPACED_)
150 if as_: # import attr as attr_
151 _a(as_, _DOT_(m, a, NN), *_SUB_PACKAGES)
152 else:
153 _a(a, m)
154 return _D
157def _a(*names):
158 '''(INTERNAL) Intern all C{names}.
159 '''
160 return tuple(map(_intern, names)) if names else _a0
163def _ALL_ATTRS(*attrs):
164 '''(INTERNAL) Unravel all exported module attributes.
165 '''
166 t = ()
167 for attr in attrs:
168 t += tuple(map(_getattras, attr))
169 return t
172_ALL_INIT = _a(_pygeodesy_abspath_, _version_)
174# __all__ value for most modules, accessible as _ALL_LAZY.<module>
175_ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY',
176 albers=_a('AlbersEqualArea', 'AlbersEqualArea2', 'AlbersEqualArea4',
177 'AlbersEqualAreaCylindrical', 'AlbersEqualAreaNorth', 'AlbersEqualAreaSouth',
178 'AlbersError', 'Albers7Tuple'),
179 auxilats=_a(), # module only
180 azimuthal=_a('AzimuthalError', 'Azimuthal7Tuple',
181 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney',
182 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney',
183 'LambertEqualArea', 'Orthographic', 'Stereographic',
184 'equidistant', 'gnomonic'),
185 basics=_a('clips', 'copysign0', 'copytype', 'halfs2',
186 'int1s', 'isbool', 'isCartesian', 'isclass', 'iscomplex', 'isDEPRECATED', 'isfloat',
187 'isidentifier', 'isinstanceof', 'isint', 'isiterable', 'isiterablen', 'isiterabletype',
188 'iskeyword', 'isLatLon', 'islistuple', 'isNvector', 'isodd',
189 'isscalar', 'issequence', 'isstr', 'issubclassof', 'itemsorted',
190 'len2', 'map1', 'map2', 'max2', 'min2', 'neg', 'neg_',
191 'signBit', 'signOf', 'splice', 'str2ub', 'ub2str', 'unsigned0'),
192 booleans=_a('BooleanFHP', 'BooleanGH', 'LatLonFHP', 'LatLonGH',
193 'isBoolean'),
194 cartesianBase=_a('RadiusThetaPhi3Tuple', 'rtp2xyz', 'rtp2xyz_', 'xyz2rtp', 'xyz2rtp_'),
195 clipy=_a('ClipCS4Tuple', 'ClipFHP4Tuple', 'ClipGH4Tuple', 'ClipLB6Tuple', 'ClipSH3Tuple',
196 'clipCS4', 'clipFHP4', 'clipGH4', 'clipLB6', 'clipSH', 'clipSH3'),
197 css=_a('CassiniSoldner', 'Css', 'CSSError', 'toCss',
198 'EasNorAziRk4Tuple', 'EasNorAziRkEqu6Tuple', 'LatLonAziRk4Tuple'),
199 constants=_a('DIG', 'EPS', 'EPS0', 'EPS02', 'EPS1', 'EPS2', 'EPS4', 'EPS_2',
200 'INF', 'INT0', 'MANT_DIG', 'MAX', 'MAX_EXP', 'MIN', 'MIN_EXP', 'NAN', 'NEG0', 'NINF',
201 'PI', 'PI2', 'PI_2', 'PI3', 'PI_3', 'PI3_2', 'PI4', 'PI_4',
202 'R_FM', 'R_GM', 'R_KM', 'R_M', 'R_MA', 'R_MB', 'R_NM', 'R_QM', 'R_SM', 'R_VM',
203 'float_', 'float0_', 'isclose', 'isfinite', 'isinf', 'isint0',
204 'isnan', 'isnear0', 'isnear1', 'isnear90', 'isneg0', 'isninf', 'isnon0',
205 'remainder'),
206 datums=_a('Datum', 'Datums', 'Transform', 'Transforms'),
207# deprecated=_a(), # module only
208 dms=_a('F_D', 'F_DM', 'F_DMS', 'F_DEG', 'F_MIN', 'F_SEC', 'F_D60', 'F__E', 'F__F', 'F__G', 'F_RAD',
209 'F_D_', 'F_DM_', 'F_DMS_', 'F_DEG_', 'F_MIN_', 'F_SEC_', 'F_D60_', 'F__E_', 'F__F_', 'F__G_', 'F_RAD_',
210 'F_D__', 'F_DM__', 'F_DMS__', 'F_DEG__', 'F_MIN__', 'F_SEC__', 'F_D60__', 'F__E__', 'F__F__', 'F__G__', 'F_RAD__',
211 'S_DEG', 'S_MIN', 'S_SEC', 'S_DMS', 'S_RAD', 'S_SEP',
212 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint',
213 'degDMS', 'latDMS', 'latlonDMS', 'latlonDMS_', 'lonDMS', 'normDMS',
214 'parseDDDMMSS', 'parseDMS', 'parseDMS2', 'parse3llh', 'parseRad', 'precision', 'toDMS'),
215 ecef=_a('EcefError', 'EcefFarrell21', 'EcefFarrell22', 'EcefKarney', 'EcefMatrix',
216 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'),
217 elevations=_a('Elevation2Tuple', 'GeoidHeight2Tuple',
218 'elevation2', 'geoidHeight2'),
219 ellipsoidalBase=_a(), # module only
220 ellipsoidalBaseDI=_a(), # module only
221 ellipsoidalExact=_a(), # module only
222 ellipsoidalGeodSolve=_a(), # module only
223 ellipsoidalKarney=_a(), # module only
224 ellipsoidalNvector=_a(), # module only
225 ellipsoidalVincenty=_a('VincentyError',), # nothing else
226 ellipsoids=_a('a_f2Tuple', 'Circle4Tuple', 'Curvature2Tuple',
227 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids',
228 'a_b2e', 'a_b2e2', 'a_b2e22', 'a_b2e32', 'a_b2f', 'a_b2f_', 'a_b2f2', 'a_b2n',
229 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a',
230 'e2f', 'e22f',
231 'f2e2', 'f2e22', 'f2e32', 'f_2f', 'f2f_', 'f2f2', 'f2n', 'n2e2', 'n2f', 'n2f_'),
232 elliptic=_a('Elliptic', 'EllipticError', 'Elliptic3Tuple'),
233 epsg=_a('Epsg', 'EPSGError'),
234 errors=_a('AuxError', 'ClipError', 'CrossError', 'GeodesicError', 'IntersectionError',
235 'NumPyError', 'LenError', 'LimitError', 'MGRSError',
236 'ParseError', 'PointsError', 'RangeError', 'RhumbError',
237 'SciPyError', 'SciPyWarning', 'TRFError', 'TriangleError', 'UnitError', 'VectorError',
238 'crosserrors', 'exception_chaining', 'isError', 'limiterrors', 'rangerrors'),
239 etm=_a('Etm', 'ETMError', 'ExactTransverseMercator',
240 'parseETM5', 'toEtm8'),
241 fmath=_a('Fdot', 'Fhorner', 'Fhypot', 'Fpolynomial', 'Fpowers', 'Fcbrt', 'Froot', 'Fsqrt',
242 'bqrt', 'cbrt', 'cbrt2', 'euclid', 'euclid_',
243 'facos1', 'fasin1', 'fatan', 'fatan1', 'fatan2', 'favg',
244 'fdot', 'fdot_', 'fdot3', 'fma', 'fmean', 'fmean_', 'fhorner', 'fidw', 'f2mul_',
245 'fpolynomial', 'fpowers', 'fprod', 'frandoms', 'frange', 'freduce', 'fremainder',
246 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_',
247 'norm2', 'norm_', 'sqrt0', 'sqrt3', 'sqrt_a', 'zcrt', 'zqrt'),
248 formy=_a('Radical2Tuple',
249 'angle2chord', 'antipode', 'antipode_', 'bearing', 'bearing_',
250 'chord2angle', 'compassAngle', 'cosineLaw', 'cosineLaw_',
251 'equirectangular', 'equirectangular4', 'euclidean', 'euclidean_',
252 'excessAbc_', 'excessCagnoli_', 'excessGirard_', 'excessLHuilier_',
253 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_',
254 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_',
255 'hartzell', 'haversine', 'haversine_', 'heightOf', 'heightOrthometric', 'horizon', 'hubeny', 'hubeny_',
256 'intersection2', 'intersections2', 'isantipode', 'isantipode_', 'isnormal', 'isnormal_',
257 'normal', 'normal_', 'opposing', 'opposing_', 'radical2',
258 'thomas', 'thomas_', 'vincentys', 'vincentys_'),
259 frechet=_a('Frechet', 'FrechetDegrees', 'FrechetError', 'FrechetRadians', 'FrechetCosineLaw',
260 'FrechetDistanceTo', 'FrechetEquirectangular', 'FrechetEuclidean', 'FrechetExact',
261 'FrechetFlatLocal', 'FrechetFlatPolar', 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney',
262 'FrechetThomas', 'FrechetVincentys', 'Frechet6Tuple',
263 'frechet_'),
264 fstats=_a('Fcook', 'Flinear', 'Fwelford'),
265 fsums=_a('Fsum', 'DivMod2Tuple', 'Fsum2Tuple', 'ResidualError',
266 'f2product', 'fsum', 'fsum_', 'fsumf_', 'fsum1', 'fsum1_', 'fsum1f_', 'nonfiniterrors'),
267 gars=_a('Garef', 'GARSError'),
268 geodesici=_a('Intersectool', 'Intersectool5Tuple', 'Intersect7Tuple',
269 'Intersector', 'Intersector5Tuple', 'Middle5Tuple', 'XDict'),
270 geodesicw=_a('Geodesic', 'GeodesicLine', 'Geodesic_WGS84'),
271 geodesicx=_a('gx', 'gxarea', 'gxbases', 'gxline', # modules
272 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'),
273 geodsolve=_a('GeodesicSolve', 'GeodesicLineSolve', 'GeodSolve12Tuple'),
274 geohash=_a('Geohash', 'Geohashed', 'GeohashError', 'Neighbors8Dict', 'Resolutions2Tuple', 'Sizes3Tuple'),
275 geoids=_a('GeoidError', 'GeoidEGM96', 'GeoidG2012B', 'GeoidKarney', 'GeoidPGM', 'egmGeoidHeights',
276 'PGMError', 'GeoidHeight5Tuple'),
277 hausdorff=_a('Hausdorff', 'HausdorffDegrees', 'HausdorffError', 'HausdorffRadians', 'HausdorffCosineLaw',
278 'HausdorffDistanceTo', 'HausdorffEquirectangular', 'HausdorffEuclidean', 'HausdorffExact',
279 'HausdorffFlatLocal', 'HausdorffFlatPolar', 'HausdorffHaversine', 'HausdorffHubeny',
280 'HausdorffKarney', 'HausdorffThomas', 'HausdorffVincentys', 'Hausdorff6Tuple',
281 'hausdorff_', 'randomrangenerator'),
282 heights=_a('HeightCubic', 'HeightError', 'HeightIDWcosineLaw', 'HeightIDWdistanceTo',
283 'HeightIDWequirectangular', 'HeightIDWeuclidean', 'HeightIDWexact', 'HeightIDWflatLocal',
284 'HeightIDWflatPolar', 'HeightIDWhaversine', 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas',
285 'HeightIDWvincentys', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'),
286 internals=_internals.__all__,
287 interns=_interns.__all__,
288 iters=_a('LatLon2PsxyIter', 'PointsIter', 'points2',
289 'isNumpy2', 'isPoints2', 'isTuple2', 'iterNumpy2', 'iterNumpy2over'),
290 karney=_a('Area3Tuple', 'Caps', 'Direct9Tuple', 'GDict', 'Inverse10Tuple', 'Rhumb8Tuple'),
291 ktm=_a('KTMError', 'KTransverseMercator'),
292 latlonBase=_a('latlon2n_xyz', 'philam2n_xyz'),
293 lazily=_a('LazyAttributeError', 'LazyImportError', 'isLazy'),
294 lcc=_a('Conic', 'Conics', 'Lcc', 'LCCError', 'toLcc'),
295 ltp=_a('Attitude', 'AttitudeError', 'ChLV', 'ChLVa', 'ChLVe', 'Frustum',
296 'LocalCartesian', 'LocalError', 'Ltp', 'tyr3d'),
297 ltpTuples=_a('Aer', 'Aer4Tuple', 'Attitude4Tuple',
298 'ChLVEN2Tuple', 'ChLV9Tuple', 'ChLVYX2Tuple', 'ChLVyx2Tuple',
299 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple', 'Los',
300 'Ned', 'Ned4Tuple', 'Uvw', 'Uvw3Tuple', 'XyzLocal', 'Xyz4Tuple'),
301 mgrs=_a('Mgrs', 'parseMGRS', 'toMgrs', 'Mgrs4Tuple', 'Mgrs6Tuple'),
302 named=_a('ADict',
303 'callername', 'classname', 'classnaming', 'modulename',
304 'nameof', 'notImplemented', 'notOverloaded'),
305 namedTuples=_a('Bearing2Tuple', 'Bounds2Tuple', 'Bounds4Tuple',
306 'Destination2Tuple', 'Destination3Tuple',
307 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple',
308 'EasNor2Tuple', 'EasNor3Tuple', 'Forward4Tuple', 'Intersection3Tuple',
309 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple',
310 'LatLonDatum3Tuple', 'LatLonDatum5Tuple',
311 'LatLonPrec3Tuple', 'LatLonPrec5Tuple',
312 'NearestOn2Tuple', 'NearestOn3Tuple', 'NearestOn6Tuple', 'NearestOn8Tuple',
313 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple',
314 'Reverse4Tuple', 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple',
315 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple',
316 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'),
317 nvectorBase=_a('NorthPole', 'SouthPole', 'n_xyz2latlon', 'n_xyz2philam'),
318 osgr=_a('Osgr', 'OSGRError', 'parseOSGR', 'toOsgr'),
319 points=_a('LatLon_', 'LatLon2psxy', 'Numpy2LatLon', 'Shape2Tuple', 'Tuple2LatLon',
320 'areaOf', 'boundsOf', 'centroidOf', 'fractional',
321 'isclockwise', 'isconvex', 'isconvex_', 'isenclosedBy', 'ispolar',
322 'luneOf', 'nearestOn5', 'perimeterOf', 'quadOf'),
323 props=_a('Property', 'Property_RO', 'property_doc_',
324 'property_RO', 'property_ROnce', 'property_ROver',
325 'deprecated_class', 'deprecated_function', 'deprecated_method',
326 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'),
327 resections=_a('Collins5Tuple', 'ResectionError', 'Survey3Tuple', 'Tienstra7Tuple',
328 'TriAngle5Tuple', 'TriSide2Tuple', 'TriSide4Tuple',
329 'cassini', 'collins5', 'pierlot', 'pierlotx', 'tienstra7',
330 'snellius3', 'wildberger3',
331 'triAngle', 'triAngle5', 'triArea', 'triSide', 'triSide2', 'triSide4'),
332 rhumb=_a(), # module only
333 rhumb_aux_=_a('RhumbAux', 'RhumbLineAux'),
334 rhumb_ekx=_a('Rhumb', 'RhumbLine'),
335 rhumb_solve=_a('RhumbSolve', 'RhumbLineSolve', 'RhumbSolve7Tuple'),
336 sphericalBase=_a(), # module only
337 sphericalNvector=_a(), # module only
338 sphericalTrigonometry=_a(), # module only
339 simplify=_a('simplify1', 'simplifyRDP', 'simplifyRW', 'simplifyVW'),
340 solveBase=_a(), # module only
341 streprs=_a('anstr', 'attrs', 'enstr2', 'fstr', 'fstrzs', 'hstr', 'instr',
342 'lrstrip', 'pairs', 'reprs', 'strs', 'unstr'),
343 trf=_a('RefFrame', 'RefFrames', 'TransformXform', 'TRFXform', 'TRFXform7Tuple',
344 'date2epoch', 'epoch2date', 'trfTransform0', 'trfTransforms', 'trfXform'),
345 triaxials=_a('BetaOmega2Tuple', 'BetaOmega3Tuple', 'Jacobi2Tuple',
346 'JacobiConformal', 'JacobiConformalSpherical',
347 'Triaxial', 'Triaxial_', 'TriaxialError', 'Triaxials', 'hartzell4'),
348 units=_a('Azimuth', 'Band', 'Bearing', 'Bearing_', 'Bool',
349 'Degrees', 'Degrees_', 'Degrees2', 'Distance', 'Distance_', 'Easting', 'Epoch',
350 'Feet', 'FIx', 'Float_', 'Height', 'Height_', 'HeightX', 'Int_',
351 'Lam', 'Lamd', 'Lat', 'Lat_', 'Lon', 'Lon_',
352 'Meter', 'Meter_', 'Meter2', 'Meter3', 'Northing', 'Number_',
353 'Phi', 'Phid', 'Precision_', 'Radians', 'Radians_', 'Radians2',
354 'Radius_', 'Scalar', 'Scalar_', 'Zone'),
355 unitsBase=_a('Float', 'Int', 'Radius', 'Str'),
356 ups=_a('Ups', 'UPSError', 'parseUPS5', 'toUps8', 'upsZoneBand5'),
357 utily=_a('acos1', 'acre2ha', 'acre2m2', 'asin1', 'atan1', 'atan1d', 'atan2', 'atan2b', 'atan2d',
358 'chain2m', 'circle4', 'cot', 'cot_', 'cotd', 'cotd_',
359 'degrees', 'degrees90', 'degrees180', 'degrees360', 'degrees2grades', 'degrees2m',
360 'fathom2m', 'ft2m', 'furlong2m', # 'degrees2grades as degrees2gons',
361 'grades', 'grades400', 'grades2degrees', 'grades2radians',
362# 'grades as gons', 'grades400 as gons400', 'grades2degrees as gons2degrees', 'grades2radians as gons2radians',
363 'ha2acre', 'ha2m2', 'hav', 'km2m',
364 'm2acre', 'm2chain', 'm2degrees', 'm2fathom', 'm2ft', 'm2furlong',
365 'm2ha', 'm2km', 'm2NM', 'm2radians', 'm2SM', 'm2toise', 'm2yard',
366 'NM2m', 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m',
367 'sincos2', 'SinCos2', 'sincos2_', 'sincos2d', 'sincos2d_', 'sincostan3', 'sincostan3d', 'SM2m',
368 'tan', 'tan_', 'tand', 'tand_', 'tan_2', 'tanPI_2_2', 'toise2m', 'truncate',
369 'unroll180', 'unrollPI',
370 'wrap90', 'wrap180', 'wrap360', 'wrapPI_2', 'wrapPI', 'wrapPI2', 'wrap_normal',
371 'yard2m'),
372 utm=_a('Utm', 'UTMError', 'parseUTM5', 'toUtm8', 'utmZoneBand5'),
373 utmups=_a('UtmUps', 'UTMUPSError', 'parseUTMUPS5', 'toUtmUps8',
374 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'),
375 utmupsBase=_a(), # module only
376 vector2d=_a('Circin6Tuple', 'Circum3Tuple', 'Circum4Tuple', 'Meeus2Tuple', 'Radii11Tuple', 'Soddy4Tuple', 'Triaxum5Tuple',
377 'circin6', 'circum3', 'circum4', 'circum4_', 'meeus2', 'radii11', 'soddy4', 'triaxum5', 'trilaterate2d2'),
378 vector3d=_a('Vector3d', 'intersection3d3', 'iscolinearWith', 'nearestOn', 'nearestOn6', 'parse3d',
379 'trilaterate3d2'),
380 vector3dBase=_a(), # module only
381 webmercator=_a('Wm', 'WebMercatorError', 'parseWM', 'toWm', 'EasNorRadius3Tuple'),
382 wgrs=_a('Georef', 'WGRSError'),)
384_ALL_DEPRECATED = _NamedEnum_RO(_name='_ALL_DEPRECATED',
385 deprecated=_a('bases', 'datum', 'nvector', # DEPRECATED modules and ...
386 'rhumbaux', 'rhumbBase', 'rhumbsolve', 'rhumbx'), # ... names
387 deprecated_bases=_a('LatLonHeightBase', 'points2'),
388 deprecated_classes=_a('ClipCS3Tuple', 'EasNorExact4Tuple', 'EcefCartesian', 'Fn_rt',
389 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert',
390 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert',
391 'HeightIDW', 'HeightIDW2', 'HeightIDW3', 'HeightIDWcosineAndoyerLambert',
392 'HeightIDWcosineForsytheAndoyerLambert', 'Helmert7Tuple',
393 'Lam_', 'LatLonExact4Tuple', 'NearestOn4Tuple', 'Ned3Tuple',
394 'Phi_', 'RefFrameError', 'Rhumb7Tuple', 'RhumbOrder2Tuple',
395 'Transform7Tuple', 'TriAngle4Tuple', 'UtmUps4Tuple', 'XDist'),
396 deprecated_consterns=_a('EPS1_2', 'MANTIS', 'OK'),
397 deprecated_datum=_a('Curvature2Tuple', 'Datum', 'Ellipsoid', 'Transform', # assert
398 'Datums', 'Ellipsoids', 'Transforms',
399 'R_FM', 'R_KM', 'R_M', 'R_MA', 'R_MB', 'R_NM', 'R_SM', 'R_VM'),
400 deprecated_functions=_a('anStr', 'areaof', 'atand', 'bounds', # most of the DEPRECATED functions, except ellipsoidal ...
401 'clipCS3', 'clipDMS', 'clipStr', 'collins', 'copysign', # ... and spherical flavors
402 'cosineAndoyerLambert', 'cosineAndoyerLambert_',
403 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_',
404 'decodeEPSG2', 'encodeEPSG', 'enStr2', 'equirectangular_', 'equirectangular3',
405 'excessAbc', 'excessGirard', 'excessLHuilier',
406 'false2f', 'falsed2f', 'float0', 'fStr', 'fStrzs', 'Fsum2product',
407 'hypot3', 'inStr', 'isenclosedby', 'istuplist',
408 'joined', 'joined_', 'nearestOn3', 'nearestOn4',
409 'parseUTM', 'perimeterof', 'polygon',
410 'scalar', 'simplify2', 'simplifyRDPm', 'simplifyVWm',
411 'tienstra', 'toUtm', 'triAngle4',
412 'unsign0', 'unStr', 'utmZoneBand2'),
413 deprecated_nvector=_a('LatLonNvectorBase', 'Nvector', 'sumOf', 'NorthPole', 'SouthPole'),)
416class _ALL_MODS(_internals._MODS_Base):
417 '''(INTERNAL) Memoized import of any L{pygeodesy} module.
418 '''
419 def __getattr__(self, name):
420 '''Get a C{pygeodesy} module or attribute by B{C{name}}.
422 @arg name: Un/qualified module or qualified attribute name (C{str}).
424 @raise ImportError: Importing module B{C{name}} failed.
426 @raise AttributeError: No attribute named B{C{name}}.
427 '''
428 try:
429 v = _lazy_dict[name] # package.__dict__
430 except KeyError:
431 v = _lazy_module(name) # package.__getattr__
432 if _tailof(_DUNDER_nameof(v)) != name:
433 try:
434 v = getattr(v, _tailof(name))
435 except AttributeError:
436 pass # XXX LazyAttributeError?
437 return v
439 def getattr(self, name, *attr_dflt): # , parent=_pygeodesy_
440 '''Get an attribute of/or a C{pygeodesy} module.
442 @arg name: Un/qualified module name (C{str}).
443 @arg attr_dflt: Optional attribute name (C{str}) and
444 optional default value (any C{type}).
446 @return: The C{pygeodesy} module's attribute value.
448 @raise ImportError: Importing module B{C{name}} failed.
450 @raise AttributeError: No attribute named B{C{attr}}.
451 '''
452 v = self.getmodule(name)
453 if attr_dflt:
454 v = getattr(v, *attr_dflt)
455 return v
457 def getmodule(self, name, parent=_pygeodesy_):
458 '''Get a C{pygeodesy} module or the C{__main__}.
460 @arg name: Un/qualified module name (C{str}).
462 @return: The C{pygeodesy} module.
464 @raise ImportError: Importing module B{C{name}} failed.
465 '''
466 if _headof(name) != parent and not _is_DUNDER_main(name):
467 name = _DOT_(parent, name)
468 try:
469 return _sys.modules[name]
470 except KeyError:
471 return _getmodule(name, parent)
473 def imported(self, name):
474 '''Return module or package C{name} if already imported.
475 '''
476 return _sys.modules.get(name, None)
478 def into(self, **mod_DUNDER_name):
479 '''Lazily import module C{mod} into module C{DUNDER_name}
480 and set C{DUNDER_name._mod} to module C{mod}, I{once}.
481 '''
482 class _Into(object):
484 def __getattr__(unused, name):
485 mod, dun = self.errors._xkwds_item2(mod_DUNDER_name)
486 _mod = _UNDER_(NN, mod)
487 d = self.getmodule(dun) # '__main__' OK
488 i = _getattribute(d, _mod, dun)
489 assert isinstance(i, _Into)
490 m = self.getmodule(mod)
491 setattr(d, _mod, m) # overwrite C{d._mod}
492 return getattr(m, name)
494 return _Into()
496# @_Property_RO
497# def _isBoolean(self):
498# '''(INTERNAL) Get function C(.booleans.isBoolean}, I{once}.
499# '''
500# return self.booleans.isBoolean
502 def items(self): # no module named 'items'
503 '''Yield the modules imported so far.
504 '''
505 for n, m in _sys.modules.items():
506 if _headof(n) == _pygeodesy_:
507 yield n, m
509_internals._MODS = _ALL_MODS = _ALL_MODS() # PYCHOK singleton
511__all__ = _ALL_LAZY.lazily
512__version__ = '25.01.05'
515def _ALL_OTHER(*objs):
516 '''(INTERNAL) Get class and function B{C{objs}} for __all__.
517 '''
518 def _interned(o): # intern'd base name
519 n = _tailof(_DUNDER_nameof(o))
520 i = NN(_UNDER_, n, _UNDER_) # intern'd
521 return getattr(_interns, i, n)
523 return tuple(map(_interned, objs)) # map2
526if _FOR_DOCS: # PYCHOK no cover
527 _ALL_DOCS = _ALL_OTHER
528 # (INTERNAL) Only export B{C{objs.__name__}} when making the
529 # docs to force C{epydoc} to include certain classes, methods,
530 # functions and other names in the documentation. Using the
531 # C{epydoc --private ...} command line option tends to include
532 # too much internal documentation.
533else:
534 def _ALL_DOCS(*unused):
535 return ()
538def _all_deprecates():
539 '''(INTERNAL) Build C{dict} of all deprecated imports.
540 '''
541 _D = _ALL_DEPRECATES
542 if not _D:
543 _ALL_DEPRECATED.fill_D(_D, _all_deprecates) # see _all_imports()
544 return _D
546_ALL_DEPRECATES = _Dict() # PYCHOK _ALL_DEPRECATED.imports()
549def _all_enums():
550 '''(INTERNAL) Yield all C{(mod_, tuple)} pairs for C{__init__._all}.
551 '''
552 # assert _init__all__
553 for mod_t in _ALL_LAZY.enums():
554 yield mod_t
555 if _FOR_DOCS:
556 for mod_t in _ALL_DEPRECATED.enums():
557 yield mod_t
560def _all_imports():
561 '''(INTERNAL) Build C{dict} of all lazy imports.
562 '''
563 # imports naming conventions stored below - [<key>] = <from>:
564 # import <module> - [<module>] = <module>
565 # from <module> import <attr> - [<attr>] = <module>
566 # from pygeodesy import <attr> - [<attr>] = <attr>
567 # from <module> import <attr> as <name> - [<name>] = <module>.<attr>.
568 _D = _ALL_IMPORTS
569 if not _D:
570 _ALL_LAZY.fill_D(_D, _all_imports) # see _all_deprecates()
571 return _D
573_ALL_IMPORTS = _Dict() # PYCHOK _ALL_LAZY.imports()
576def _all_missing2(_all_):
577 '''(INTERNAL) Get diffs between pygeodesy.__all__ and lazily._all_imports.
578 '''
579 def _diff(one, two):
580 return tuple(sorted(a for a in one if a not in two))
582 _alzy = _Dict((a, a) for a in _ALL_INIT)
583 _alzy.update(_all_imports()) # without _all_backups!
584 return ((_DOT_(_lazily_, _all_imports.__name__), _diff(_all_, _alzy)),
585 (_DOT_(_pygeodesy_, _DUNDER_all_), _diff(_alzy.keys(), _all_)))
588def _getattras(attr_as): # test/testDeprecated
589 '''(INTERNAL) Get the C{"as name"} or C{"name"} of a lazy entry.
590 '''
591 a_, _, as_ = attr_as.partition(_asSPACED_)
592 return as_ or a_.rstrip(_DOT_)
595def _getattribute(m, name, mod=_pygeodesy_):
596 '''(INTERNAL) Get attr C{m.name}.
597 '''
598 try:
599 return getattr(m, name)
600 except AttributeError:
601 name = _DOT_(mod, name)
602 # <https://GitHub.com/mrJean1/PyGeodesy/issues/76>
603 raise LazyAttributeError(_no_(_attribute_), txt=name)
606def _getmodule(name, *parent):
607 '''(INTERNAL) Wrapper for C{import_module}.
608 '''
609 try:
610 return import_module(name, parent)
611 except ImportError:
612 # <https://GitHub.com/mrJean1/PyGeodesy/issues/76>
613 raise LazyImportError(_no_(_module_), txt=name)
616# def _lazy_attributes(DUNDER_name):
617# '''(INTERNAL) Return a function to C{B{__name__}.__getattr__(attr)}
618# on lazily imported modules and sub-modules.
619# '''
620# if _unlazy:
621# raise AssertionError(_COMMASPACE_(DUNDER_name, _not_(_DEPRECATED_)))
622#
623# def _getattr(attr, *dflt):
624# try: # a module name
625# return _ALL_MODS.getmodule(attr)
626# except (AttributeError, ImportError):
627# return _ALL_MODS.getattr(DUNDER_name, attr, *dflt)
628#
629# return _getattr
632_lazy_dict = {} # PYCHOK overwritten by _lazy_import2
635def _lazy_import2(pack): # MCCABE 14
636 '''Check for and set up C{lazy import}.
638 @arg pack: The name of the package (C{str}) performing the imports,
639 to help resolving relative imports, usually C{__package__}.
641 @return: 2-Tuple C{(package, getattr)} of the importing package for
642 easy reference within itself and the callable to be set to
643 C{package.__getattr__}.
645 @raise LazyAttributeError: The package, module or attribute name is
646 invalid or does not exist.
648 @raise LazyImportError: Lazy import not supported or not enabled or
649 an import failed.
651 @note: This is I{Brett Cannon}'s function U{modutil.lazy_import
652 <https://GitHub.com/brettcannon/modutil/blob/master/modutil.py>}
653 modified to handle the C{__all__} and C{__dir__} attributes and
654 call C{importlib.import_module(<module>.<name>, ...)} without
655 causing a C{ModuleNotFoundError}.
657 @see: The original U{modutil<https://PyPI.org/project/modutil>},
658 U{PEP 562<https://www.Python.org/dev/peps/pep-0562>} and the
659 U{new way<https://Snarky.Ca/lazy-importing-in-python-3-7/>}.
660 '''
661 _DOT_ = _interns._DOT_
662 _SPACE_ = _interns._SPACE_
664 if pack != _pygeodesy_ or _unlazy: # Python 3.7+ # PYCHOK no cover
665 t = _DOT_(pack, _DUNDER_nameof(_lazy_import2))
666 raise LazyImportError(_no_(t), txt=_versions())
668 package, parent = _lazy_init2(pack) # _pygeodesy_
670 _DUNDER_package_ = '__package__'
671 _lazily_imported_ = _SPACE_(_HASH_, _lazily_, 'imported', parent)
673 sub_packages = set((parent, NN) + tuple(
674 _DOT_(parent, s) for s in _SUB_PACKAGES))
675 imports = _all_imports()
676 deprecates = _all_deprecates()
678 def __getattr__(name): # __getattr__ only for Python 3.7+
679 # only called once for each undefined pygeodesy attribute
680 mod = imports.get(name, NN) or deprecates.get(name, NN)
681 if mod:
682 # importlib.import_module() implicitly sets sub-modules
683 # on this module as appropriate for direct imports (see
684 # note in the _lazy_import2.__doc__ above).
685 if mod.endswith(_DOT_): # import mod[.attr] as name
686 mod, _, attr = mod[:-1].rpartition(_DOT_)
687 else: # from mod import name
688 attr = name
689 v = _getmodule(_DOT_(pack, mod), parent)
690 t = getattr(v, _DUNDER_package_, None)
691 if t not in sub_packages: # invalid module package
692 raise LazyImportError(_DOT_(mod, _DUNDER_package_), t)
693 if attr: # get mod.attr
694 v = _getattribute(v, attr, mod)
696 elif name in (_DUNDER_all_,): # XXX _DUNDER_dir_, _DUNDER_members_?
697 v = _ALL_INIT + tuple(imports.keys())
698 else: # PYCHOK no cover
699 t = _no_(_module_, _or_, _attribute_)
700 # <https://GitHub.com/mrJean1/PyGeodesy/issues/76>
701 raise LazyAttributeError(t, txt=_DOT_(parent, name))
703 setattr(package, name, v) # package.__dict__[name] = val
704 if isLazy > 1:
705 t = _DOT_(_lazily_imported_, name)
706 if mod and _tailof(mod) != name:
707 t = _SPACE_(t, _from_, _DOT_(NN, mod))
708 if isLazy > 2:
709 try: # see C{_caller3}
710 _, f, s = _caller3(2)
711 t = _SPACE_(t, _by_, f, _line_, s)
712 except ValueError:
713 pass
714 printf(t) # XXX print
716 return v # __getattr__
718 global _lazy_dict, _lazy_module
719 _lazy_dict = package.__dict__
720 _lazy_module = __getattr__
722 return package, __getattr__ # _lazy_import2
725# def _lazy_import_all(DUNDER_name):
726# '''(INTERNAL) Return a function mimicking C{from B{__name__} import *},
727# of all items, see .deprecated.__init__
728# '''
729# if _unlazy:
730# raise AssertionError(_COMMASPACE_(DUNDER_name, _not_(_DEPRECATED_)))
731#
732# _getattr = _lazy_attributes(DUNDER_name) # __name__.__getattr__
733# _import_start = _lazy_import_star(DUNDER_name, ALL_=_ALL_IMPORTS)
734#
735# def _import_all(attr, *dflt):
736# return _import_star(DUNDER_name) if attr == _DUNDER_all_ else \
737# _getattr(attr, *dflt)
738#
739# return _import_all
742def _lazy_import_as(DUNDER_name):
743 '''(INTERNAL) Return a function to C{import B{__name__}.mod as mod}
744 I{of modules only}, see .deprecated, .rhumb or get an attribute
745 lazily exported by C{__name__}.
746 '''
747 if _unlazy:
748 return None
750 def _import_as(mod):
751 try:
752 return _ALL_MODS.getmodule(_DOT_(DUNDER_name, mod))
753 except ImportError:
754 return _lazy_module(mod)
756 return _import_as
759# def _lazy_import_star(DUNDER_name, ALL_=_ALL_DEPRECATES):
760# '''(INTERNAL) Return a function to mimick C{from B{__name__} import *},
761# of all DEPRECATED items, see .deprecated, .testDeprecated
762# '''
763# if _unlazy:
764# raise AssertionError(_COMMASPACE_(DUNDER_name, _not_(_DEPRECATED_)))
765#
766# def _import_star(_into_):
767# '''Do C{from B{__name__} import *} inside module C{B{__into__}}.
768# '''
769# d = dict()
770# nm = _tailof(DUNDER_name)
771# _g = _ALL_MODS.getattr # pygeodesy.__getattr__
772# for a, m in ALL_.items():
773# if _headof(m) == nm:
774# try:
775# d[a] = _g(m, a)
776# except (AttributeError, ImportError):
777# pass
778# _sys.modules[_into_].__dict__.update(d)
779# return d.keys() # imported names
780#
781# return _import_star
784def _lazy_init2(pack):
785 '''(INTERNAL) Initialize lazy import and set globals C{isLazy} and C{_unLazy0}.
787 @arg pack: The name of the package (C{str}) performing the imports,
788 to help resolving relative imports, usually C{__package__}.
790 @return: 2-Tuple C{(package, parent)} with the importing C{package}
791 for easy reference within itself and its name aka the
792 C{parent}, same as B{C{pack}}.
794 @raise LazyImportError: Lazy import not supported or not enabled,
795 an import failed or the package name is
796 invalid or does not exist.
798 @note: Global C{isLazy} is set accordingly.
799 '''
800 global isLazy, _unLazy0
802 z = _getPYGEODESY('LAZY_IMPORT', None)
803 if z is None: # not set, but ...
804 isLazy = 1 # ... default on 3.7+
805 else:
806 z = z.strip() # like PYTHONVERBOSE et.al.
807 isLazy = int(z) if z.isdigit() else (1 if z else 0)
809 _unLazy0 = _unlazy or not isLazy # pre-3.7 or w/o lazy import
811 if isLazy < 1: # invalid, not enabled
812 e = _internals._PYGEODESY('LAZY_IMPORT')
813 raise LazyImportError(e, repr(z), txt_not_='enabled')
814 if _sys.flags.verbose: # PYCHOK no cover
815 isLazy += 1
817 try: # to initialize in Python 3+
818 package = import_module(pack)
819 parent = package.__spec__.parent # __spec__ only in Python 3.7+
820 if parent != pack: # assert
821 t = _COMMASPACE_(parent, _not_(pack)) # PYCHOK no cover
822 raise AttributeError(_EQUALSPACED_('parent', t))
824 except (AttributeError, ImportError) as x:
825 isLazy = False # failed
826 z = _DUNDER_nameof(_lazy_init2)
827 raise LazyImportError(z, pack, cause=x)
829 return package, parent
832def _lazy_module(name): # overwritten by _lazy_import2
833 '''(INTERNAL) Get or import a C{pygeodesy} module.
834 '''
835 try: # most likely ... module has been imported
836 m = _ALL_MODS.getmodule(name)
837 except (AttributeError, ImportError) as x:
838 raise LazyImportError(name, cause=x)
839 _lazy_dict[name] = m # cache
840 return m
843# def _lazy_subs(DUNDER_name, force=_FOR_DOCS, over=False):
844# '''(INTERNAL) Return the names of a package's sub-packages and
845# update the package's C{__dict__} accordingly.
846# '''
847# sm = dict()
848# if force and not _is_DUNDER_main(DUNDER_name):
849# nm = _tailof(DUNDER_name)
850# _a = _ALL_MODS.getattr
851# _m = _ALL_MODS.getmodule
852# d = _a(DUNDER_name, _DUNDER_dict_, {})
853# for n in _a(DUNDER_name, _DUNDER_all_, ()):
854# try: # n is a class name, get its mod name
855# m = _a(DUNDER_name, n).__module__
856# n, s = m.split(_DOT_)[-2:]
857# if n == nm and s not in sm:
858# # like import m as s
859# m = _m(m)
860# sm[s] = m if over else d.get(s, m)
861# except (AttributeError, ImportError, ValueError) as x:
862# pass
863# d.update(sm)
864#
865# return _ALL_OTHER(*sm.values())
867# del _a, _a0
869if _is_DUNDER_main(__name__): # PYCHOK no cover
871 def _main():
872 from timeit import timeit
874 def t1():
875 from pygeodesy.trf import RefFrame
876 return RefFrame
878 def t2():
879 return _ALL_MODS.trf.RefFrame
881 assert t1() is t2() # prime each
883 t1 = timeit(t1, number=1000000)
884 t2 = timeit(t2, number=1000000)
885 A = _DUNDER_nameof(_ALL_MODS.__class__)
886 v = _versions()
887 printf('%.6f import vs %.6f %s: %.2fX, %s', t1, t2, A, (t1 / t2), v)
889 _main()
891# % python3.13 -W ignore -m pygeodesy.lazily
892# 0.106602 import vs 0.078136 _ALL_MODS: 1.36X, pygeodesy 24.10.24 Python 3.13.0 64bit arm64 macOS 14.6.1
894# % python3.12 -W ignore -m pygeodesy.lazily
895# 0.138844 import vs 0.080458 _ALL_MODS: 1.73X, pygeodesy 24.10.24 Python 3.12.7 64bit arm64 macOS 14.6.1
897# % python3.11 -W ignore -m pygeodesy.lazily
898# 0.387520 import vs 0.254229 _ALL_MODS: 1.52X, pygeodesy 24.10.24 Python 3.11.5 64bit arm64 macOS 14.6.1
900# % python3.10 -W ignore -m pygeodesy.lazily
901# 0.371269 import vs 0.272897 _ALL_MODS: 1.36X, pygeodesy 24.10.24 Python 3.10.8 64bit arm64 macOS 14.6.1
903# % python3.8 -W ignore -m pygeodesy.lazily
904# 0.555572 import vs 0.370304 _ALL_MODS: 1.50X, pygeodesy 24.10.24 Python 3.8.10 64bit arm64_x86_64 macOS 10.16
906# % python2 -m pygeodesy.lazily
907# 1.160292 import vs 0.490279 _ALL_MODS: 2.37X, pygeodesy 24.10.24 Python 2.7.18 64bit arm64_x86_64 macOS 10.16
909# **) MIT License
910#
911# Copyright (C) 2018-2025 -- mrJean1 at Gmail -- All Rights Reserved.
912#
913# Permission is hereby granted, free of charge, to any person obtaining a
914# copy of this software and associated documentation files (the "Software"),
915# to deal in the Software without restriction, including without limitation
916# the rights to use, copy, modify, merge, publish, distribute, sublicense,
917# and/or sell copies of the Software, and to permit persons to whom the
918# Software is furnished to do so, subject to the following conditions:
919#
920# The above copyright notice and this permission notice shall be included
921# in all copies or substantial portions of the Software.
922#
923# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
924# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
925# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
926# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
927# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
928# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
929# OTHER DEALINGS IN THE SOFTWARE.