cs-py-func 20220619__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.
cs/py/func.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
#
|
|
3
|
+
# Convenience routines for python functions.
|
|
4
|
+
# - Cameron Simpson <cs@cskk.id.au> 15apr2014
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
r'''
|
|
8
|
+
Convenience facilities related to Python functions.
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
from functools import partial
|
|
12
|
+
from pprint import pformat
|
|
13
|
+
from cs.deco import decorator
|
|
14
|
+
from cs.py.stack import caller
|
|
15
|
+
from cs.py3 import unicode, raise_from
|
|
16
|
+
from cs.x import X
|
|
17
|
+
|
|
18
|
+
__version__ = '20220619'
|
|
19
|
+
|
|
20
|
+
DISTINFO = {
|
|
21
|
+
'keywords': ["python2", "python3"],
|
|
22
|
+
'classifiers': [
|
|
23
|
+
"Programming Language :: Python",
|
|
24
|
+
"Programming Language :: Python :: 2",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
],
|
|
27
|
+
'install_requires': [
|
|
28
|
+
'cs.deco',
|
|
29
|
+
'cs.py.stack',
|
|
30
|
+
'cs.py3',
|
|
31
|
+
'cs.x',
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def funcname(func):
|
|
36
|
+
''' Return a name for the supplied function `func`.
|
|
37
|
+
Several objects do not have a __name__ attribute, such as partials.
|
|
38
|
+
'''
|
|
39
|
+
try:
|
|
40
|
+
return func.__qualname__
|
|
41
|
+
except AttributeError:
|
|
42
|
+
try:
|
|
43
|
+
return func.__name__
|
|
44
|
+
except AttributeError:
|
|
45
|
+
if isinstance(func, partial):
|
|
46
|
+
return "partial(%s)" % (funcname(func.func),)
|
|
47
|
+
return str(func)
|
|
48
|
+
|
|
49
|
+
def funccite(func):
|
|
50
|
+
''' Return a citation for a function (name and code location).
|
|
51
|
+
'''
|
|
52
|
+
try:
|
|
53
|
+
code = func.__code__
|
|
54
|
+
except AttributeError:
|
|
55
|
+
return "%s[no.__code__]" % (repr(func),)
|
|
56
|
+
return "%s[%s:%d]" % (funcname(func), code.co_filename, code.co_firstlineno)
|
|
57
|
+
|
|
58
|
+
def func_a_kw_fmt(func, *a, **kw):
|
|
59
|
+
''' Prepare a percent-format string and associated argument list
|
|
60
|
+
describing a call to `func(*a,**kw)`.
|
|
61
|
+
Return `format,args`.
|
|
62
|
+
|
|
63
|
+
The `func` argument can also be a string,
|
|
64
|
+
presumably a prepared description of `func` such as `funccite(func)`.
|
|
65
|
+
'''
|
|
66
|
+
av = [
|
|
67
|
+
func if isinstance(func, str) else getattr(func, '__name__', str(func))
|
|
68
|
+
]
|
|
69
|
+
afv = ['%r'] * len(a)
|
|
70
|
+
av.extend(a)
|
|
71
|
+
afv.extend(['%s=%r'] * len(kw))
|
|
72
|
+
for kv in kw.items():
|
|
73
|
+
av.extend(kv)
|
|
74
|
+
return '%s(' + ','.join(afv) + ')', av
|
|
75
|
+
|
|
76
|
+
def callif(doit, func, *a, **kw):
|
|
77
|
+
''' Call `func(*a,**kw)` if `doit` is true
|
|
78
|
+
otherwise just print it out.
|
|
79
|
+
|
|
80
|
+
The parameter `func` may be preceeded optionally by a `dict`
|
|
81
|
+
containing modes. The current modes are:
|
|
82
|
+
* `'print'`: the print function, default the builtin `print`
|
|
83
|
+
'''
|
|
84
|
+
if isinstance(func, dict):
|
|
85
|
+
modes = func
|
|
86
|
+
a = list(a)
|
|
87
|
+
func = a.pop(0)
|
|
88
|
+
else:
|
|
89
|
+
modes = {}
|
|
90
|
+
modes.setdefault('print', print)
|
|
91
|
+
if doit:
|
|
92
|
+
return func(*a, **kw)
|
|
93
|
+
fmt, av = func_a_kw_fmt(func, *a, **kw)
|
|
94
|
+
modes['print'](fmt % tuple(av))
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
@decorator
|
|
98
|
+
# pylint: disable=too-many-arguments
|
|
99
|
+
def trace(
|
|
100
|
+
func,
|
|
101
|
+
call=True,
|
|
102
|
+
retval=False,
|
|
103
|
+
exception=False,
|
|
104
|
+
use_pformat=False,
|
|
105
|
+
with_caller=False,
|
|
106
|
+
with_pfx=False,
|
|
107
|
+
):
|
|
108
|
+
''' Decorator to report the call and return of a function.
|
|
109
|
+
'''
|
|
110
|
+
|
|
111
|
+
citation = funccite(func)
|
|
112
|
+
|
|
113
|
+
def traced_function_wrapper(*a, **kw):
|
|
114
|
+
''' Wrapper for `func` to trace call and return.
|
|
115
|
+
'''
|
|
116
|
+
# late import so that we can use this in modules we import
|
|
117
|
+
# pylint: disable=import-outside-toplevel
|
|
118
|
+
if with_pfx:
|
|
119
|
+
try:
|
|
120
|
+
from cs.pfx import XP as xlog
|
|
121
|
+
except ImportError:
|
|
122
|
+
xlog = X
|
|
123
|
+
else:
|
|
124
|
+
xlog = X
|
|
125
|
+
log_cite = citation
|
|
126
|
+
if with_caller:
|
|
127
|
+
log_cite = log_cite + "from[%s]" % (caller(),)
|
|
128
|
+
if call:
|
|
129
|
+
fmt, av = func_a_kw_fmt(log_cite, *a, **kw)
|
|
130
|
+
xlog("CALL " + fmt, *av)
|
|
131
|
+
try:
|
|
132
|
+
result = func(*a, **kw)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
if exception:
|
|
135
|
+
xlog("CALL %s RAISE %r", log_cite, e)
|
|
136
|
+
raise
|
|
137
|
+
else:
|
|
138
|
+
if retval:
|
|
139
|
+
xlog(
|
|
140
|
+
"CALL %s RETURN %s", log_cite,
|
|
141
|
+
(pformat if use_pformat else repr)(result)
|
|
142
|
+
)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
traced_function_wrapper.__name__ = "@trace(%s)" % (citation,)
|
|
146
|
+
traced_function_wrapper.__doc__ = "@trace(%s)\n\n" + (func.__doc__ or '')
|
|
147
|
+
return traced_function_wrapper
|
|
148
|
+
|
|
149
|
+
def callmethod_if(o, method, default=None, a=None, kw=None):
|
|
150
|
+
''' Call the named `method` on the object `o` if it exists.
|
|
151
|
+
|
|
152
|
+
If it does not exist, return `default` (which defaults to None).
|
|
153
|
+
Otherwise call getattr(o, method)(*a, **kw).
|
|
154
|
+
`a` defaults to ().
|
|
155
|
+
`kw` defaults to {}.
|
|
156
|
+
'''
|
|
157
|
+
try:
|
|
158
|
+
m = getattr(o, method)
|
|
159
|
+
except AttributeError:
|
|
160
|
+
return default
|
|
161
|
+
if a is None:
|
|
162
|
+
a = ()
|
|
163
|
+
if kw is None:
|
|
164
|
+
kw = {}
|
|
165
|
+
return m(*a, **kw)
|
|
166
|
+
|
|
167
|
+
def prop(func):
|
|
168
|
+
''' A substitute for the builtin @property.
|
|
169
|
+
|
|
170
|
+
The builtin @property decorator lets internal AttributeErrors escape.
|
|
171
|
+
While that can support properties that appear to exist conditionally,
|
|
172
|
+
in practice this is almost never what I want, and it masks deeper errors.
|
|
173
|
+
Hence this wrapper for @property that transmutes internal AttributeErrors
|
|
174
|
+
into RuntimeErrors.
|
|
175
|
+
'''
|
|
176
|
+
|
|
177
|
+
# pylint: disable=inconsistent-return-statements
|
|
178
|
+
def prop_wrapper(*a, **kw):
|
|
179
|
+
try:
|
|
180
|
+
return func(*a, **kw)
|
|
181
|
+
except AttributeError as e:
|
|
182
|
+
raise_from(RuntimeError("inner function %s raised %s" % (func, e)), e)
|
|
183
|
+
|
|
184
|
+
prop_wrapper.__name__ = "@prop(%s)" % (funcname(func),)
|
|
185
|
+
return property(prop_wrapper)
|
|
186
|
+
|
|
187
|
+
def derived_property(
|
|
188
|
+
func,
|
|
189
|
+
original_revision_name='_revision',
|
|
190
|
+
lock_name='_lock',
|
|
191
|
+
property_name=None,
|
|
192
|
+
unset_object=None
|
|
193
|
+
):
|
|
194
|
+
''' A property which must be recomputed
|
|
195
|
+
if the reference revision (attached to self)
|
|
196
|
+
exceeds the snapshot revision.
|
|
197
|
+
'''
|
|
198
|
+
if property_name is None:
|
|
199
|
+
property_name = '_' + func.__name__
|
|
200
|
+
# the property used to track the reference revision
|
|
201
|
+
property_revision_name = property_name + '__revision'
|
|
202
|
+
|
|
203
|
+
def property_value(self):
|
|
204
|
+
''' Attempt lockless fetch of property first.
|
|
205
|
+
Use lock if property is unset and up to date.
|
|
206
|
+
'''
|
|
207
|
+
# poll outside lock
|
|
208
|
+
try:
|
|
209
|
+
p = getattr(self, property_name, unset_object)
|
|
210
|
+
p_revision = getattr(self, property_revision_name, 0)
|
|
211
|
+
o_revision = getattr(self, original_revision_name)
|
|
212
|
+
if p is unset_object or p_revision < o_revision:
|
|
213
|
+
with getattr(self, lock_name):
|
|
214
|
+
# repoll value inside lock
|
|
215
|
+
p = getattr(self, property_name, unset_object)
|
|
216
|
+
p_revision = getattr(self, property_revision_name, 0)
|
|
217
|
+
o_revision = getattr(self, original_revision_name)
|
|
218
|
+
if p is unset_object or p_revision < o_revision:
|
|
219
|
+
p = func(self)
|
|
220
|
+
setattr(self, property_name, p)
|
|
221
|
+
setattr(self, property_revision_name, o_revision)
|
|
222
|
+
except AttributeError as e:
|
|
223
|
+
raise_from(RuntimeError("AttributeError: %s" % (e,)), e)
|
|
224
|
+
return p
|
|
225
|
+
|
|
226
|
+
return property(property_value)
|
|
227
|
+
|
|
228
|
+
def derived_from(property_name):
|
|
229
|
+
''' A property which must be recomputed
|
|
230
|
+
if the revision of another property exceeds the snapshot revision.
|
|
231
|
+
'''
|
|
232
|
+
return partial(
|
|
233
|
+
derived_property,
|
|
234
|
+
original_revision_name='_' + property_name + '__revision'
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def yields_type(func, basetype):
|
|
238
|
+
''' Decorator which checks that a generator yields values of type `basetype`.
|
|
239
|
+
'''
|
|
240
|
+
citation = funccite(func)
|
|
241
|
+
|
|
242
|
+
def check_yields_type(*a, **kw):
|
|
243
|
+
for item in func(*a, **kw):
|
|
244
|
+
if not isinstance(item, basetype):
|
|
245
|
+
raise TypeError(
|
|
246
|
+
"wrong type yielded from func %s: expected subclass of %s, got %s: %r"
|
|
247
|
+
% (citation, basetype, type(item), item)
|
|
248
|
+
)
|
|
249
|
+
yield item
|
|
250
|
+
|
|
251
|
+
check_yields_type.__name__ = (
|
|
252
|
+
'check_yields_type[%s,basetype=%s]' % (citation, basetype)
|
|
253
|
+
)
|
|
254
|
+
return check_yields_type
|
|
255
|
+
|
|
256
|
+
def returns_type(func, basetype):
|
|
257
|
+
''' Decrator which checks that a function returns values of type `basetype`.
|
|
258
|
+
'''
|
|
259
|
+
citation = funccite(func)
|
|
260
|
+
|
|
261
|
+
def check_returns_type(*a, **kw):
|
|
262
|
+
retval = func(*a, **kw)
|
|
263
|
+
if not isinstance(retval, basetype):
|
|
264
|
+
raise TypeError(
|
|
265
|
+
"wrong type returned from func %s: expected subclass of %s, got %s: %r"
|
|
266
|
+
% (citation, basetype, type(retval), retval)
|
|
267
|
+
)
|
|
268
|
+
return retval
|
|
269
|
+
|
|
270
|
+
check_returns_type.__name__ = (
|
|
271
|
+
'check_returns_type[%s,basetype=%s]' % (citation, basetype)
|
|
272
|
+
)
|
|
273
|
+
return check_returns_type
|
|
274
|
+
|
|
275
|
+
def yields_str(func):
|
|
276
|
+
''' Decorator for generators which should yield strings.
|
|
277
|
+
'''
|
|
278
|
+
return yields_type(func, (str, unicode))
|
|
279
|
+
|
|
280
|
+
def returns_bool(func):
|
|
281
|
+
''' Decorator for functions which should return Booleans.
|
|
282
|
+
'''
|
|
283
|
+
return returns_type(func, bool)
|
|
284
|
+
|
|
285
|
+
def returns_str(func):
|
|
286
|
+
''' Decorator for functions which should return strings.
|
|
287
|
+
'''
|
|
288
|
+
return returns_type(func, (str, unicode))
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cs.py.func
|
|
3
|
+
Version: 20220619
|
|
4
|
+
Summary: Convenience facilities related to Python functions.
|
|
5
|
+
Home-page: https://bitbucket.org/cameron_simpson/css/commits/all
|
|
6
|
+
Author: Cameron Simpson
|
|
7
|
+
Author-email: Cameron Simpson <cs@cskk.id.au>
|
|
8
|
+
License: GNU General Public License v3 or later (GPLv3+)
|
|
9
|
+
Project-URL: URL, https://bitbucket.org/cameron_simpson/css/commits/all
|
|
10
|
+
Keywords: python2,python3
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 2
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: cs.deco (>=20220327)
|
|
21
|
+
Requires-Dist: cs.py.stack (>=20220429)
|
|
22
|
+
Requires-Dist: cs.py3 (>=20220523)
|
|
23
|
+
Requires-Dist: cs.x (>=20211208)
|
|
24
|
+
|
|
25
|
+
Convenience facilities related to Python functions.
|
|
26
|
+
|
|
27
|
+
*Latest release 20220619*:
|
|
28
|
+
@trace: rename some parameters, add with_caller.
|
|
29
|
+
|
|
30
|
+
## Function `callif(doit, func, *a, **kw)`
|
|
31
|
+
|
|
32
|
+
Call `func(*a,**kw)` if `doit` is true
|
|
33
|
+
otherwise just print it out.
|
|
34
|
+
|
|
35
|
+
The parameter `func` may be preceeded optionally by a `dict`
|
|
36
|
+
containing modes. The current modes are:
|
|
37
|
+
* `'print'`: the print function, default the builtin `print`
|
|
38
|
+
|
|
39
|
+
## Function `callmethod_if(o, method, default=None, a=None, kw=None)`
|
|
40
|
+
|
|
41
|
+
Call the named `method` on the object `o` if it exists.
|
|
42
|
+
|
|
43
|
+
If it does not exist, return `default` (which defaults to None).
|
|
44
|
+
Otherwise call getattr(o, method)(*a, **kw).
|
|
45
|
+
`a` defaults to ().
|
|
46
|
+
`kw` defaults to {}.
|
|
47
|
+
|
|
48
|
+
## Function `derived_from(property_name)`
|
|
49
|
+
|
|
50
|
+
A property which must be recomputed
|
|
51
|
+
if the revision of another property exceeds the snapshot revision.
|
|
52
|
+
|
|
53
|
+
## Function `derived_property(func, original_revision_name='_revision', lock_name='_lock', property_name=None, unset_object=None)`
|
|
54
|
+
|
|
55
|
+
A property which must be recomputed
|
|
56
|
+
if the reference revision (attached to self)
|
|
57
|
+
exceeds the snapshot revision.
|
|
58
|
+
|
|
59
|
+
## Function `func_a_kw_fmt(func, *a, **kw)`
|
|
60
|
+
|
|
61
|
+
Prepare a percent-format string and associated argument list
|
|
62
|
+
describing a call to `func(*a,**kw)`.
|
|
63
|
+
Return `format,args`.
|
|
64
|
+
|
|
65
|
+
The `func` argument can also be a string,
|
|
66
|
+
presumably a prepared description of `func` such as `funccite(func)`.
|
|
67
|
+
|
|
68
|
+
## Function `funccite(func)`
|
|
69
|
+
|
|
70
|
+
Return a citation for a function (name and code location).
|
|
71
|
+
|
|
72
|
+
## Function `funcname(func)`
|
|
73
|
+
|
|
74
|
+
Return a name for the supplied function `func`.
|
|
75
|
+
Several objects do not have a __name__ attribute, such as partials.
|
|
76
|
+
|
|
77
|
+
## Function `prop(func)`
|
|
78
|
+
|
|
79
|
+
A substitute for the builtin @property.
|
|
80
|
+
|
|
81
|
+
The builtin @property decorator lets internal AttributeErrors escape.
|
|
82
|
+
While that can support properties that appear to exist conditionally,
|
|
83
|
+
in practice this is almost never what I want, and it masks deeper errors.
|
|
84
|
+
Hence this wrapper for @property that transmutes internal AttributeErrors
|
|
85
|
+
into RuntimeErrors.
|
|
86
|
+
|
|
87
|
+
## Function `returns_bool(func)`
|
|
88
|
+
|
|
89
|
+
Decorator for functions which should return Booleans.
|
|
90
|
+
|
|
91
|
+
## Function `returns_str(func)`
|
|
92
|
+
|
|
93
|
+
Decorator for functions which should return strings.
|
|
94
|
+
|
|
95
|
+
## Function `returns_type(func, basetype)`
|
|
96
|
+
|
|
97
|
+
Decrator which checks that a function returns values of type `basetype`.
|
|
98
|
+
|
|
99
|
+
## Function `trace(*da, **dkw)`
|
|
100
|
+
|
|
101
|
+
Decorator to report the call and return of a function.
|
|
102
|
+
|
|
103
|
+
## Function `yields_str(func)`
|
|
104
|
+
|
|
105
|
+
Decorator for generators which should yield strings.
|
|
106
|
+
|
|
107
|
+
## Function `yields_type(func, basetype)`
|
|
108
|
+
|
|
109
|
+
Decorator which checks that a generator yields values of type `basetype`.
|
|
110
|
+
|
|
111
|
+
# Release Log
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
*Release 20220619*:
|
|
116
|
+
@trace: rename some parameters, add with_caller.
|
|
117
|
+
|
|
118
|
+
*Release 20220311.1*:
|
|
119
|
+
New callif(doit[,modes],func,*a,**kw) to call or print a function, used in -n/--dry-run stuff sometimes.
|
|
120
|
+
|
|
121
|
+
*Release 20220311*:
|
|
122
|
+
* @trace: new pprint=False option to use pprint.pformat instead of repr for the return value.
|
|
123
|
+
* @trace: bugfix use of retval parameter.
|
|
124
|
+
|
|
125
|
+
*Release 20210913*:
|
|
126
|
+
New func_a_kw_fmt(func,*a,**kw) imported from cs.pfx, hooked it into @trace.
|
|
127
|
+
|
|
128
|
+
*Release 20210906*:
|
|
129
|
+
funcname: special case functools.partial, return concise name.
|
|
130
|
+
|
|
131
|
+
*Release 20210717*:
|
|
132
|
+
* Move @trace from debug to py.func, defer log call imports to avoid loops.
|
|
133
|
+
* Drop cs.pfx requirement, import opportunisticly with fallback.
|
|
134
|
+
|
|
135
|
+
*Release 20200518*:
|
|
136
|
+
funcname: prefer `func.__qualname__` over `func.__name__`
|
|
137
|
+
|
|
138
|
+
*Release 20190729*:
|
|
139
|
+
funccite: handle callables with no __code__ attribute.
|
|
140
|
+
|
|
141
|
+
*Release 20190108*:
|
|
142
|
+
Break import loop. Use cs.py3.raise_from for portability.
|
|
143
|
+
|
|
144
|
+
*Release 20181231*:
|
|
145
|
+
* Some type specific convenience wrappers for yields_type and returns_type.
|
|
146
|
+
* Bugfix for @prop.
|
|
147
|
+
|
|
148
|
+
*Release 20170906.1*:
|
|
149
|
+
Minor tweaks.
|
|
150
|
+
|
|
151
|
+
*Release 20170906*:
|
|
152
|
+
Bugfix for @prop.
|
|
153
|
+
|
|
154
|
+
*Release 20170608*:
|
|
155
|
+
New decorator @prop which works just like the builtin @property, but transmutes internal AttributeError into RuntimeError, unmasking many bugs.
|
|
156
|
+
|
|
157
|
+
*Release 20160828*:
|
|
158
|
+
Use "install_requires" instead of "requires" in DISTINFO.
|
|
159
|
+
|
|
160
|
+
*Release 20150115*:
|
|
161
|
+
First PyPI release.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
cs/py/func.py,sha256=vUqCtqYJi6qlFUZZRyRanMt7DcgYxZVxVLI5SCDuuq4,8255
|
|
2
|
+
cs.py.func-20220619.dist-info/METADATA,sha256=LikrQehY1CiKfeKcbXIZhqA5FoSvP44nTxsmGkd6iDg,5012
|
|
3
|
+
cs.py.func-20220619.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
4
|
+
cs.py.func-20220619.dist-info/top_level.txt,sha256=MJn10B_hUOb4f5hIJP5wKj_mMYsOQ6NUWqo9vSLmwcQ,3
|
|
5
|
+
cs.py.func-20220619.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cs
|