cs-py-func 20250724__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,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
+ )
@@ -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,4 @@
1
+ cs/py/func.py,sha256=umu-GyFCIadnJPHBaBvptTlTmJVdePwZ_J1jhjavFrU,5765
2
+ cs_py_func-20250724.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
3
+ cs_py_func-20250724.dist-info/METADATA,sha256=sM6sVEqnKPBi6t2zcOc1jFM8QgRQyxSkhXynDber488,6404
4
+ cs_py_func-20250724.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.12.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any