cs-py-func 20250724__tar.gz
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-20250724/PKG-INFO +149 -0
- cs_py_func-20250724/pyproject.toml +173 -0
- cs_py_func-20250724/src/cs/py/func.py +192 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cs-py-func
|
|
3
|
+
Version: 20250724
|
|
4
|
+
Summary: Convenience facilities related to Python functions.
|
|
5
|
+
Keywords: python2,python3
|
|
6
|
+
Author-email: Cameron Simpson <cs@cskk.id.au>
|
|
7
|
+
Requires-Python: >=3
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 2
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
17
|
+
Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
|
|
18
|
+
Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
|
|
19
|
+
Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
|
|
20
|
+
Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/py/func.py
|
|
21
|
+
|
|
22
|
+
Convenience facilities related to Python functions.
|
|
23
|
+
|
|
24
|
+
*Latest release 20250724*:
|
|
25
|
+
* func_a_kw_fmt: shorten long %r, widen another field.
|
|
26
|
+
* Drop Python 2 support.
|
|
27
|
+
|
|
28
|
+
Short summary:
|
|
29
|
+
* `callif`: Call `func(*a,**kw)` if `doit` is true otherwise just print it out.
|
|
30
|
+
* `callmethod_if`: Call the named `method` on the object `obj` if it exists.
|
|
31
|
+
* `derived_from`: A property which must be recomputed if the revision of another property exceeds the snapshot revision.
|
|
32
|
+
* `derived_property`: A property which must be recomputed if the reference revision (attached to self) exceeds the snapshot revision.
|
|
33
|
+
* `func_a_kw`: Return a string representing a call to `func(*a,**kw)`.
|
|
34
|
+
* `func_a_kw_fmt`: Prepare a percent-format string and associated argument list describing a call to `func(*a,**kw)`. Return `format,args`.
|
|
35
|
+
* `funccite`: Return a citation for a function (name and code location).
|
|
36
|
+
* `funcname`: Return a name for the supplied function `func`. Several objects do not have a __name__ attribute, such as partials.
|
|
37
|
+
* `prop`: A substitute for the builtin @property.
|
|
38
|
+
|
|
39
|
+
Module contents:
|
|
40
|
+
- <a name="callif"></a>`callif(doit, func, *a, **kw)`: Call `func(*a,**kw)` if `doit` is true
|
|
41
|
+
otherwise just print it out.
|
|
42
|
+
|
|
43
|
+
The parameter `func` may be preceeded optionally by a `dict`
|
|
44
|
+
containing modes. The current modes are:
|
|
45
|
+
* `'print'`: the print function, default the builtin `print`
|
|
46
|
+
- <a name="callmethod_if"></a>`callmethod_if(obj, method, default=None, a=None, kw=None)`: Call the named `method` on the object `obj` if it exists.
|
|
47
|
+
|
|
48
|
+
If it does not exist, return `default` (which defaults to None).
|
|
49
|
+
Otherwise call getattr(obj, method)(*a, **kw).
|
|
50
|
+
`a` defaults to ().
|
|
51
|
+
`kw` defaults to {}.
|
|
52
|
+
- <a name="derived_from"></a>`derived_from(property_name)`: A property which must be recomputed
|
|
53
|
+
if the revision of another property exceeds the snapshot revision.
|
|
54
|
+
- <a name="derived_property"></a>`derived_property(func, original_revision_name='_revision', lock_name='_lock', property_name=None, unset_object=None)`: A property which must be recomputed
|
|
55
|
+
if the reference revision (attached to self)
|
|
56
|
+
exceeds the snapshot revision.
|
|
57
|
+
- <a name="func_a_kw"></a>`func_a_kw(func, *a, **kw)`: Return a string representing a call to `func(*a,**kw)`.
|
|
58
|
+
- <a name="func_a_kw_fmt"></a>`func_a_kw_fmt(func, *a, **kw)`: Prepare a percent-format string and associated argument list
|
|
59
|
+
describing a call to `func(*a,**kw)`.
|
|
60
|
+
Return `format,args`.
|
|
61
|
+
|
|
62
|
+
The `func` argument can also be a string,
|
|
63
|
+
typically a prepared description of `func` such as `funccite(func)`.
|
|
64
|
+
|
|
65
|
+
*Note*: the returned `args` is a `list` for easy incorporation
|
|
66
|
+
into further arguments. The `%` operator requires a `tuple`.
|
|
67
|
+
- <a name="funccite"></a>`funccite(func)`: Return a citation for a function (name and code location).
|
|
68
|
+
- <a name="funcname"></a>`funcname(func)`: Return a name for the supplied function `func`.
|
|
69
|
+
Several objects do not have a __name__ attribute, such as partials.
|
|
70
|
+
- <a name="prop"></a>`prop(func)`: A substitute for the builtin @property.
|
|
71
|
+
|
|
72
|
+
The builtin @property decorator lets internal AttributeErrors escape.
|
|
73
|
+
While that can support properties that appear to exist conditionally,
|
|
74
|
+
in practice this is almost never what I want, and it masks deeper errors.
|
|
75
|
+
Hence this wrapper for @property that transmutes internal AttributeErrors
|
|
76
|
+
into RuntimeErrors.
|
|
77
|
+
|
|
78
|
+
# Release Log
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
*Release 20250724*:
|
|
83
|
+
* func_a_kw_fmt: shorten long %r, widen another field.
|
|
84
|
+
* Drop Python 2 support.
|
|
85
|
+
|
|
86
|
+
*Release 20240630*:
|
|
87
|
+
funcname: include the module name, handle functions with no __module__.
|
|
88
|
+
|
|
89
|
+
*Release 20230331*:
|
|
90
|
+
Drop @trace, moved to cs.debug.
|
|
91
|
+
|
|
92
|
+
*Release 20230210*:
|
|
93
|
+
Drop the returns_* and yields_* decorators, these days type annotations and @typechecked are the go.
|
|
94
|
+
|
|
95
|
+
*Release 20221207*:
|
|
96
|
+
* @trace: trace exceptions by default, previously false.
|
|
97
|
+
* Late import of cs.fs.shortpath to avoid import loop.
|
|
98
|
+
* New func_a_kw(func,*a,**kw) returning the formatted form of func_a_kw_fmt(func,*a,**kw).
|
|
99
|
+
|
|
100
|
+
*Release 20221118*:
|
|
101
|
+
@trace: add indenting.
|
|
102
|
+
|
|
103
|
+
*Release 20220619*:
|
|
104
|
+
@trace: rename some parameters, add with_caller.
|
|
105
|
+
|
|
106
|
+
*Release 20220311.1*:
|
|
107
|
+
New callif(doit[,modes],func,*a,**kw) to call or print a function, used in -n/--dry-run stuff sometimes.
|
|
108
|
+
|
|
109
|
+
*Release 20220311*:
|
|
110
|
+
* @trace: new pprint=False option to use pprint.pformat instead of repr for the return value.
|
|
111
|
+
* @trace: bugfix use of retval parameter.
|
|
112
|
+
|
|
113
|
+
*Release 20210913*:
|
|
114
|
+
New func_a_kw_fmt(func,*a,**kw) imported from cs.pfx, hooked it into @trace.
|
|
115
|
+
|
|
116
|
+
*Release 20210906*:
|
|
117
|
+
funcname: special case functools.partial, return concise name.
|
|
118
|
+
|
|
119
|
+
*Release 20210717*:
|
|
120
|
+
* Move @trace from debug to py.func, defer log call imports to avoid loops.
|
|
121
|
+
* Drop cs.pfx requirement, import opportunisticly with fallback.
|
|
122
|
+
|
|
123
|
+
*Release 20200518*:
|
|
124
|
+
funcname: prefer `func.__qualname__` over `func.__name__`
|
|
125
|
+
|
|
126
|
+
*Release 20190729*:
|
|
127
|
+
funccite: handle callables with no __code__ attribute.
|
|
128
|
+
|
|
129
|
+
*Release 20190108*:
|
|
130
|
+
Break import loop. Use cs.py3.raise_from for portability.
|
|
131
|
+
|
|
132
|
+
*Release 20181231*:
|
|
133
|
+
* Some type specific convenience wrappers for yields_type and returns_type.
|
|
134
|
+
* Bugfix for @prop.
|
|
135
|
+
|
|
136
|
+
*Release 20170906.1*:
|
|
137
|
+
Minor tweaks.
|
|
138
|
+
|
|
139
|
+
*Release 20170906*:
|
|
140
|
+
Bugfix for @prop.
|
|
141
|
+
|
|
142
|
+
*Release 20170608*:
|
|
143
|
+
New decorator @prop which works just like the builtin @property, but transmutes internal AttributeError into RuntimeError, unmasking many bugs.
|
|
144
|
+
|
|
145
|
+
*Release 20160828*:
|
|
146
|
+
Use "install_requires" instead of "requires" in DISTINFO.
|
|
147
|
+
|
|
148
|
+
*Release 20150115*:
|
|
149
|
+
First PyPI release.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cs-py-func"
|
|
3
|
+
description = "Convenience facilities related to Python functions."
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Cameron Simpson", email = "cs@cskk.id.au" },
|
|
6
|
+
]
|
|
7
|
+
keywords = [
|
|
8
|
+
"python2",
|
|
9
|
+
"python3",
|
|
10
|
+
]
|
|
11
|
+
dependencies = []
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python",
|
|
14
|
+
"Programming Language :: Python :: 2",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
21
|
+
]
|
|
22
|
+
version = "20250724"
|
|
23
|
+
requires-python = ">=3"
|
|
24
|
+
|
|
25
|
+
[project.license]
|
|
26
|
+
text = "GNU General Public License v3 or later (GPLv3+)"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
"Monorepo Hg/Mercurial Mirror" = "https://hg.sr.ht/~cameron-simpson/css"
|
|
30
|
+
"Monorepo Git Mirror" = "https://github.com/cameron-simpson/css"
|
|
31
|
+
"MonoRepo Commits" = "https://bitbucket.org/cameron_simpson/css/commits/branch/main"
|
|
32
|
+
Source = "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/py/func.py"
|
|
33
|
+
|
|
34
|
+
[project.readme]
|
|
35
|
+
text = """
|
|
36
|
+
Convenience facilities related to Python functions.
|
|
37
|
+
|
|
38
|
+
*Latest release 20250724*:
|
|
39
|
+
* func_a_kw_fmt: shorten long %r, widen another field.
|
|
40
|
+
* Drop Python 2 support.
|
|
41
|
+
|
|
42
|
+
Short summary:
|
|
43
|
+
* `callif`: Call `func(*a,**kw)` if `doit` is true otherwise just print it out.
|
|
44
|
+
* `callmethod_if`: Call the named `method` on the object `obj` if it exists.
|
|
45
|
+
* `derived_from`: A property which must be recomputed if the revision of another property exceeds the snapshot revision.
|
|
46
|
+
* `derived_property`: A property which must be recomputed if the reference revision (attached to self) exceeds the snapshot revision.
|
|
47
|
+
* `func_a_kw`: Return a string representing a call to `func(*a,**kw)`.
|
|
48
|
+
* `func_a_kw_fmt`: Prepare a percent-format string and associated argument list describing a call to `func(*a,**kw)`. Return `format,args`.
|
|
49
|
+
* `funccite`: Return a citation for a function (name and code location).
|
|
50
|
+
* `funcname`: Return a name for the supplied function `func`. Several objects do not have a __name__ attribute, such as partials.
|
|
51
|
+
* `prop`: A substitute for the builtin @property.
|
|
52
|
+
|
|
53
|
+
Module contents:
|
|
54
|
+
- <a name=\"callif\"></a>`callif(doit, func, *a, **kw)`: Call `func(*a,**kw)` if `doit` is true
|
|
55
|
+
otherwise just print it out.
|
|
56
|
+
|
|
57
|
+
The parameter `func` may be preceeded optionally by a `dict`
|
|
58
|
+
containing modes. The current modes are:
|
|
59
|
+
* `'print'`: the print function, default the builtin `print`
|
|
60
|
+
- <a name=\"callmethod_if\"></a>`callmethod_if(obj, method, default=None, a=None, kw=None)`: Call the named `method` on the object `obj` if it exists.
|
|
61
|
+
|
|
62
|
+
If it does not exist, return `default` (which defaults to None).
|
|
63
|
+
Otherwise call getattr(obj, method)(*a, **kw).
|
|
64
|
+
`a` defaults to ().
|
|
65
|
+
`kw` defaults to {}.
|
|
66
|
+
- <a name=\"derived_from\"></a>`derived_from(property_name)`: A property which must be recomputed
|
|
67
|
+
if the revision of another property exceeds the snapshot revision.
|
|
68
|
+
- <a name=\"derived_property\"></a>`derived_property(func, original_revision_name='_revision', lock_name='_lock', property_name=None, unset_object=None)`: A property which must be recomputed
|
|
69
|
+
if the reference revision (attached to self)
|
|
70
|
+
exceeds the snapshot revision.
|
|
71
|
+
- <a name=\"func_a_kw\"></a>`func_a_kw(func, *a, **kw)`: Return a string representing a call to `func(*a,**kw)`.
|
|
72
|
+
- <a name=\"func_a_kw_fmt\"></a>`func_a_kw_fmt(func, *a, **kw)`: Prepare a percent-format string and associated argument list
|
|
73
|
+
describing a call to `func(*a,**kw)`.
|
|
74
|
+
Return `format,args`.
|
|
75
|
+
|
|
76
|
+
The `func` argument can also be a string,
|
|
77
|
+
typically a prepared description of `func` such as `funccite(func)`.
|
|
78
|
+
|
|
79
|
+
*Note*: the returned `args` is a `list` for easy incorporation
|
|
80
|
+
into further arguments. The `%` operator requires a `tuple`.
|
|
81
|
+
- <a name=\"funccite\"></a>`funccite(func)`: Return a citation for a function (name and code location).
|
|
82
|
+
- <a name=\"funcname\"></a>`funcname(func)`: Return a name for the supplied function `func`.
|
|
83
|
+
Several objects do not have a __name__ attribute, such as partials.
|
|
84
|
+
- <a name=\"prop\"></a>`prop(func)`: A substitute for the builtin @property.
|
|
85
|
+
|
|
86
|
+
The builtin @property decorator lets internal AttributeErrors escape.
|
|
87
|
+
While that can support properties that appear to exist conditionally,
|
|
88
|
+
in practice this is almost never what I want, and it masks deeper errors.
|
|
89
|
+
Hence this wrapper for @property that transmutes internal AttributeErrors
|
|
90
|
+
into RuntimeErrors.
|
|
91
|
+
|
|
92
|
+
# Release Log
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
*Release 20250724*:
|
|
97
|
+
* func_a_kw_fmt: shorten long %r, widen another field.
|
|
98
|
+
* Drop Python 2 support.
|
|
99
|
+
|
|
100
|
+
*Release 20240630*:
|
|
101
|
+
funcname: include the module name, handle functions with no __module__.
|
|
102
|
+
|
|
103
|
+
*Release 20230331*:
|
|
104
|
+
Drop @trace, moved to cs.debug.
|
|
105
|
+
|
|
106
|
+
*Release 20230210*:
|
|
107
|
+
Drop the returns_* and yields_* decorators, these days type annotations and @typechecked are the go.
|
|
108
|
+
|
|
109
|
+
*Release 20221207*:
|
|
110
|
+
* @trace: trace exceptions by default, previously false.
|
|
111
|
+
* Late import of cs.fs.shortpath to avoid import loop.
|
|
112
|
+
* New func_a_kw(func,*a,**kw) returning the formatted form of func_a_kw_fmt(func,*a,**kw).
|
|
113
|
+
|
|
114
|
+
*Release 20221118*:
|
|
115
|
+
@trace: add indenting.
|
|
116
|
+
|
|
117
|
+
*Release 20220619*:
|
|
118
|
+
@trace: rename some parameters, add with_caller.
|
|
119
|
+
|
|
120
|
+
*Release 20220311.1*:
|
|
121
|
+
New callif(doit[,modes],func,*a,**kw) to call or print a function, used in -n/--dry-run stuff sometimes.
|
|
122
|
+
|
|
123
|
+
*Release 20220311*:
|
|
124
|
+
* @trace: new pprint=False option to use pprint.pformat instead of repr for the return value.
|
|
125
|
+
* @trace: bugfix use of retval parameter.
|
|
126
|
+
|
|
127
|
+
*Release 20210913*:
|
|
128
|
+
New func_a_kw_fmt(func,*a,**kw) imported from cs.pfx, hooked it into @trace.
|
|
129
|
+
|
|
130
|
+
*Release 20210906*:
|
|
131
|
+
funcname: special case functools.partial, return concise name.
|
|
132
|
+
|
|
133
|
+
*Release 20210717*:
|
|
134
|
+
* Move @trace from debug to py.func, defer log call imports to avoid loops.
|
|
135
|
+
* Drop cs.pfx requirement, import opportunisticly with fallback.
|
|
136
|
+
|
|
137
|
+
*Release 20200518*:
|
|
138
|
+
funcname: prefer `func.__qualname__` over `func.__name__`
|
|
139
|
+
|
|
140
|
+
*Release 20190729*:
|
|
141
|
+
funccite: handle callables with no __code__ attribute.
|
|
142
|
+
|
|
143
|
+
*Release 20190108*:
|
|
144
|
+
Break import loop. Use cs.py3.raise_from for portability.
|
|
145
|
+
|
|
146
|
+
*Release 20181231*:
|
|
147
|
+
* Some type specific convenience wrappers for yields_type and returns_type.
|
|
148
|
+
* Bugfix for @prop.
|
|
149
|
+
|
|
150
|
+
*Release 20170906.1*:
|
|
151
|
+
Minor tweaks.
|
|
152
|
+
|
|
153
|
+
*Release 20170906*:
|
|
154
|
+
Bugfix for @prop.
|
|
155
|
+
|
|
156
|
+
*Release 20170608*:
|
|
157
|
+
New decorator @prop which works just like the builtin @property, but transmutes internal AttributeError into RuntimeError, unmasking many bugs.
|
|
158
|
+
|
|
159
|
+
*Release 20160828*:
|
|
160
|
+
Use \"install_requires\" instead of \"requires\" in DISTINFO.
|
|
161
|
+
|
|
162
|
+
*Release 20150115*:
|
|
163
|
+
First PyPI release."""
|
|
164
|
+
content-type = "text/markdown"
|
|
165
|
+
|
|
166
|
+
[build-system]
|
|
167
|
+
build-backend = "flit_core.buildapi"
|
|
168
|
+
requires = [
|
|
169
|
+
"flit_core >=3.2,<4",
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
[tool.flit.module]
|
|
173
|
+
name = "cs.py.func"
|
|
@@ -0,0 +1,192 @@
|
|
|
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
|
+
|
|
13
|
+
__version__ = '20250724'
|
|
14
|
+
|
|
15
|
+
DISTINFO = {
|
|
16
|
+
'keywords': ["python2", "python3"],
|
|
17
|
+
'classifiers': [
|
|
18
|
+
"Programming Language :: Python",
|
|
19
|
+
"Programming Language :: Python :: 2",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
],
|
|
22
|
+
'python_requires':
|
|
23
|
+
'>=3',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def funcname(func):
|
|
27
|
+
''' Return a name for the supplied function `func`.
|
|
28
|
+
Several objects do not have a __name__ attribute, such as partials.
|
|
29
|
+
'''
|
|
30
|
+
try:
|
|
31
|
+
name = func.__qualname__
|
|
32
|
+
except AttributeError:
|
|
33
|
+
try:
|
|
34
|
+
name = func.__name__
|
|
35
|
+
except AttributeError:
|
|
36
|
+
if isinstance(func, partial):
|
|
37
|
+
name = "partial(%s)" % (funcname(func.func),)
|
|
38
|
+
else:
|
|
39
|
+
name = str(func)
|
|
40
|
+
return "%s:%s" % (getattr(func, '__module__', '?'), name)
|
|
41
|
+
|
|
42
|
+
def funccite(func):
|
|
43
|
+
''' Return a citation for a function (name and code location).
|
|
44
|
+
'''
|
|
45
|
+
try:
|
|
46
|
+
code = func.__code__
|
|
47
|
+
except AttributeError:
|
|
48
|
+
return "%s[no.__code__]" % (repr(func),)
|
|
49
|
+
try:
|
|
50
|
+
from cs.fs import shortpath # pylint: disable=import-outside-toplevel
|
|
51
|
+
except ImportError:
|
|
52
|
+
shortpath = lambda p: p # pylint: disable=unnecessary-lambda-assignment
|
|
53
|
+
return "%s[%s:%d]" % (
|
|
54
|
+
funcname(func),
|
|
55
|
+
shortpath(code.co_filename),
|
|
56
|
+
code.co_firstlineno,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def func_a_kw_fmt(func, *a, **kw):
|
|
60
|
+
''' Prepare a percent-format string and associated argument list
|
|
61
|
+
describing a call to `func(*a,**kw)`.
|
|
62
|
+
Return `format,args`.
|
|
63
|
+
|
|
64
|
+
The `func` argument can also be a string,
|
|
65
|
+
typically a prepared description of `func` such as `funccite(func)`.
|
|
66
|
+
|
|
67
|
+
*Note*: the returned `args` is a `list` for easy incorporation
|
|
68
|
+
into further arguments. The `%` operator requires a `tuple`.
|
|
69
|
+
'''
|
|
70
|
+
av = [
|
|
71
|
+
func if isinstance(func, str) else getattr(func, '__name__', str(func))
|
|
72
|
+
]
|
|
73
|
+
afv = ['%.40r'] * len(a)
|
|
74
|
+
av.extend(a)
|
|
75
|
+
afv.extend(['%s=%.40r'] * len(kw))
|
|
76
|
+
for kv in kw.items():
|
|
77
|
+
av.extend(kv)
|
|
78
|
+
return '%s(' + ','.join(afv) + ')', av
|
|
79
|
+
|
|
80
|
+
def func_a_kw(func, *a, **kw):
|
|
81
|
+
''' Return a string representing a call to `func(*a,**kw)`.
|
|
82
|
+
'''
|
|
83
|
+
fmt, args = func_a_kw_fmt(func, *a, **kw)
|
|
84
|
+
return fmt % tuple(args)
|
|
85
|
+
|
|
86
|
+
def callif(doit, func, *a, **kw):
|
|
87
|
+
''' Call `func(*a,**kw)` if `doit` is true
|
|
88
|
+
otherwise just print it out.
|
|
89
|
+
|
|
90
|
+
The parameter `func` may be preceeded optionally by a `dict`
|
|
91
|
+
containing modes. The current modes are:
|
|
92
|
+
* `'print'`: the print function, default the builtin `print`
|
|
93
|
+
'''
|
|
94
|
+
if isinstance(func, dict):
|
|
95
|
+
modes = func
|
|
96
|
+
a = list(a)
|
|
97
|
+
func = a.pop(0)
|
|
98
|
+
else:
|
|
99
|
+
modes = {}
|
|
100
|
+
if doit:
|
|
101
|
+
return func(*a, **kw)
|
|
102
|
+
# just recite the function
|
|
103
|
+
modes.get('print', print)(func_a_kw(func, *a, **kw))
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def callmethod_if(obj, method, default=None, a=None, kw=None):
|
|
107
|
+
''' Call the named `method` on the object `obj` if it exists.
|
|
108
|
+
|
|
109
|
+
If it does not exist, return `default` (which defaults to None).
|
|
110
|
+
Otherwise call getattr(obj, method)(*a, **kw).
|
|
111
|
+
`a` defaults to ().
|
|
112
|
+
`kw` defaults to {}.
|
|
113
|
+
'''
|
|
114
|
+
try:
|
|
115
|
+
m = getattr(obj, method)
|
|
116
|
+
except AttributeError:
|
|
117
|
+
return default
|
|
118
|
+
if a is None:
|
|
119
|
+
a = ()
|
|
120
|
+
if kw is None:
|
|
121
|
+
kw = {}
|
|
122
|
+
return m(*a, **kw)
|
|
123
|
+
|
|
124
|
+
def prop(func):
|
|
125
|
+
''' A substitute for the builtin @property.
|
|
126
|
+
|
|
127
|
+
The builtin @property decorator lets internal AttributeErrors escape.
|
|
128
|
+
While that can support properties that appear to exist conditionally,
|
|
129
|
+
in practice this is almost never what I want, and it masks deeper errors.
|
|
130
|
+
Hence this wrapper for @property that transmutes internal AttributeErrors
|
|
131
|
+
into RuntimeErrors.
|
|
132
|
+
'''
|
|
133
|
+
|
|
134
|
+
# pylint: disable=inconsistent-return-statements
|
|
135
|
+
def prop_wrapper(*a, **kw):
|
|
136
|
+
try:
|
|
137
|
+
return func(*a, **kw)
|
|
138
|
+
except AttributeError as e:
|
|
139
|
+
raise RuntimeError("inner function %s raised %s" % (func, e)) from e
|
|
140
|
+
|
|
141
|
+
prop_wrapper.__name__ = "@prop(%s)" % (funcname(func),)
|
|
142
|
+
return property(prop_wrapper)
|
|
143
|
+
|
|
144
|
+
def derived_property(
|
|
145
|
+
func,
|
|
146
|
+
original_revision_name='_revision',
|
|
147
|
+
lock_name='_lock',
|
|
148
|
+
property_name=None,
|
|
149
|
+
unset_object=None,
|
|
150
|
+
):
|
|
151
|
+
''' A property which must be recomputed
|
|
152
|
+
if the reference revision (attached to self)
|
|
153
|
+
exceeds the snapshot revision.
|
|
154
|
+
'''
|
|
155
|
+
if property_name is None:
|
|
156
|
+
property_name = '_' + func.__name__
|
|
157
|
+
# the property used to track the reference revision
|
|
158
|
+
property_revision_name = property_name + '__revision'
|
|
159
|
+
|
|
160
|
+
def property_value(self):
|
|
161
|
+
''' Attempt lockless fetch of property first.
|
|
162
|
+
Use lock if property is unset and up to date.
|
|
163
|
+
'''
|
|
164
|
+
# poll outside lock
|
|
165
|
+
try:
|
|
166
|
+
p = getattr(self, property_name, unset_object)
|
|
167
|
+
p_revision = getattr(self, property_revision_name, 0)
|
|
168
|
+
o_revision = getattr(self, original_revision_name)
|
|
169
|
+
if p is unset_object or p_revision < o_revision:
|
|
170
|
+
with getattr(self, lock_name):
|
|
171
|
+
# repoll value inside lock
|
|
172
|
+
p = getattr(self, property_name, unset_object)
|
|
173
|
+
p_revision = getattr(self, property_revision_name, 0)
|
|
174
|
+
o_revision = getattr(self, original_revision_name)
|
|
175
|
+
if p is unset_object or p_revision < o_revision:
|
|
176
|
+
p = func(self)
|
|
177
|
+
setattr(self, property_name, p)
|
|
178
|
+
setattr(self, property_revision_name, o_revision)
|
|
179
|
+
except AttributeError as e:
|
|
180
|
+
raise RuntimeError("AttributeError: %s" % (e,)) from e
|
|
181
|
+
return p
|
|
182
|
+
|
|
183
|
+
return property(property_value)
|
|
184
|
+
|
|
185
|
+
def derived_from(property_name):
|
|
186
|
+
''' A property which must be recomputed
|
|
187
|
+
if the revision of another property exceeds the snapshot revision.
|
|
188
|
+
'''
|
|
189
|
+
return partial(
|
|
190
|
+
derived_property,
|
|
191
|
+
original_revision_name='_' + property_name + '__revision'
|
|
192
|
+
)
|