pygeodesy 24.3.24__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyGeodesy-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
pygeodesy/props.py
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Mutable, immutable and caching/memoizing properties and
|
|
5
|
+
deprecation decorators.
|
|
6
|
+
|
|
7
|
+
To enable C{DeprecationWarning}s from C{PyGeodesy}, set env var
|
|
8
|
+
C{PYGEODESY_WARNINGS} to a non-empty string I{AND} run C{python}
|
|
9
|
+
with command line option C{-X dev} or with one of the C{-W}
|
|
10
|
+
choices, see callable L{DeprecationWarnings} below.
|
|
11
|
+
'''
|
|
12
|
+
|
|
13
|
+
from pygeodesy.basics import isclass as _isclass # _MODS
|
|
14
|
+
from pygeodesy.errors import _AssertionError, _AttributeError, \
|
|
15
|
+
_xkwds, _xkwds_get
|
|
16
|
+
from pygeodesy.interns import MISSING, NN, _an_, _COMMASPACE_, \
|
|
17
|
+
_DEPRECATED_, _DOT_, _EQUALSPACED_, \
|
|
18
|
+
_immutable_, _invalid_, _module_, _N_A_, \
|
|
19
|
+
_not_, _SPACE_, _UNDER_, _DNL_ # PYCHOK used!
|
|
20
|
+
# from pygeodesy.named import callname # _MODS, avoid circular
|
|
21
|
+
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, \
|
|
22
|
+
_FOR_DOCS, _WARNINGS_X_DEV
|
|
23
|
+
# from pygeodesy.streprs import Fmt # _MODS
|
|
24
|
+
|
|
25
|
+
from functools import wraps as _wraps
|
|
26
|
+
|
|
27
|
+
__all__ = _ALL_LAZY.props
|
|
28
|
+
__version__ = '24.03.06'
|
|
29
|
+
|
|
30
|
+
_class_ = 'class'
|
|
31
|
+
_dont_use_ = _DEPRECATED_ + ", don't use."
|
|
32
|
+
_function_ = 'function'
|
|
33
|
+
_get_and_set_ = 'get and set'
|
|
34
|
+
_has_been_ = 'has been' # PYCHOK used!
|
|
35
|
+
_method_ = 'method'
|
|
36
|
+
_not_an_inst_ = _not_(_an_, 'instance')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _allPropertiesOf(Clas_or_inst, *Bases):
|
|
40
|
+
'''(INTERNAL) Yield all C{R/property/_RO}s at C{Clas_or_inst}
|
|
41
|
+
as specified in the C{Bases} arguments.
|
|
42
|
+
'''
|
|
43
|
+
if _isclass(Clas_or_inst):
|
|
44
|
+
S = Clas_or_inst, # just this Clas
|
|
45
|
+
else: # class and super-classes of inst
|
|
46
|
+
try:
|
|
47
|
+
S = Clas_or_inst.__class__.__mro__[:-1] # not object
|
|
48
|
+
except AttributeError:
|
|
49
|
+
raise
|
|
50
|
+
S = () # not an inst
|
|
51
|
+
B = Bases or _PropertyBase
|
|
52
|
+
for C in S:
|
|
53
|
+
for n, p in C.__dict__.items():
|
|
54
|
+
if isinstance(p, B) and p.name == n:
|
|
55
|
+
yield p
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _allPropertiesOf_n(n, Clas_or_inst, *Bases):
|
|
59
|
+
'''(INTERNAL) Assert the number of C{R/property/_RO}s at C{Clas_or_inst}.
|
|
60
|
+
'''
|
|
61
|
+
t = tuple(p.name for p in _allPropertiesOf(Clas_or_inst, *Bases))
|
|
62
|
+
if len(t) != n:
|
|
63
|
+
raise _AssertionError(_COMMASPACE_.join(t), Clas_or_inst,
|
|
64
|
+
txt=_COMMASPACE_(len(t), _not_(n)))
|
|
65
|
+
return t
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _hasProperty(inst, name, *Classes): # in .named._NamedBase._update
|
|
69
|
+
'''(INTERNAL) Check whether C{inst} has a C{P/property/_RO} by this C{name}.
|
|
70
|
+
'''
|
|
71
|
+
p = getattr(inst.__class__, name, None) # walks __class__.__mro__
|
|
72
|
+
return bool(p and isinstance(p, Classes or _PropertyBase)
|
|
73
|
+
and p.name == name)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# def _isclass(obj):
|
|
77
|
+
# '''(INTERNAL) Get and overwrite C{_isclass}.
|
|
78
|
+
# '''
|
|
79
|
+
# _MODS.getmodule(__name__)._isclass = f = _MODS.basics.isclass
|
|
80
|
+
# return f(obj)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _update_all(inst, *attrs, **Base_needed):
|
|
84
|
+
'''(INTERNAL) Zap all I{cached} L{property_RO}s, L{Property}s,
|
|
85
|
+
L{Property_RO}s and the named C{attrs} of an instance.
|
|
86
|
+
|
|
87
|
+
@return: The number of updates (C{int}), if any.
|
|
88
|
+
'''
|
|
89
|
+
if _isclass(inst):
|
|
90
|
+
raise _AssertionError(inst, txt=_not_an_inst_)
|
|
91
|
+
try:
|
|
92
|
+
d = inst.__dict__
|
|
93
|
+
except AttributeError:
|
|
94
|
+
return 0
|
|
95
|
+
u = len(d)
|
|
96
|
+
if u > _xkwds_get(Base_needed, needed=0):
|
|
97
|
+
B = _xkwds_get(Base_needed, Base=_PropertyBase)
|
|
98
|
+
for p in _allPropertiesOf(inst, B):
|
|
99
|
+
p._update(inst) # d.pop(p.name, None)
|
|
100
|
+
|
|
101
|
+
if attrs:
|
|
102
|
+
_update_attrs(inst, *attrs) # remove attributes from inst.__dict__
|
|
103
|
+
u -= len(d)
|
|
104
|
+
return u # updates
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# def _update_all_from(inst, other, **Base):
|
|
108
|
+
# '''(INTERNAL) Update all I{cached} L{Property}s and
|
|
109
|
+
# L{Property_RO}s of instance C{inst} from C{other}.
|
|
110
|
+
#
|
|
111
|
+
# @return: The number of updates (C{int}), if any.
|
|
112
|
+
# '''
|
|
113
|
+
# if _isclass(inst):
|
|
114
|
+
# raise _AssertionError(inst, txt=_not_an_inst_)
|
|
115
|
+
# try:
|
|
116
|
+
# d = inst.__dict__
|
|
117
|
+
# f = other.__dict__
|
|
118
|
+
# except AttributeError:
|
|
119
|
+
# return 0
|
|
120
|
+
# u = len(f)
|
|
121
|
+
# if u:
|
|
122
|
+
# u = len(d)
|
|
123
|
+
# B = _xkwds_get(Base, Base=_PropertyBase)
|
|
124
|
+
# for p in _allPropertiesOf(inst, B):
|
|
125
|
+
# p._update_from(inst, other)
|
|
126
|
+
# u -= len(d)
|
|
127
|
+
# return u # number of updates
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _update_attrs(inst, *attrs):
|
|
131
|
+
'''(INTERNAL) Zap all named C{attrs} of an instance.
|
|
132
|
+
|
|
133
|
+
@return: The number of updates (C{int}), if any.
|
|
134
|
+
'''
|
|
135
|
+
try:
|
|
136
|
+
d = inst.__dict__
|
|
137
|
+
except AttributeError:
|
|
138
|
+
return 0
|
|
139
|
+
u = len(d)
|
|
140
|
+
if u: # zap attrs from inst.__dict__
|
|
141
|
+
_p = d.pop
|
|
142
|
+
for a in attrs:
|
|
143
|
+
_ = _p(a, MISSING)
|
|
144
|
+
# if _ is MISSING and not hasattr(inst, a):
|
|
145
|
+
# n = _MODS.named.classname(inst, prefixed=True)
|
|
146
|
+
# a = _DOT_(n, _SPACE_(a, _invalid_))
|
|
147
|
+
# raise _AssertionError(a, txt=repr(inst))
|
|
148
|
+
# _ = _p(a, None) # redo: hasattr side effect
|
|
149
|
+
u -= len(d)
|
|
150
|
+
# assert u >= 0
|
|
151
|
+
return u # number of named C{attrs} zapped
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class _PropertyBase(property):
|
|
155
|
+
'''(INTERNAL) Base class for C{P/property/_RO}.
|
|
156
|
+
'''
|
|
157
|
+
def __init__(self, method, fget, fset, doc=NN):
|
|
158
|
+
|
|
159
|
+
if not callable(method):
|
|
160
|
+
self.getter(method) # PYCHOK no cover
|
|
161
|
+
|
|
162
|
+
self.method = method
|
|
163
|
+
self.name = method.__name__
|
|
164
|
+
d = doc or method.__doc__
|
|
165
|
+
if _FOR_DOCS and d:
|
|
166
|
+
self.__doc__ = d # PYCHOK no cover
|
|
167
|
+
|
|
168
|
+
property.__init__(self, fget, fset, self._fdel, d or _N_A_)
|
|
169
|
+
|
|
170
|
+
def _Error(self, kind, nameter, farg):
|
|
171
|
+
'''(INTERNAL) Return an C{AttributeError} instance.
|
|
172
|
+
'''
|
|
173
|
+
if farg:
|
|
174
|
+
n = _DOT_(self.name, nameter.__name__)
|
|
175
|
+
n = _SPACE_(n, farg.__name__)
|
|
176
|
+
else:
|
|
177
|
+
n = nameter
|
|
178
|
+
e = _SPACE_(kind, _MODS.named.classname(self))
|
|
179
|
+
return _AttributeError(e, txt=n)
|
|
180
|
+
|
|
181
|
+
def _fdel(self, inst):
|
|
182
|
+
'''Zap the I{cached/memoized} C{property} value.
|
|
183
|
+
'''
|
|
184
|
+
self._update(inst, None) # PYCHOK no cover
|
|
185
|
+
|
|
186
|
+
def _fget(self, inst):
|
|
187
|
+
'''Get and I{cache/memoize} the C{property} value.
|
|
188
|
+
'''
|
|
189
|
+
try: # to get the value cached in instance' __dict__
|
|
190
|
+
return inst.__dict__[self.name]
|
|
191
|
+
except KeyError:
|
|
192
|
+
# cache the value in the instance' __dict__
|
|
193
|
+
inst.__dict__[self.name] = val = self.method(inst)
|
|
194
|
+
return val
|
|
195
|
+
|
|
196
|
+
def _fset_error(self, inst, val):
|
|
197
|
+
'''Throws an C{AttributeError}, always.
|
|
198
|
+
'''
|
|
199
|
+
n = _MODS.named.classname(inst)
|
|
200
|
+
n = _DOT_(n, self.name)
|
|
201
|
+
n = _EQUALSPACED_(n, repr(val))
|
|
202
|
+
raise self._Error(_immutable_, n, None)
|
|
203
|
+
|
|
204
|
+
def _update(self, inst, *unused):
|
|
205
|
+
'''(INTERNAL) Zap the I{cached/memoized} C{inst.__dict__[name]} item.
|
|
206
|
+
'''
|
|
207
|
+
inst.__dict__.pop(self.name, None) # name, NOT _name
|
|
208
|
+
|
|
209
|
+
def _update_from(self, inst, other):
|
|
210
|
+
'''(INTERNAL) Copy a I{cached/memoized} C{inst.__dict__[name]} item
|
|
211
|
+
from C{other.__dict__[name]} if present, otherwise zap it.
|
|
212
|
+
'''
|
|
213
|
+
n = self.name # name, NOT _name
|
|
214
|
+
v = other.__dict__.get(n, MISSING)
|
|
215
|
+
if v is MISSING:
|
|
216
|
+
inst.__dict__.pop(n, None)
|
|
217
|
+
else:
|
|
218
|
+
inst.__dict__[n] = v
|
|
219
|
+
|
|
220
|
+
def deleter(self, fdel):
|
|
221
|
+
'''Throws an C{AttributeError}, always.
|
|
222
|
+
'''
|
|
223
|
+
raise self._Error(_invalid_, self.deleter, fdel)
|
|
224
|
+
|
|
225
|
+
def getter(self, fget):
|
|
226
|
+
'''Throws an C{AttributeError}, always.
|
|
227
|
+
'''
|
|
228
|
+
raise self._Error(_invalid_, self.getter, fget)
|
|
229
|
+
|
|
230
|
+
def setter(self, fset):
|
|
231
|
+
'''Throws an C{AttributeError}, always.
|
|
232
|
+
'''
|
|
233
|
+
raise self._Error(_immutable_, self.setter, fset)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class Property_RO(_PropertyBase):
|
|
237
|
+
# No __doc__ on purpose
|
|
238
|
+
def __init__(self, method, doc=NN): # PYCHOK expected
|
|
239
|
+
'''New I{immutable}, I{caching}, I{memoizing} C{property} I{Factory}
|
|
240
|
+
to be used as C{decorator}.
|
|
241
|
+
|
|
242
|
+
@arg method: The callable being decorated as this C{property}'s C{getter},
|
|
243
|
+
to be invoked only once.
|
|
244
|
+
@kwarg doc: Optional property documentation (C{str}).
|
|
245
|
+
|
|
246
|
+
@note: Like standard Python C{property} without a C{setter}, but with
|
|
247
|
+
a more descriptive error message when set.
|
|
248
|
+
|
|
249
|
+
@see: Python 3's U{functools.cached_property<https://docs.Python.org/3/
|
|
250
|
+
library/functools.html#functools.cached_property>} and U{-.cache
|
|
251
|
+
<https://Docs.Python.org/3/library/functools.html#functools.cache>}
|
|
252
|
+
to I{cache} or I{memoize} the property value.
|
|
253
|
+
|
|
254
|
+
@see: Luciano Ramalho, "Fluent Python", page 636, O'Reilly, Example
|
|
255
|
+
19-24, 2016 p. 636 or Example 22-28, 2022 p. 869+ and U{class
|
|
256
|
+
Property<https://docs.Python.org/3/howto/descriptor.html>}.
|
|
257
|
+
'''
|
|
258
|
+
_fget = method if _FOR_DOCS else self._fget # XXX force method.__doc__ to epydoc
|
|
259
|
+
_PropertyBase.__init__(self, method, _fget, self._fset_error)
|
|
260
|
+
|
|
261
|
+
def __get__(self, inst, *unused): # PYCHOK 2 vs 3 args
|
|
262
|
+
if inst is None:
|
|
263
|
+
return self
|
|
264
|
+
try: # to get the cached value immediately
|
|
265
|
+
return inst.__dict__[self.name]
|
|
266
|
+
except (AttributeError, KeyError):
|
|
267
|
+
return self._fget(inst)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class Property(Property_RO):
|
|
271
|
+
# No __doc__ on purpose
|
|
272
|
+
__init__ = Property_RO.__init__
|
|
273
|
+
'''New I{mutable}, I{caching}, I{memoizing} C{property} I{Factory}
|
|
274
|
+
to be used as C{decorator}.
|
|
275
|
+
|
|
276
|
+
@see: L{Property_RO} for more details.
|
|
277
|
+
|
|
278
|
+
@note: Unless and until the C{setter} is defined, this L{Property} behaves
|
|
279
|
+
like an I{immutable}, I{caching}, I{memoizing} L{Property_RO}.
|
|
280
|
+
'''
|
|
281
|
+
|
|
282
|
+
def setter(self, method):
|
|
283
|
+
'''Make this C{Property} I{mutable}.
|
|
284
|
+
|
|
285
|
+
@arg method: The callable being decorated as this C{Property}'s C{setter}.
|
|
286
|
+
|
|
287
|
+
@note: Setting a new property value always clears the previously I{cached}
|
|
288
|
+
or I{memoized} value I{after} invoking the B{C{method}}.
|
|
289
|
+
'''
|
|
290
|
+
if not callable(method):
|
|
291
|
+
_PropertyBase.setter(self, method) # PYCHOK no cover
|
|
292
|
+
|
|
293
|
+
if _FOR_DOCS: # XXX force method.__doc__ into epydoc
|
|
294
|
+
_PropertyBase.__init__(self, self.method, self.method, method)
|
|
295
|
+
else:
|
|
296
|
+
|
|
297
|
+
def _fset(inst, val):
|
|
298
|
+
'''Set and I{cache}, I{memoize} the C{property} value.
|
|
299
|
+
'''
|
|
300
|
+
method(inst, val)
|
|
301
|
+
self._update(inst) # un-cache this item
|
|
302
|
+
|
|
303
|
+
# class Property <https://docs.Python.org/3/howto/descriptor.html>
|
|
304
|
+
_PropertyBase.__init__(self, self.method, self._fget, _fset)
|
|
305
|
+
return self
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class property_RO(_PropertyBase):
|
|
309
|
+
# No __doc__ on purpose
|
|
310
|
+
_uname = NN
|
|
311
|
+
|
|
312
|
+
def __init__(self, method, doc=NN): # PYCHOK expected
|
|
313
|
+
'''New I{immutable}, standard C{property} to be used as C{decorator}.
|
|
314
|
+
|
|
315
|
+
@arg method: The callable being decorated as C{property}'s C{getter}.
|
|
316
|
+
@kwarg doc: Optional property documentation (C{str}).
|
|
317
|
+
|
|
318
|
+
@note: Like standard Python C{property} without a setter, but with
|
|
319
|
+
a more descriptive error message when set.
|
|
320
|
+
|
|
321
|
+
@see: L{Property_RO}.
|
|
322
|
+
'''
|
|
323
|
+
_PropertyBase.__init__(self, method, method, self._fset_error, doc=doc)
|
|
324
|
+
self._uname = NN(_UNDER_, self.name) # actual attr UNDER<name>
|
|
325
|
+
|
|
326
|
+
def _update(self, inst, *Clas): # PYCHOK signature
|
|
327
|
+
'''(INTERNAL) Zap the I{cached} C{B{inst}.__dict__[_name]} item.
|
|
328
|
+
'''
|
|
329
|
+
uname = self._uname
|
|
330
|
+
if uname in inst.__dict__:
|
|
331
|
+
if Clas: # overrides inst.__class__
|
|
332
|
+
d = Clas[0].__dict__.get(uname, MISSING)
|
|
333
|
+
else:
|
|
334
|
+
d = getattr(inst.__class__, uname, MISSING)
|
|
335
|
+
# if d is MISSING: # XXX superfluous
|
|
336
|
+
# for c in inst.__class__.__mro__[:-1]:
|
|
337
|
+
# if uname in c.__dict__:
|
|
338
|
+
# d = c.__dict__[uname]
|
|
339
|
+
# break
|
|
340
|
+
if d is None: # remove inst value
|
|
341
|
+
inst.__dict__.pop(uname)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class _NamedProperty(property):
|
|
345
|
+
'''Class C{property} with retrievable name.
|
|
346
|
+
'''
|
|
347
|
+
@Property_RO
|
|
348
|
+
def name(self):
|
|
349
|
+
'''Get the name of this C{property} (C{str}).
|
|
350
|
+
'''
|
|
351
|
+
return self.fget.__name__
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def property_doc_(doc):
|
|
355
|
+
'''Decorator for a standard C{property} with basic documentation.
|
|
356
|
+
|
|
357
|
+
@arg doc: The property documentation (C{str}).
|
|
358
|
+
|
|
359
|
+
@example:
|
|
360
|
+
|
|
361
|
+
>>> @property_doc_("documentation text.")
|
|
362
|
+
>>> def name(self):
|
|
363
|
+
>>> ...
|
|
364
|
+
>>>
|
|
365
|
+
>>> @name.setter
|
|
366
|
+
>>> def name(self, value):
|
|
367
|
+
>>> ...
|
|
368
|
+
'''
|
|
369
|
+
# See Luciano Ramalho, "Fluent Python", O'Reilly, Example 7-23,
|
|
370
|
+
# 2016 p. 212+, 2022 p. 331+, Example 9-22 and <https://
|
|
371
|
+
# Python-3-Patterns-Idioms-Test.ReadTheDocs.io/en/latest/PythonDecorators.html>
|
|
372
|
+
|
|
373
|
+
def _documented_property(method):
|
|
374
|
+
'''(INTERNAL) Return the documented C{property}.
|
|
375
|
+
'''
|
|
376
|
+
t = _get_and_set_ if doc.startswith(_SPACE_) else NN
|
|
377
|
+
return _NamedProperty(method, None, None, NN('Property to ', t, doc))
|
|
378
|
+
|
|
379
|
+
return _documented_property
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _deprecated(call, kind, qual_d):
|
|
383
|
+
'''(INTERNAL) Decorator for DEPRECATED functions, methods, etc.
|
|
384
|
+
|
|
385
|
+
@see: Brett Slatkin, "Effective Python", page 105, 2nd ed,
|
|
386
|
+
Addison-Wesley, 2019.
|
|
387
|
+
'''
|
|
388
|
+
doc = _docof(call)
|
|
389
|
+
|
|
390
|
+
@_wraps(call) # PYCHOK self?
|
|
391
|
+
def _deprecated_call(*args, **kwds):
|
|
392
|
+
if qual_d: # function
|
|
393
|
+
q = qual_d
|
|
394
|
+
elif args: # method
|
|
395
|
+
q = _qualified(args[0], call.__name__)
|
|
396
|
+
else: # PYCHOK no cover
|
|
397
|
+
q = call.__name__
|
|
398
|
+
_throwarning(kind, q, doc)
|
|
399
|
+
return call(*args, **kwds)
|
|
400
|
+
|
|
401
|
+
return _deprecated_call
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def deprecated_class(cls_or_class):
|
|
405
|
+
'''Use inside __new__ or __init__ of a DEPRECATED class.
|
|
406
|
+
|
|
407
|
+
@arg cls_or_class: The class (C{cls} or C{Class}).
|
|
408
|
+
|
|
409
|
+
@note: NOT a decorator!
|
|
410
|
+
'''
|
|
411
|
+
if _WARNINGS_X_DEV:
|
|
412
|
+
q = _DOT_(cls_or_class.__module__, cls_or_class.__name__)
|
|
413
|
+
_throwarning(_class_, q, cls_or_class.__doc__)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def deprecated_function(call):
|
|
417
|
+
'''Decorator for a DEPRECATED function.
|
|
418
|
+
|
|
419
|
+
@arg call: The deprecated function (C{callable}).
|
|
420
|
+
|
|
421
|
+
@return: The B{C{call}} DEPRECATED.
|
|
422
|
+
'''
|
|
423
|
+
return _deprecated(call, _function_, _DOT_(
|
|
424
|
+
call.__module__, call.__name__)) if \
|
|
425
|
+
_WARNINGS_X_DEV else call
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def deprecated_method(call):
|
|
429
|
+
'''Decorator for a DEPRECATED method.
|
|
430
|
+
|
|
431
|
+
@arg call: The deprecated method (C{callable}).
|
|
432
|
+
|
|
433
|
+
@return: The B{C{call}} DEPRECATED.
|
|
434
|
+
'''
|
|
435
|
+
return _deprecated(call, _method_, NN) if _WARNINGS_X_DEV else call
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _deprecated_module(name): # PYCHOK no cover
|
|
439
|
+
'''(INTERNAL) Callable within a DEPRECATED module.
|
|
440
|
+
'''
|
|
441
|
+
if _WARNINGS_X_DEV:
|
|
442
|
+
_throwarning(_module_, name, _dont_use_)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
if _WARNINGS_X_DEV:
|
|
446
|
+
class deprecated_property(_PropertyBase):
|
|
447
|
+
'''Decorator for a DEPRECATED C{property} or C{Property}.
|
|
448
|
+
'''
|
|
449
|
+
def __init__(self, method):
|
|
450
|
+
'''Decorator for a DEPRECATED C{property} or C{Property} getter.
|
|
451
|
+
'''
|
|
452
|
+
doc = _docof(method)
|
|
453
|
+
|
|
454
|
+
def _fget(inst): # PYCHOK no cover
|
|
455
|
+
'''Get the C{property} or C{Property} value.
|
|
456
|
+
'''
|
|
457
|
+
q = _qualified(inst, self.name)
|
|
458
|
+
_throwarning(property.__name__, q, doc)
|
|
459
|
+
return self.method(inst) # == method
|
|
460
|
+
|
|
461
|
+
_PropertyBase.__init__(self, method, _fget, None, doc=doc)
|
|
462
|
+
|
|
463
|
+
def setter(self, method):
|
|
464
|
+
'''Decorator for a DEPRECATED C{property} or C{Property} setter.
|
|
465
|
+
|
|
466
|
+
@arg method: The callable being decorated as this C{Property}'s C{setter}.
|
|
467
|
+
|
|
468
|
+
@note: Setting a new property value always clears the previously I{cached}
|
|
469
|
+
or I{memoized} value I{after} invoking the B{C{method}}.
|
|
470
|
+
'''
|
|
471
|
+
if not callable(method):
|
|
472
|
+
_PropertyBase.setter(self, method) # PYCHOK no cover
|
|
473
|
+
|
|
474
|
+
if _FOR_DOCS: # XXX force method.__doc__ into epydoc
|
|
475
|
+
_PropertyBase.__init__(self, self.method, self.method, method)
|
|
476
|
+
else:
|
|
477
|
+
|
|
478
|
+
def _fset(inst, val):
|
|
479
|
+
'''Set the C{property} or C{Property} value.
|
|
480
|
+
'''
|
|
481
|
+
q = _qualified(inst, self.name)
|
|
482
|
+
_throwarning(property.__name__, q, _docof(method))
|
|
483
|
+
method(inst, val)
|
|
484
|
+
# self._update(inst) # un-cache this item
|
|
485
|
+
|
|
486
|
+
# class Property <https://docs.Python.org/3/howto/descriptor.html>
|
|
487
|
+
_PropertyBase.__init__(self, self.method, self._fget, _fset)
|
|
488
|
+
return self
|
|
489
|
+
|
|
490
|
+
else: # PYCHOK no cover
|
|
491
|
+
class deprecated_property(property): # PYCHOK expected
|
|
492
|
+
'''Decorator for a DEPRECATED C{property} or C{Property}.
|
|
493
|
+
'''
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
deprecated_Property = deprecated_property
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def deprecated_Property_RO(method):
|
|
500
|
+
'''Decorator for a DEPRECATED L{Property_RO}.
|
|
501
|
+
|
|
502
|
+
@arg method: The C{Property_RO.fget} method (C{callable}).
|
|
503
|
+
|
|
504
|
+
@return: The B{C{method}} DEPRECATED.
|
|
505
|
+
'''
|
|
506
|
+
return _deprecated_RO(method, Property_RO)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def deprecated_property_RO(method):
|
|
510
|
+
'''Decorator for a DEPRECATED L{property_RO}.
|
|
511
|
+
|
|
512
|
+
@arg method: The C{property_RO.fget} method (C{callable}).
|
|
513
|
+
|
|
514
|
+
@return: The B{C{method}} DEPRECATED.
|
|
515
|
+
'''
|
|
516
|
+
return _deprecated_RO(method, property_RO)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _deprecated_RO(method, _RO):
|
|
520
|
+
'''(INTERNAL) Create a DEPRECATED C{property_RO} or C{Property_RO}.
|
|
521
|
+
'''
|
|
522
|
+
doc = _docof(method)
|
|
523
|
+
|
|
524
|
+
if _WARNINGS_X_DEV:
|
|
525
|
+
|
|
526
|
+
class _Deprecated_RO(_PropertyBase):
|
|
527
|
+
__doc__ = doc
|
|
528
|
+
|
|
529
|
+
def __init__(self, method):
|
|
530
|
+
_PropertyBase.__init__(self, method, self._fget, self._fset_error, doc=doc)
|
|
531
|
+
|
|
532
|
+
def _fget(self, inst): # PYCHOK no cover
|
|
533
|
+
q = _qualified(inst, self.name)
|
|
534
|
+
_throwarning(_RO.__name__, q, doc)
|
|
535
|
+
return self.method(inst)
|
|
536
|
+
|
|
537
|
+
return _Deprecated_RO(method)
|
|
538
|
+
else: # PYCHOK no cover
|
|
539
|
+
return _RO(method, doc=doc)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _docof(obj):
|
|
543
|
+
'''(INTERNAL) Get uniform DEPRECATED __doc__ string.
|
|
544
|
+
'''
|
|
545
|
+
try:
|
|
546
|
+
d = obj.__doc__.strip()
|
|
547
|
+
i = d.find(_DEPRECATED_)
|
|
548
|
+
except AttributeError:
|
|
549
|
+
i = -1
|
|
550
|
+
return _DOT_(_DEPRECATED_, NN) if i < 0 else d[i:]
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _qualified(inst, name):
|
|
554
|
+
'''(INTERNAL) Fully qualify a name.
|
|
555
|
+
'''
|
|
556
|
+
# _DOT_(inst.classname, name), not _DOT_(inst.named4, name)
|
|
557
|
+
c = inst.__class__
|
|
558
|
+
q = _DOT_(c.__module__, c.__name__, name)
|
|
559
|
+
return q
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class DeprecationWarnings(object):
|
|
563
|
+
'''(INTERNAL) Handle C{DeprecationWaring}s.
|
|
564
|
+
'''
|
|
565
|
+
_Warnings = 0
|
|
566
|
+
|
|
567
|
+
def __call__(self): # for backward compatibility
|
|
568
|
+
'''Have any C{DeprecationWarning}s been reported or raised?
|
|
569
|
+
|
|
570
|
+
@return: The number of C{DeprecationWarning}s (C{int}) so
|
|
571
|
+
far or C{None} if not enabled.
|
|
572
|
+
|
|
573
|
+
@note: To get C{DeprecationWarning}s if any, run C{python}
|
|
574
|
+
with env var C{PYGEODESY_WARNINGS} set to a non-empty
|
|
575
|
+
string I{AND} use C{python[3]} command line option
|
|
576
|
+
C{-X dev}, C{-W always} or C{-W error}, etc.
|
|
577
|
+
'''
|
|
578
|
+
return self.Warnings
|
|
579
|
+
|
|
580
|
+
def throw(self, kind, name, doc, **stacklevel): # stacklevel=3
|
|
581
|
+
'''Report or raise a C{DeprecationWarning}.
|
|
582
|
+
'''
|
|
583
|
+
line = doc.split(_DNL_, 1)[0].strip()
|
|
584
|
+
name = _MODS.streprs.Fmt.CURLY(L=name)
|
|
585
|
+
text = _SPACE_(kind, name, _has_been_, *line.split())
|
|
586
|
+
kwds = _xkwds(stacklevel, stacklevel=3)
|
|
587
|
+
# XXX invoke warn or raise DeprecationWarning(text)
|
|
588
|
+
self._warn(text, category=DeprecationWarning, **kwds)
|
|
589
|
+
self._Warnings += 1
|
|
590
|
+
|
|
591
|
+
@Property_RO
|
|
592
|
+
def _warn(self):
|
|
593
|
+
'''Get Python's C{warnings.warn}.
|
|
594
|
+
'''
|
|
595
|
+
from warnings import warn
|
|
596
|
+
return warn
|
|
597
|
+
|
|
598
|
+
@property_RO
|
|
599
|
+
def Warnings(self):
|
|
600
|
+
'''Get the number of C{DeprecationWarning}s (C{int}) so
|
|
601
|
+
far or C{None} if not enabled.
|
|
602
|
+
'''
|
|
603
|
+
return self._Warnings if _WARNINGS_X_DEV else None
|
|
604
|
+
|
|
605
|
+
DeprecationWarnings = DeprecationWarnings() # PYCHOK singleton
|
|
606
|
+
_throwarning = DeprecationWarnings.throw
|
|
607
|
+
|
|
608
|
+
# **) MIT License
|
|
609
|
+
#
|
|
610
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
611
|
+
#
|
|
612
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
613
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
614
|
+
# to deal in the Software without restriction, including without limitation
|
|
615
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
616
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
617
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
618
|
+
#
|
|
619
|
+
# The above copyright notice and this permission notice shall be included
|
|
620
|
+
# in all copies or substantial portions of the Software.
|
|
621
|
+
#
|
|
622
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
623
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
624
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
625
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
626
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
627
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
628
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|