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_, _COMMASPACE_, \
27                             _doesn_t_exist_, _DOT_, _dunder_name, \
28                             _enabled_, _EQUALSPACED_, _immutable_, \
29                             _isclockwise_, _ispolar_, _module_, \
30                             _NL_, _no_, _not_, _or_, _perimeterOf_, \
31                             _Python_, _pygeodesy_abspath_, _sep_, \
32                             _SPACE_, _UNDER_, _version_
33
34from os import getenv as _getenv  # in .errors, .geodsolve, .props, .units
35from os.path import basename as _basename
36import sys as _sys  # in .props
37try:
38    from importlib import import_module
39except ImportError:  # no import_module in Python 2.6-
40
41    def import_module(name, package=None):  # PYCHOK
42        raise LazyImportError(name=name, package=package,
43                              txt=_no_(import_module.__name__))
44
45_a_l_l_            = '__all__'
46_FOR_DOCS          = _getenv('PYGEODESY_FOR_DOCS', NN)  # for epydoc ...
47_imports_          = 'imports'
48_p_a_c_k_a_g_e_    = '__package__'
49_PYGEODESY_LAZY_IMPORT_  = 'PYGEODESY_LAZY_IMPORT'
50_PYTHON_X_DEV      =  getattr(_sys, '_xoptions', {}).get('dev',  # Python 3.2
51                     _getenv('PYTHONDEVMODE', NN))  # PYCHOK exported
52_sub_packages      = 'deprecated' , 'geodesicx'
53_sys_version_info2 = _sys.version_info[:2]  # in .fmath, .geodsolve
54
55# @module_property[_RO?] <https://GitHub.com/jtushman/proxy_tools/>
56isLazy = None  # see @var isLazy
57
58
59class LazyImportError(ImportError):
60    '''Raised if C{lazy import} is not supported, disabled or failed some other way.
61    '''
62    def __init__(self, *name_value, **txt):
63        from pygeodesy.errors import _error_init
64        _error_init(ImportError, self, name_value, **txt)
65
66
67class _Dict(dict):
68    '''(INTERNAL) Imports C{dict}.
69    '''
70    def add(self, key, value, *values):
71        '''Add C{[key] = value}, typically C{[attr] = mod}.
72
73           @raise AssertionError: The B{C{key}} already
74                                  exists with different
75                                  B{C{value}}.
76        '''
77        if key in self:
78            val = self[key]  # duplicate OK
79            if val != value and val not in values:  # PYCHOK no cover
80                from pygeodesy.streprs import Fmt as _Fmt
81                t = _Fmt.SQUARE(_imports_, key), val, value
82                raise AssertionError('%s: %r, not %r' % t)
83        else:
84            self[key] = value
85
86
87class _NamedEnum_RO(dict):
88    '''(INTERNAL) C{Read_Only} enum-like C{dict} sub-class.
89    '''
90#   _name = NN  # also first kwd, __init__(_name=...)
91
92    def _DOT_(self, attr):
93        return _DOT_(self._name, attr)  # PYCHOK _name
94
95    def __getattr__(self, attr):
96        try:
97            return self[attr]
98        except KeyError:
99            from pygeodesy.errors import _AttributeError
100            raise _AttributeError(self._DOT_(attr), txt=_doesn_t_exist_)
101
102    def __setattr__(self, attr, value):  # PYCHOK no cover
103        from pygeodesy.errors import _TypeError
104        t = _EQUALSPACED_(self._DOT_(attr), value)
105        raise _TypeError(t, txt=_immutable_)
106
107    def enums(self):
108        for k, v in dict.items(self):
109            if not k.startswith(_UNDER_):  # skip _name
110                yield k, v
111
112
113_ALL_INIT = _pygeodesy_abspath_, _version_
114
115# __all__ value for most modules, accessible as _ALL_LAZY.<module>
116_ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY',
117                         albers=('AlbersEqualArea', 'AlbersEqualArea2', 'AlbersEqualArea4',
118                                 'AlbersEqualAreaCylindrical', 'AlbersEqualAreaNorth', 'AlbersEqualAreaSouth',
119                                 'AlbersError', 'Albers7Tuple'),
120                      azimuthal=('AzimuthalError', 'Azimuthal7Tuple',
121                                 'Equidistant', 'EquidistantExact', 'EquidistantGeodSolve', 'EquidistantKarney',
122                                 'Gnomonic', 'GnomonicExact', 'GnomonicGeodSolve', 'GnomonicKarney',
123                                 'LambertEqualArea', 'Orthographic', 'Stereographic',
124                                 'equidistant', 'gnomonic'),
125                         basics=('clips', 'copysign0', 'copytype', 'halfs2',
126                                 'isbool', 'isclass', 'isfinite', 'isidentifier', 'isinf', 'isint', 'iskeyword',
127                                 'isnan', 'isnear0', 'isneg0', 'isnon0', 'isodd', 'isscalar', 'issequence', 'isstr', 'issubclassof',
128                                 'len2', 'map1', 'map2', 'neg', 'neg_',
129                                 'signOf', 'splice', 'ub2str', 'unsign0'),
130                          clipy=('ClipError',
131                                 'ClipCS4Tuple', 'ClipLB6Tuple', 'ClipSH3Tuple',
132                                 'clipCS4', 'clipLB6', 'clipSH', 'clipSH3'),
133                            css=('CassiniSoldner', 'Css', 'CSSError', 'toCss',
134                                 'EasNorAziRk4Tuple', 'LatLonAziRk4Tuple'),
135                         datums=('Datum', 'Datums', 'Transform', 'Transforms'),
136                     deprecated=('OK',  # DEPRECATED constants
137                                 'bases', 'datum', 'nvector',  # DEPRECATED modules
138                                 'ClipCS3Tuple', 'EcefCartesian', 'HeightIDW', 'HeightIDW2', 'HeightIDW3', 'RefFrameError', 'UtmUps4Tuple',  # DEPRECATED classes
139                                 'anStr', 'areaof', 'bounds', 'clipCS3', 'clipDMS', 'clipStr', 'decodeEPSG2', 'encodeEPSG',  # most of the DEPRECATED functions, ...
140                                 'equirectangular3', 'enStr2', 'false2f', 'falsed2f', 'fStr', 'fStrzs',  # ... except ellipsoidal, spherical flavors
141                                 'hypot3', 'inStr', 'isenclosedby', 'joined', 'joined_',
142                                 'nearestOn3', 'nearestOn4', 'parseUTM', 'perimeterof', 'polygon',
143                                 'scalar', 'simplify2', 'toUtm', 'unStr', 'utmZoneBand2'),
144                            dms=('F_D',   'F_DM',   'F_DMS',   'F_DEG',   'F_MIN',   'F_SEC',   'F__E',   'F__F',   'F__G',   'F_RAD',
145                                 'F_D_',  'F_DM_',  'F_DMS_',  'F_DEG_',  'F_MIN_',  'F_SEC_',  'F__E_',  'F__F_',  'F__G_',  'F_RAD_',
146                                 'F_D__', 'F_DM__', 'F_DMS__', 'F_DEG__', 'F_MIN__', 'F_SEC__', 'F__E__', 'F__F__', 'F__G__', 'F_RAD__',
147                                 'S_DEG', 'S_MIN', 'S_SEC', 'S_RAD', 'S_SEP', 'ParseError',
148                                 'bearingDMS', 'clipDegrees', 'clipRadians', 'compassDMS', 'compassPoint',
149                                 'degDMS', 'latDMS', 'latlonDMS', 'lonDMS', 'normDMS',
150                                 'parseDDDMMSS', 'parseDMS', 'parseDMS2', 'parse3llh', 'parseRad', 'precision', 'toDMS'),
151                           ecef=('EcefError', 'EcefFarrell21', 'EcefFarrell22', 'EcefKarney', 'EcefMatrix', 'EcefSudano', 'Ecef9Tuple', 'EcefVeness', 'EcefYou'),
152                     elevations=('elevation2', 'geoidHeight2',
153                                 'Elevation2Tuple', 'GeoidHeight2Tuple'),
154               ellipsoidalExact=(),  # module only
155           ellipsoidalGeodSolve=(),  # module only
156              ellipsoidalKarney=(),  # module only
157             ellipsoidalNvector=('Ned3Tuple',),  # nothing else
158            ellipsoidalVincenty=('VincentyError',),  # nothing else
159                     ellipsoids=('R_M', 'R_MA', 'R_MB', 'R_KM', 'R_NM', 'R_SM', 'R_FM', 'R_GM', 'R_VM',
160                                 'a_f2Tuple', 'Circle4Tuple', 'Curvature2Tuple',
161                                 'Ellipsoid', 'Ellipsoid2', 'Ellipsoids',
162                                 'a_b2e', 'a_b2e2', 'a_b2e22', 'a_b2e32', 'a_b2f', 'a_b2f_', 'a_b2f2', 'a_b2n',
163                                 'a_f2b', 'a_f_2b', 'b_f2a', 'b_f_2a',
164                                 'f2e2', 'f2e22', 'f2e32', 'f_2f', 'f2f_', 'f2f2', 'f2n', 'n2e2', 'n2f', 'n2f_'),
165                       elliptic=('Elliptic', 'EllipticError', 'Elliptic3Tuple'),
166                           epsg=('Epsg', 'EPSGError'),
167                         errors=('CrossError', 'IntersectionError', 'NumPyError', 'LenError', 'LimitError', 'PointsError',
168                                 'RangeError', 'SciPyError', 'SciPyWarning', 'TRFError', 'UnitError', 'VectorError',
169                                 'crosserrors', 'exception_chaining', 'limiterrors', 'rangerrors'),
170                            etm=('Etm', 'ETMError', 'ExactTransverseMercator',
171                                 'EasNorExact4Tuple', 'LatLonExact4Tuple',
172                                 'parseETM5', 'toEtm8'),
173                          fmath=('Fdot', 'Fhorner', 'Fpolynomial', 'Fsum',
174                                 'cbrt', 'cbrt2', 'euclid', 'euclid_',
175                                 'facos1', 'fasin1', 'fatan', 'fatan1', 'fatan2', 'favg',
176                                 'fdot', 'fdot3', 'fmean', 'fmean_', 'fhorner', 'fidw', 'fpolynomial',
177                                 'fpowers', 'fprod', 'frange', 'freduce', 'fsum', 'fsum_', 'fsum1_',
178                                 'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_',
179                                 'norm2', 'norm_', 'sqrt0', 'sqrt3'),
180                          formy=('antipode', 'antipode_', 'bearing', 'bearing_',
181                                 'compassAngle', 'cosineForsytheAndoyerLambert', 'cosineForsytheAndoyerLambert_',
182                                 'cosineAndoyerLambert', 'cosineAndoyerLambert_', 'cosineLaw', 'cosineLaw_',
183                                 'equirectangular', 'equirectangular_', 'euclidean', 'euclidean_',
184                                 'excessAbc', 'excessGirard', 'excessLHuilier',
185                                 'excessKarney', 'excessKarney_', 'excessQuad', 'excessQuad_',
186                                 'flatLocal', 'flatLocal_', 'flatPolar', 'flatPolar_',
187                                 'hartzell', 'haversine', 'haversine_', 'heightOf', 'horizon', 'hubeny', 'hubeny_',
188                                 'intersections2', 'isantipode', 'isantipode_',
189                                 'latlon2n_xyz', 'n_xyz2latlon', 'n_xyz2philam',
190                                 'opposing', 'opposing_', 'philam2n_xyz',
191                                 'radical2', 'thomas', 'thomas_', 'vincentys', 'vincentys_',
192                                 'Radical2Tuple'),
193                        frechet=('Frechet', 'FrechetDegrees', 'FrechetError', 'FrechetRadians',
194                                 'FrechetCosineAndoyerLambert', 'FrechetCosineForsytheAndoyerLambert',
195                                 'FrechetCosineLaw', 'FrechetDistanceTo', 'FrechetEquirectangular',
196                                 'FrechetEuclidean', 'FrechetExact', 'FrechetFlatLocal', 'FrechetFlatPolar',
197                                 'FrechetHaversine', 'FrechetHubeny', 'FrechetKarney', 'FrechetThomas',
198                                 'FrechetVincentys', 'Frechet6Tuple',
199                                 'frechet_'),
200                           gars=('Garef', 'GARSError'),
201                      geodesicx=('gx', 'gxarea', 'gxline',  # modules
202                                 'Caps', 'GeodesicAreaExact', 'GeodesicExact', 'GeodesicLineExact', 'PolygonArea'),
203                      geodsolve=('GeodesicSolve', 'GeodesicLineSolve'),
204                        geohash=('Geohash', 'GeohashError', 'Neighbors8Dict', 'Resolutions2Tuple'),
205                         geoids=('GeoidError', 'GeoidG2012B', 'GeoidKarney', 'GeoidPGM', 'egmGeoidHeights',
206                                 'PGMError', 'GeoidHeight5Tuple'),
207                      hausdorff=('Hausdorff', 'HausdorffDegrees', 'HausdorffError', 'HausdorffRadians',
208                                 'HausdorffCosineAndoyerLambert', 'HausdorffCosineForsytheAndoyerLambert',
209                                 'HausdorffCosineLaw', 'HausdorffDistanceTo', 'HausdorffEquirectangular',
210                                 'HausdorffEuclidean', 'HausdorffExact', 'HausdorffFlatLocal', 'HausdorffFlatPolar',
211                                 'HausdorffHaversine', 'HausdorffHubeny', 'HausdorffKarney', 'HausdorffThomas',
212                                 'HausdorffVincentys', 'Hausdorff6Tuple',
213                                 'hausdorff_', 'randomrangenerator'),
214                        heights=('HeightError',
215                                 'HeightIDWcosineAndoyerLambert', 'HeightIDWcosineForsytheAndoyerLambert',
216                                 'HeightIDWcosineLaw', 'HeightIDWdistanceTo', 'HeightIDWequirectangular',
217                                 'HeightIDWeuclidean', 'HeightIDWflatLocal', 'HeightIDWflatPolar', 'HeightIDWhaversine',
218                                 'HeightIDWhubeny', 'HeightIDWkarney', 'HeightIDWthomas', 'HeightIDWvincentys',
219                                 'HeightCubic', 'HeightLinear', 'HeightLSQBiSpline', 'HeightSmoothBiSpline'),
220                        interns=_interns_a_l_l_,
221                          iters=('LatLon2PsxyIter', 'PointsIter', 'points2',
222                                 'isNumpy2', 'isPoints2', 'isTuple2', 'iterNumpy2', 'iterNumpy2over'),
223                         karney=('Direct9Tuple', 'GDict', 'GeodesicError', 'GeodSolve12Tuple', 'Inverse10Tuple'),
224                         lazily=('LazyImportError', 'isLazy', 'print_', 'printf'),
225                            lcc=('Conic', 'Conics', 'Lcc', 'LCCError', 'toLcc'),
226                            ltp=('Frustum', 'LocalCartesian', 'LocalError', 'Ltp'),
227                      ltpTuples=('Aer', 'Aer4Tuple', 'Enu', 'Enu4Tuple', 'Footprint5Tuple', 'Local9Tuple',
228                                 'Ned', 'Ned4Tuple', 'XyzLocal', 'Xyz4Tuple'),
229                           mgrs=('Mgrs', 'MGRSError', 'parseMGRS', 'toMgrs', 'Mgrs4Tuple', 'Mgrs6Tuple'),
230                          named=('callername', 'classname', 'classnaming', 'modulename', 'nameof', 'notImplemented', 'notOverloaded'),
231                    namedTuples=('Bearing2Tuple', 'Bounds2Tuple', 'Bounds4Tuple',
232                                 'Destination2Tuple', 'Destination3Tuple',
233                                 'Distance2Tuple', 'Distance3Tuple', 'Distance4Tuple',
234                                 'EasNor2Tuple', 'EasNor3Tuple', 'Intersection3Tuple',
235                                 'LatLon2Tuple', 'LatLon3Tuple', 'LatLon4Tuple',
236                                 'LatLonDatum3Tuple', 'LatLonDatum5Tuple',
237                                 'LatLonPrec3Tuple', 'LatLonPrec5Tuple',
238                                 'NearestOn3Tuple',
239                                 'PhiLam2Tuple', 'PhiLam3Tuple', 'PhiLam4Tuple', 'Point3Tuple', 'Points2Tuple',
240                                 'Triangle7Tuple', 'Triangle8Tuple', 'Trilaterate5Tuple',
241                                 'UtmUps2Tuple', 'UtmUps5Tuple', 'UtmUps8Tuple', 'UtmUpsLatLon5Tuple',
242                                 'Vector2Tuple', 'Vector3Tuple', 'Vector4Tuple'),
243                           osgr=('Osgr', 'OSGRError', 'parseOSGR', 'toOsgr'),
244                         points=('LatLon_', 'LatLon2psxy', 'NearestOn5Tuple', 'Numpy2LatLon', 'Shape2Tuple', 'Tuple2LatLon',
245                                 _areaOf_, 'boundsOf', 'centroidOf', 'fractional',
246                                 _isclockwise_, 'isconvex', 'isconvex_', 'isenclosedBy', _ispolar_,
247                                 'luneOf', 'nearestOn5', _perimeterOf_, 'quadOf'),
248                          props=('Property', 'Property_RO', 'property_RO', 'property_doc_',
249                                 'deprecated_class', 'deprecated_function', 'deprecated_method',
250                                 'deprecated_Property_RO', 'deprecated_property_RO', 'DeprecationWarnings'),
251               sphericalNvector=(),  # module only
252          sphericalTrigonometry=(),  # module only
253                       simplify=('simplify1', 'simplifyRDP', 'simplifyRDPm', 'simplifyRW', 'simplifyVW', 'simplifyVWm'),
254                        streprs=('anstr', 'attrs', 'enstr2', 'fstr', 'fstrzs', 'hstr', 'instr', 'pairs', 'reprs', 'strs', 'unstr'),
255                            trf=('RefFrame', 'RefFrames', 'Transform7Tuple',
256                                 'date2epoch', 'epoch2date', 'trfXform'),
257                          units=('Band', 'Bearing', 'Bearing_', 'Bool',
258                                 'Degrees', 'Degrees_', 'Degrees2', 'Distance', 'Distance_', 'Easting', 'Epoch',
259                                 'Feet', 'FIx', 'Float', 'Float_', 'Height', 'Int', 'Int_',
260                                 'Lam', 'Lam_', 'Lat', 'Lat_', 'Lon', 'Lon_',
261                                 'Meter', 'Meter_', 'Meter2', 'Meter3', 'Northing', 'Number_',
262                                 'Phi', 'Phi_', 'Precision_', 'Radians', 'Radians_', 'Radians2',
263                                 'Radius', 'Radius_', 'Scalar', 'Scalar_', 'Str', 'Zone'),
264                            ups=('Ups', 'UPSError', 'parseUPS5', 'toUps8', 'upsZoneBand5'),
265                          utily=('acos1', 'acre2ha', 'acre2m2', 'asin1', 'atand', 'atan2b', 'atan2d',
266                                 'chain2m', 'circle4', 'cotd', 'cotd_',
267                                 'degrees', 'degrees90', 'degrees180', 'degrees360', 'degrees2grades', 'degrees2m',
268                                 'fathom2m', 'ft2m', 'furlong2m',
269                                 'grades', 'grades400', 'grades2degrees', 'grades2radians',
270                                 'm2degrees', 'm2ft', 'm2km', 'm2NM', 'm2radians', 'm2SM', 'm2yard',
271                                 'radians', 'radiansPI', 'radiansPI2', 'radiansPI_2', 'radians2m',
272                                 'sincos2', 'sincos2_', 'sincos2d', 'sincos2d_', 'tand', 'tand_', 'tan_2', 'tanPI_2_2',
273                                 'unroll180', 'unrollPI',
274                                 'wrap90', 'wrap180', 'wrap360', 'wrapPI_2','wrapPI', 'wrapPI2',
275                                 'yard2m'),
276                            utm=('Utm', 'UTMError', 'parseUTM5', 'toUtm8', 'utmZoneBand5'),
277                         utmups=('UtmUps', 'UTMUPSError', 'parseUTMUPS5', 'toUtmUps8',
278                                 'utmupsValidate', 'utmupsValidateOK', 'utmupsZoneBand5'),
279                       vector2d=('Circin6Tuple', 'Circum3Tuple', 'Circum4Tuple', 'Meeus2Tuple', 'Radii11Tuple', 'Soddy4Tuple',
280                                 'circin6', 'circum3', 'circum4_', 'meeus2', 'radii11', 'soddy4'),
281                       vector3d=('Vector3d', 'intersection3d3', 'iscolinearWith', 'parse3d', 'trilaterate2d2', 'trilaterate3d2'),
282                    webmercator=('Wm', 'WebMercatorError', 'parseWM', 'toWm', 'EasNorRadius3Tuple'),
283                           wgrs=('Georef', 'WGRSError'))
284
285# DEPRECATED __all__ names overloading those in _ALL_LAZY.deprecated where
286# the new name is fully backward compatible in signature and return value
287_ALL_OVERRIDDEN = _NamedEnum_RO(_name='_ALL_OVERRIDING',  # all DEPRECATED
288                               basics=('clips as clipStr',),
289                                fmath=('hypot_ as hypot3',),
290                                formy=('points2 as polygon',),
291                              heights=('HeightIDWequirectangular as HeightIDW2', 'HeightIDWeuclidean as HeightIDW',
292                                       'HeightIDWhaversine as HeightIDW3'),
293                               points=('areaOf as areaof',
294                                       'isenclosedBy as isenclosedby', 'perimeterOf as perimeterof'),
295                             simplify=('simplifyRW as simplify2',),
296                              streprs=('anstr as anStr', 'enstr2 as enStr2', 'fstr as fStr', 'fstrzs as fStrzs',
297                                       'instr as inStr', 'unstr as unStr'))
298
299__all__ = _ALL_LAZY.lazily
300__version__ = '21.09.14'
301
302
303def _ALL_OTHER(*objs):
304    '''(INTERNAL) List local objects for __all__.
305    '''
306    from pygeodesy import interns  # PYCHOK import
307
308    def _dun(o):
309        n = _dunder_name(o).rsplit(_DOT_, 1)[-1]
310        u = NN(_UNDER_, n, _UNDER_)
311        return getattr(interns, u, n)
312
313    return tuple(map(_dun, objs))
314
315
316if _FOR_DOCS:
317    _ALL_DOCS = _ALL_OTHER
318    # (INTERNAL) Only export B{C{objs.__name__}} when making the
319    # docs to force C{epydoc} to include certain classes, methods,
320    # functions and other names in the documentation.  Using the
321    # C{epydoc --private ...} command line option tends to include
322    # too much internal documentation.
323else:
324    def _ALL_DOCS(*unused):
325        return ()
326
327
328def _all_imports(**more):
329    '''(INTERNAL) Build C{dict} of all lazy imports.
330    '''
331    # imports naming conventions stored below - [<key>] = <from>:
332    #  import <module>                        - [<module>] = <module>
333    #  from <module> import <attr>            - [<attr>] = <module>
334    #  from pygeodesy import <attr>           - [<attr>] = <attr>
335    #  from <module> import <attr> as <name>  - [<name>] = <module>.<attr>
336    imports = _Dict()
337    imports_add = imports.add
338
339    for ALL in (_ALL_LAZY, _ALL_OVERRIDDEN, more):
340        for mod, attrs in ALL.items():
341            if isinstance(attrs, tuple) and not mod.startswith(_UNDER_):
342                imports_add(mod, mod)
343                for attr in attrs:
344                    attr, _, as_attr = attr.partition(' as ')
345                    if as_attr:
346                        imports_add(as_attr, _DOT_(mod, attr), *_sub_packages)
347                    else:
348                        imports_add(attr, mod)
349    return imports
350
351
352def _all_missing2(_all_):
353    '''(INTERNAL) Get diffs between pygeodesy.__all__ and lazily._all_imports.
354    '''
355    _alzy = _all_imports(**_NamedEnum_RO((a, ()) for a in _ALL_INIT))
356    return ((_DOT_('lazily', _all_imports.__name__), _COMMASPACE_.join(a for a in _all_ if a not in _alzy)),
357            (_DOT_('pygeodesy', _a_l_l_),            _COMMASPACE_.join(a for a in _alzy if a not in _all_)))
358
359
360def _caller3(up):  # in .named
361    '''(INTERNAL) Get 3-tuple C{(caller name, file name, line number)}
362       for the caller B{C{up}} stack frames in the Python call stack.
363    '''
364    # sys._getframe(1) ... 'importlib._bootstrap' line 1032,
365    # may throw a ValueError('call stack not deep enough')
366    f = _sys._getframe(up + 1)
367    return (f.f_code.co_name,  # caller name
368           _basename(f.f_code.co_filename),  # file name
369            f.f_lineno)  # line number
370
371
372def _lazy_import2(_pygeodesy_):  # MCCABE 15
373    '''Check for and set up C{lazy import}.
374
375       @arg _pygeodesy_: The name of the package (C{str}) performing
376                         the imports, to help facilitate resolving
377                         relative imports, usually C{__package__}.
378
379       @return: 2-Tuple C{(package, getattr)} of the importing package
380                for easy reference within itself and the callable to
381                be set to `__getattr__`.
382
383       @raise LazyImportError: Lazy import not supported or not enabled,
384                               an import failed or the package name or
385                               module name or attribute name is invalid
386                               or does not exist.
387
388       @note: This is the original function U{modutil.lazy_import
389              <https://GitHub.com/brettcannon/modutil/blob/master/modutil.py>}
390              modified to handle the C{__all__} and C{__dir__} attributes
391              and call C{importlib.import_module(<module>.<name>, ...)}
392              without causing a C{ModuleNotFoundError}.
393
394       @see: The original U{modutil<https://PyPi.org/project/modutil>},
395             U{PEP 562<https://www.Python.org/dev/peps/pep-0562>} and the
396             U{new way<https://Snarky.CA/lazy-importing-in-python-3-7/>}.
397    '''
398    if _sys_version_info2 < (3, 7):  # not supported before 3.7
399        t = _no_(_DOT_(_pygeodesy_, _lazy_import2.__name__))
400        raise LazyImportError(t, txt=_Python_(_sys))
401
402    package, parent = _lazy_init2(_pygeodesy_)
403
404    packages = (parent, '__main__', NN) + tuple(
405               _DOT_(parent, s) for s in _sub_packages)
406    imports  = _all_imports()
407
408    def __getattr__(name):  # __getattr__ only for Python 3.7+
409        # only called once for each undefined pygeodesy attribute
410        if name in imports:
411            # importlib.import_module() implicitly sets sub-modules
412            # on this module as appropriate for direct imports (see
413            # note in the _lazy_import.__doc__ above).
414            mod, _, attr = imports[name].partition(_DOT_)
415            if mod not in imports:
416                raise LazyImportError(_no_(_module_), txt=_DOT_(parent, mod))
417            imported = import_module(_DOT_(_pygeodesy_, mod), parent)
418            pkg = getattr(imported, _p_a_c_k_a_g_e_, None)
419            if pkg not in packages:  # invalid package
420                raise LazyImportError(_DOT_(mod, _p_a_c_k_a_g_e_), pkg)
421            # import the module or module attribute
422            if attr:
423                imported = getattr(imported, attr, MISSING)
424            elif name != mod:
425                imported = getattr(imported, name, MISSING)
426            if imported is MISSING:
427                raise LazyImportError(_no_(_attribute_),
428                                      txt=_DOT_(mod, attr or name))
429
430        elif name in (_a_l_l_,):  # XXX '_d_i_r_', '_m_e_m_b_e_r_s_'?
431            imported = _ALL_INIT + tuple(imports.keys())
432            mod = NN
433        else:
434            raise LazyImportError(_no_(_module_, _or_, _attribute_),
435                                  txt=_DOT_(parent, name))
436
437        setattr(package, name, imported)
438        if isLazy > 1:
439            z = NN
440            if mod and mod != name:
441                z = ' from .%s' % (mod,)
442            if isLazy > 2:
443                try:  # see C{_caller3}
444                    _, f, s = _caller3(2)
445                    z = '%s by %s line %d' % (z, f, s)
446                except ValueError:
447                    pass
448            printf('# lazily imported %s%s', _DOT_(parent, name), z)
449
450        return imported  # __getattr__
451
452    return package, __getattr__  # _lazy_import2
453
454
455def _lazy_init2(_pygeodesy_):
456    '''(INTERNAL) Try to initialize lazy import.
457
458       @arg _pygeodesy_: The name of the package (C{str}) performing
459                         the imports, to help facilitate resolving
460                         relative imports, usually C{__package__}.
461
462       @return: 3-Tuple C{(import_module, package, parent)} of module
463                C{importlib.import_module}, the importing C{package}
464                for easy reference within itself, always C{pygeodesy}
465                and the package name, aka the C{parent}, always
466                C{'pygeodesy'}.
467
468       @raise LazyImportError: Lazy import not supported or not enabled,
469                               an import failed or the package name is
470                               invalid or does not exist.
471
472       @note: Global C{isLazy} is set accordingly.
473    '''
474    global isLazy
475
476    z = _getenv(_PYGEODESY_LAZY_IMPORT_, None)
477    if z is None:  # _PYGEODESY_LAZY_IMPORT_ not set
478        isLazy = 1  # ... but on by default on 3.7
479    else:
480        z = z.strip()  # like PYTHONVERBOSE et.al.
481        isLazy = int(z) if z.isdigit() else (1 if z else 0)
482    if isLazy < 1:  # not enabled
483        raise LazyImportError(_PYGEODESY_LAZY_IMPORT_, repr(z), txt=_not_(_enabled_))
484    if _getenv('PYTHONVERBOSE', None):  # PYCHOK no cover
485        isLazy += 1
486
487    try:  # to initialize in Python 3+
488        package = import_module(_pygeodesy_)
489        parent = package.__spec__.parent  # __spec__ only in Python 3.7+
490        if parent != _pygeodesy_:  # assert
491            t = _COMMASPACE_(parent, _not_(_pygeodesy_))
492            raise AttributeError(_EQUALSPACED_('parent', t))
493
494    except (AttributeError, ImportError) as x:
495        isLazy = False  # failed
496        raise LazyImportError(_lazy_init2.__name__, _pygeodesy_, txt=str(x))
497
498    return package, parent
499
500
501def print_(*args, **nl_nt_prefix_end_file_flush_sep):
502    '''Python 3-style C{print} function.
503
504       @arg args: Values to be converted to C{str} and
505                  concatenated (C{any} types).
506       @kwarg nl=0: Number of leading blank lines (C{int}).
507       @kwarg nt=0: Number of additional , trailing blank lines (C{int}).
508       @kwarg prefix=NN: To be inserted before the formatted text (C{str}).
509
510       @note: Python 3+ keyword arguments C{end}, C{file} and C{flush}
511              are silently ignored.
512    '''
513    sep = nl_nt_prefix_end_file_flush_sep.get(_sep_, _SPACE_)
514    txt = sep.join(map(str, args))
515    printf(txt, **nl_nt_prefix_end_file_flush_sep)
516
517
518def printf(fmt, *args, **nl_nt_prefix_end_file_flush_sep):
519    '''C-style C{printf} function.
520
521       @arg fmt: C-style formating text (C{str}).
522       @arg args: Values to be formatted (C{any} types).
523       @kwarg nl=0: Number of leading blank lines (C{int}).
524       @kwarg nt=0: Number of additional , trailing blank lines (C{int}).
525       @kwarg prefix=NN: To be inserted before the formatted text (C{str}).
526
527       @note: Python 3+ keyword arguments C{end}, C{file}, C{flush}
528              and C{sep} are silently ignored.
529    '''
530    def _kwds(nl=0, nt=0, prefix=NN, **kwds):  # XXX end?
531        nl = (_NL_ * nl) if nl else NN
532        nt = (_NL_ * nt) if nt else NN
533        return nl, nt, prefix, kwds
534
535    nl, nt, prefix, _ = _kwds(**nl_nt_prefix_end_file_flush_sep)
536    if args:
537        fmt %= args
538#   elif kwds:
539#       fmt %= kwds
540    print(NN(nl, prefix, fmt, nt))
541
542
543# **) MIT License
544#
545# Copyright (C) 2018-2021 -- mrJean1 at Gmail -- All Rights Reserved.
546#
547# Permission is hereby granted, free of charge, to any person obtaining a
548# copy of this software and associated documentation files (the "Software"),
549# to deal in the Software without restriction, including without limitation
550# the rights to use, copy, modify, merge, publish, distribute, sublicense,
551# and/or sell copies of the Software, and to permit persons to whom the
552# Software is furnished to do so, subject to the following conditions:
553#
554# The above copyright notice and this permission notice shall be included
555# in all copies or substantial portions of the Software.
556#
557# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
558# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
559# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
560# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
561# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
562# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
563# OTHER DEALINGS IN THE SOFTWARE.
564