cdxcore 0.1.5__py3-none-any.whl → 0.1.9__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.
Potentially problematic release.
This version of cdxcore might be problematic. Click here for more details.
- cdxcore/__init__.py +1 -9
- cdxcore/config.py +1188 -521
- cdxcore/crman.py +95 -25
- cdxcore/err.py +371 -0
- cdxcore/pretty.py +468 -0
- cdxcore/pretty.py_bak.py +750 -0
- cdxcore/subdir.py +2225 -1334
- cdxcore/uniquehash.py +515 -363
- cdxcore/util.py +358 -417
- cdxcore/verbose.py +683 -248
- cdxcore/version.py +398 -139
- cdxcore-0.1.9.dist-info/METADATA +27 -0
- cdxcore-0.1.9.dist-info/RECORD +36 -0
- {cdxcore-0.1.5.dist-info → cdxcore-0.1.9.dist-info}/top_level.txt +3 -1
- docs/source/conf.py +123 -0
- docs2/source/conf.py +35 -0
- tests/test_config.py +502 -0
- tests/test_crman.py +54 -0
- tests/test_err.py +86 -0
- tests/test_pretty.py +404 -0
- tests/test_subdir.py +289 -0
- tests/test_uniquehash.py +159 -144
- tests/test_util.py +122 -83
- tests/test_verbose.py +119 -0
- tests/test_version.py +153 -0
- up/git_message.py +2 -2
- cdxcore/logger.py +0 -319
- cdxcore/prettydict.py +0 -388
- cdxcore/prettyobject.py +0 -64
- cdxcore-0.1.5.dist-info/METADATA +0 -1418
- cdxcore-0.1.5.dist-info/RECORD +0 -30
- conda/conda_exists.py +0 -10
- conda/conda_modify_yaml.py +0 -42
- tests/_cdxbasics.py +0 -1086
- {cdxcore-0.1.5.dist-info → cdxcore-0.1.9.dist-info}/WHEEL +0 -0
- {cdxcore-0.1.5.dist-info → cdxcore-0.1.9.dist-info}/licenses/LICENSE +0 -0
- {cdxcore → tmp}/deferred.py +0 -0
- {cdxcore → tmp}/dynaplot.py +0 -0
- {cdxcore → tmp}/filelock.py +0 -0
- {cdxcore → tmp}/jcpool.py +0 -0
- {cdxcore → tmp}/np.py +0 -0
- {cdxcore → tmp}/npio.py +0 -0
- {cdxcore → tmp}/sharedarray.py +0 -0
cdxcore/util.py
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Overview
|
|
3
|
+
--------
|
|
4
|
+
|
|
5
|
+
Basic utilities for Python such as type management, formatting, some trivial timers.
|
|
6
|
+
|
|
7
|
+
Import
|
|
8
|
+
------
|
|
9
|
+
.. code-block:: python
|
|
10
|
+
|
|
11
|
+
import cdxcore.util as util
|
|
12
|
+
|
|
13
|
+
Documentation
|
|
14
|
+
-------------
|
|
3
15
|
"""
|
|
4
16
|
|
|
5
17
|
import datetime as datetime
|
|
@@ -12,9 +24,7 @@ from collections import OrderedDict
|
|
|
12
24
|
from sortedcontainers import SortedDict
|
|
13
25
|
import numpy as np
|
|
14
26
|
import pandas as pd
|
|
15
|
-
import
|
|
16
|
-
import warnings as warnings
|
|
17
|
-
from collections.abc import Callable, Collection
|
|
27
|
+
from .err import fmt, _fmt, verify, error, warn_if, warn #NOQA
|
|
18
28
|
|
|
19
29
|
# =============================================================================
|
|
20
30
|
# basic indentification short cuts
|
|
@@ -22,187 +32,83 @@ from collections.abc import Callable, Collection
|
|
|
22
32
|
|
|
23
33
|
__types_functions = None
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
#: a set of all ``types`` considered functions
|
|
36
|
+
def types_functions() -> tuple[type]:
|
|
37
|
+
""" Returns a set of all ``types`` considered functions """
|
|
27
38
|
global __types_functions
|
|
28
39
|
if __types_functions is None:
|
|
29
|
-
|
|
30
|
-
try:
|
|
40
|
+
fs = set()
|
|
41
|
+
try: fs.add(types.FunctionType)
|
|
31
42
|
except: pass
|
|
32
|
-
try:
|
|
43
|
+
try: fs.add(types.LambdaType)
|
|
33
44
|
except: pass
|
|
34
|
-
try:
|
|
45
|
+
try: fs.add(types.CodeType)
|
|
35
46
|
except: pass
|
|
36
47
|
#types.MappingProxyType
|
|
37
48
|
#types.SimpleNamespace
|
|
38
|
-
try:
|
|
49
|
+
try: fs.add(types.GeneratorType)
|
|
39
50
|
except: pass
|
|
40
|
-
try:
|
|
51
|
+
try: fs.add(types.CoroutineType)
|
|
41
52
|
except: pass
|
|
42
|
-
try:
|
|
53
|
+
try: fs.add(types.AsyncGeneratorType)
|
|
43
54
|
except: pass
|
|
44
|
-
try:
|
|
55
|
+
try: fs.add(types.MethodType)
|
|
45
56
|
except: pass
|
|
46
|
-
try:
|
|
57
|
+
try: fs.add(types.BuiltinFunctionType)
|
|
47
58
|
except: pass
|
|
48
|
-
try:
|
|
59
|
+
try: fs.add(types.BuiltinMethodType)
|
|
49
60
|
except: pass
|
|
50
|
-
try:
|
|
61
|
+
try: fs.add(types.WrapperDescriptorType)
|
|
51
62
|
except: pass
|
|
52
|
-
try:
|
|
63
|
+
try: fs.add(types.MethodWrapperType)
|
|
53
64
|
except: pass
|
|
54
|
-
try:
|
|
65
|
+
try: fs.add(types.MethodDescriptorType)
|
|
55
66
|
except: pass
|
|
56
|
-
try:
|
|
67
|
+
try: fs.add(types.ClassMethodDescriptorType)
|
|
57
68
|
except: pass
|
|
58
69
|
#types.ModuleType,
|
|
59
70
|
#types.TracebackType,
|
|
60
71
|
#types.FrameType,
|
|
61
|
-
try:
|
|
72
|
+
try: fs.add(types.GetSetDescriptorType)
|
|
62
73
|
except: pass
|
|
63
|
-
try:
|
|
74
|
+
try: fs.add(types.MemberDescriptorType)
|
|
64
75
|
except: pass
|
|
65
|
-
try:
|
|
76
|
+
try: fs.add(types.DynamicClassAttribute)
|
|
66
77
|
except: pass
|
|
67
|
-
__types_functions = tuple(
|
|
78
|
+
__types_functions = tuple(fs)
|
|
68
79
|
return __types_functions
|
|
69
80
|
|
|
70
|
-
def
|
|
81
|
+
def is_function(f) -> bool:
|
|
71
82
|
"""
|
|
72
|
-
Checks whether
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
Checks whether ``f`` is a function in an extended sense.
|
|
84
|
+
|
|
85
|
+
Check :func:`cdxcore.util.types_functions` for what is tested against.
|
|
86
|
+
In particular ``is_function`` does not test positive for properties.
|
|
75
87
|
"""
|
|
76
88
|
return isinstance(f,types_functions())
|
|
77
89
|
|
|
78
|
-
def
|
|
79
|
-
"""
|
|
90
|
+
def is_atomic( o ):
|
|
91
|
+
"""
|
|
92
|
+
Whether an element is atomic.
|
|
93
|
+
|
|
94
|
+
Returns ``True`` if ``o`` is a
|
|
95
|
+
``string``, ``int``, ``float``, :class:`datedatime.date`, ``bool``,
|
|
96
|
+
or a :class:`numpy.generic`
|
|
97
|
+
"""
|
|
80
98
|
if type(o) in [str,int,bool,float,datetime.date]:
|
|
81
99
|
return True
|
|
82
100
|
if isinstance(o,np.generic):
|
|
83
101
|
return True
|
|
84
102
|
return False
|
|
85
103
|
|
|
86
|
-
def
|
|
87
|
-
""" Checks whether a type is a float """
|
|
104
|
+
def is_float( o ):
|
|
105
|
+
""" Checks whether a type is a ``float`` which includes numpy floating types """
|
|
88
106
|
if type(o) is float:
|
|
89
107
|
return True
|
|
90
108
|
if isinstance(o,np.floating):
|
|
91
109
|
return True
|
|
92
110
|
return False
|
|
93
111
|
|
|
94
|
-
# =============================================================================
|
|
95
|
-
# exceptions
|
|
96
|
-
# =============================================================================
|
|
97
|
-
|
|
98
|
-
def _verify( cond : bool, msgf : Callable, exception : Exception = Exception, **msg_kwargs ):
|
|
99
|
-
"""
|
|
100
|
-
Verifies 'cond' and raises an exception of type 'exception' with message 'msgf()' if cond is not True.
|
|
101
|
-
The message itself is generated by calling msgf() if it is a callable, or by str.fomrat(msgf,**msg_kwargs) if it is a string.
|
|
102
|
-
That means that the message is only formatted if the conditon was not met and an exception is to be thrown:
|
|
103
|
-
|
|
104
|
-
Functional use case:
|
|
105
|
-
x=1
|
|
106
|
-
verify(x==1, lambda : f"Error: x is {x}") # <- the use of 'lambda' delays generation of the error message
|
|
107
|
-
|
|
108
|
-
If 'msgf' is a string, then str.format(msgf, **msg_kwargs) is called:
|
|
109
|
-
x=1
|
|
110
|
-
verify(x==1, "Error: x is {x}", x=x ) # <- do *not* use f-string !
|
|
111
|
-
|
|
112
|
-
Basically
|
|
113
|
-
verify(cond, msgf, exception, **msg_kwargs)
|
|
114
|
-
is functionally equivalent to
|
|
115
|
-
if not cond: raise exception(msgf())
|
|
116
|
-
or, if 'msgf' is a string:
|
|
117
|
-
if not cond: raise exception(str.format(msgf,**msg_kwargs))
|
|
118
|
-
|
|
119
|
-
Parameters
|
|
120
|
-
----------
|
|
121
|
-
cond:
|
|
122
|
-
condition to be tested. An exception is thrown if it is not true
|
|
123
|
-
msgf:
|
|
124
|
-
function to call to generate the error message if 'cond' is False,
|
|
125
|
-
or str.format formatting string using {} [this is *not* an f-string].
|
|
126
|
-
In both case the message is only generated if the conditon 'cond' is False.
|
|
127
|
-
|
|
128
|
-
Two main use cases:
|
|
129
|
-
For complicated formatting, use a lambda function which returns an f-string.
|
|
130
|
-
For simple formatting, use a non-fstring.
|
|
131
|
-
exception:
|
|
132
|
-
|
|
133
|
-
Anything the function msfg() returns is passed to the constructor
|
|
134
|
-
of the 'exception', except if msgf() returns itself an Exception
|
|
135
|
-
object. In that case that will be raised.
|
|
136
|
-
The function msgf cannot return None.
|
|
137
|
-
exception:
|
|
138
|
-
Exception type to raise.
|
|
139
|
-
msg_kwargs :
|
|
140
|
-
Keywords for msgf if msgf is a string; must be empty otherwise.
|
|
141
|
-
"""
|
|
142
|
-
if not bool(cond):
|
|
143
|
-
if not isinstance( msgf, str ):
|
|
144
|
-
assert len(msg_kwargs) == 0, ("Superflous arguments passed", str(msg_kwargs)[:100] )
|
|
145
|
-
msg = msgf()
|
|
146
|
-
assert not msg is None, ("'msgf' returned None")
|
|
147
|
-
if isinstance(msg, Exception):
|
|
148
|
-
raise msg
|
|
149
|
-
else:
|
|
150
|
-
msg = str.format(msgf,**msg_kwargs)
|
|
151
|
-
raise exception( msg )
|
|
152
|
-
|
|
153
|
-
_warn_skips = (os.path.dirname(__file__),)
|
|
154
|
-
|
|
155
|
-
def _warn( message : str, category : Warning = RuntimeWarning, stack_level : int = 1 ):
|
|
156
|
-
""" Standard warning """
|
|
157
|
-
warnings.warn( message=str(message), category=category, stacklevel=stack_level, skip_file_prefixes=_warn_skips )
|
|
158
|
-
|
|
159
|
-
def _warn_if( cond : bool, msgf : Callable, *, category : Warning = RuntimeWarning, stack_level : int = 1, **msg_kwargs ):
|
|
160
|
-
"""
|
|
161
|
-
Tests 'cond' and issues a warning with message 'msgf()'.
|
|
162
|
-
The message itself is generated by calling msgf() if it is a callable, or by str.fomrat(msgf,**msg_kwargs) if it is a string.
|
|
163
|
-
That means that the message is only formatted if the conditon was met and the warning is printed.
|
|
164
|
-
|
|
165
|
-
Functional use case:
|
|
166
|
-
x=1
|
|
167
|
-
warn_if(x!=0, lambda : f"Warn: x is {x}") # <- the use of 'lambda' delays generation of the warning message
|
|
168
|
-
|
|
169
|
-
If 'msgf' is a string, then str.format(msgf, **msg_kwargs) is called:
|
|
170
|
-
x=1
|
|
171
|
-
verify(x!=1, "Warn: x is {x}", x=x ) # <- do *not* use f-string !
|
|
172
|
-
|
|
173
|
-
Basically
|
|
174
|
-
warn_if(cond, msgf, exception, **msg_kwargs)
|
|
175
|
-
is functionally equivalent to
|
|
176
|
-
if cond: warn(msgf())
|
|
177
|
-
or, if 'msgf' is a string:
|
|
178
|
-
if cond: warn(str.format(msgf,**msg_kwargs))
|
|
179
|
-
|
|
180
|
-
Parameters
|
|
181
|
-
----------
|
|
182
|
-
cond:
|
|
183
|
-
condition to be tested. An exception is thrown if it is not true
|
|
184
|
-
msgf:
|
|
185
|
-
function to call to generate the error message if 'cond' is False,
|
|
186
|
-
or str.format formatting string using {} [this is *not* an f-string].
|
|
187
|
-
In both case the message is only generated if the conditon 'cond' is False.
|
|
188
|
-
|
|
189
|
-
Two main use cases:
|
|
190
|
-
For complicated formatting, use a lambda function which returns an f-string.
|
|
191
|
-
For simple formatting, use a non-fstring.
|
|
192
|
-
exception:
|
|
193
|
-
exception:
|
|
194
|
-
Exception type to raise.
|
|
195
|
-
msg_kwargs :
|
|
196
|
-
Keywords for msgf if msgf is a string; must be empty otherwise.
|
|
197
|
-
"""
|
|
198
|
-
if bool(cond):
|
|
199
|
-
if not isinstance( msgf, str ):
|
|
200
|
-
assert len(msg_kwargs) == 0, ("Superflous arguments passed?", str(msg_kwargs)[:100] )
|
|
201
|
-
msg = msgf()
|
|
202
|
-
else:
|
|
203
|
-
msg = str.format(msgf,**msg_kwargs)
|
|
204
|
-
_warn( msg, category=category, stack_level=stack_level )
|
|
205
|
-
|
|
206
112
|
# =============================================================================
|
|
207
113
|
# python basics
|
|
208
114
|
# =============================================================================
|
|
@@ -210,6 +116,7 @@ def _warn_if( cond : bool, msgf : Callable, *, category : Warning = RuntimeWarni
|
|
|
210
116
|
def _get_recursive_size(obj, seen=None):
|
|
211
117
|
"""
|
|
212
118
|
Recursive helper for sizeof
|
|
119
|
+
:meta private:
|
|
213
120
|
"""
|
|
214
121
|
if seen is None:
|
|
215
122
|
seen = set() # Keep track of seen objects to avoid double-counting
|
|
@@ -222,7 +129,7 @@ def _get_recursive_size(obj, seen=None):
|
|
|
222
129
|
return 0
|
|
223
130
|
seen.add(id(obj))
|
|
224
131
|
|
|
225
|
-
if isinstance( obj, np.ndarray ):
|
|
132
|
+
if isinstance( obj, (np.ndarray, pd.DataFrame) ):
|
|
226
133
|
size += obj.nbytes
|
|
227
134
|
elif isinstance(obj, Mapping):
|
|
228
135
|
for key, value in obj.items():
|
|
@@ -244,8 +151,11 @@ def _get_recursive_size(obj, seen=None):
|
|
|
244
151
|
|
|
245
152
|
def getsizeof(obj):
|
|
246
153
|
"""
|
|
247
|
-
Approximates the size of
|
|
248
|
-
|
|
154
|
+
Approximates the size of an object.
|
|
155
|
+
|
|
156
|
+
In addition to calling :func:`sys.getsizeof` this function
|
|
157
|
+
also iterates embedded containers, numpy arrays, and panda dataframes.
|
|
158
|
+
:meta private:
|
|
249
159
|
"""
|
|
250
160
|
return _get_recursive_size(obj,None)
|
|
251
161
|
|
|
@@ -253,38 +163,22 @@ def getsizeof(obj):
|
|
|
253
163
|
# string formatting
|
|
254
164
|
# =============================================================================
|
|
255
165
|
|
|
256
|
-
def
|
|
257
|
-
""" Utility function. See fmt() """
|
|
258
|
-
if text.find('%') == -1:
|
|
259
|
-
return text
|
|
260
|
-
if not args is None and len(args) > 0:
|
|
261
|
-
assert kwargs is None or len(kwargs) == 0, "Cannot specify both 'args' and 'kwargs'"
|
|
262
|
-
return text % tuple(args)
|
|
263
|
-
if not kwargs is None and len(kwargs) > 0:
|
|
264
|
-
return text % kwargs
|
|
265
|
-
return text
|
|
266
|
-
|
|
267
|
-
def fmt(text : str,*args,**kwargs) -> str:
|
|
268
|
-
"""
|
|
269
|
-
String formatting made easy
|
|
270
|
-
text - pattern
|
|
271
|
-
Examples
|
|
272
|
-
fmt("The is one = %ld", 1)
|
|
273
|
-
fmt("The is text = %s", 1.3)
|
|
274
|
-
fmt("Using keywords: one=%(one)d, two=%(two)d", two=2, one=1)
|
|
166
|
+
def fmt_seconds( seconds : float, *, eps : float = 1E-8 ) -> str:
|
|
275
167
|
"""
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
168
|
+
Generate format string for seconds, e.g. "23s"" for ``seconds=23``, or "1:10" for ``seconds=70``.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
seconds : float
|
|
173
|
+
Seconds as a float.
|
|
174
|
+
|
|
175
|
+
eps : float
|
|
176
|
+
anything below ``eps`` is considered zero. Default ``1E-8``.
|
|
285
177
|
|
|
286
|
-
|
|
287
|
-
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
Seconds : string
|
|
181
|
+
"""
|
|
288
182
|
assert eps>=0., ("'eps' must not be negative")
|
|
289
183
|
if seconds < -eps:
|
|
290
184
|
return "-" + fmt_seconds(-seconds, eps=eps)
|
|
@@ -304,19 +198,25 @@ def fmt_seconds( seconds : float, *, eps : float = 1E-8 ) -> str:
|
|
|
304
198
|
|
|
305
199
|
def fmt_list( lst : list, *, none : str = "-", link : str = "and", sort : bool = False ) -> str:
|
|
306
200
|
"""
|
|
307
|
-
Returns a
|
|
308
|
-
|
|
201
|
+
Returns a formatted string of a list, its elements separated by commas and (by default) a final 'and'.
|
|
202
|
+
|
|
203
|
+
If the list is ``[1,2,3]`` then the function will return ``"1, 2 and 3"``.
|
|
204
|
+
|
|
309
205
|
Parameters
|
|
310
206
|
----------
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
207
|
+
lst : list.
|
|
208
|
+
The ``list()`` operator is applied to ``lst``, so it will resolve dictionaries and generators.
|
|
209
|
+
none : str, optional
|
|
210
|
+
String to be used when ``list`` is empty. Default is ``"-"``.
|
|
211
|
+
link : str, optional
|
|
212
|
+
String to be used to connect the last item. Default is ``"and"``.
|
|
213
|
+
sort : bool, optional
|
|
214
|
+
Whether to sort the list. Default is ``False``.
|
|
316
215
|
|
|
317
216
|
Returns
|
|
318
217
|
-------
|
|
319
|
-
|
|
218
|
+
Text : str
|
|
219
|
+
String.
|
|
320
220
|
"""
|
|
321
221
|
if lst is None:
|
|
322
222
|
return str(none)
|
|
@@ -343,21 +243,27 @@ def fmt_list( lst : list, *, none : str = "-", link : str = "and", sort : bool =
|
|
|
343
243
|
|
|
344
244
|
def fmt_dict( dct : dict, *, sort : bool = False, none : str = "-", link : str = "and" ) -> str:
|
|
345
245
|
"""
|
|
346
|
-
Return a
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
246
|
+
Return a readable representation of a dictionary.
|
|
247
|
+
|
|
248
|
+
This assumes that the elements of the dictionary itself can be formatted well with :func:`str()`.
|
|
249
|
+
|
|
250
|
+
For a dictionary ``dict(a=1,b=2,c=3)`` this function will return ``"a: 1, b: 2, and c: 3"``.
|
|
350
251
|
|
|
351
252
|
Parameters
|
|
352
253
|
----------
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
254
|
+
dct : dict
|
|
255
|
+
The dictionary to format.
|
|
256
|
+
sort : bool, optional
|
|
257
|
+
Whether to sort the keys. Default is ``False``.
|
|
258
|
+
none : str, optional
|
|
259
|
+
String to be used if dictionary is empty. Default is ``"-"``.
|
|
260
|
+
link : str, optional
|
|
261
|
+
String to be used to link the last element to the previous string. Default is ``"and"``.
|
|
357
262
|
|
|
358
263
|
Returns
|
|
359
264
|
-------
|
|
360
|
-
|
|
265
|
+
Text : str
|
|
266
|
+
String.
|
|
361
267
|
"""
|
|
362
268
|
if len(dct) == 0:
|
|
363
269
|
return str(none)
|
|
@@ -368,46 +274,52 @@ def fmt_dict( dct : dict, *, sort : bool = False, none : str = "-", link : str =
|
|
|
368
274
|
strs = [ str(k) + ": " + str(dct[k]) for k in keys ]
|
|
369
275
|
return fmt_list( strs, none=none, link=link, sort=False )
|
|
370
276
|
|
|
371
|
-
def fmt_digits(
|
|
277
|
+
def fmt_digits( integer : int, sep : str = "," ):
|
|
372
278
|
"""
|
|
373
|
-
String representation of
|
|
374
|
-
So 10000 becomes "10,000".
|
|
279
|
+
String representation of an integer with 1000 separators: 10000 becomes "10,000".
|
|
375
280
|
|
|
376
281
|
Parameters
|
|
377
282
|
--------
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
283
|
+
integer : int
|
|
284
|
+
The number. The function will :func:`int()` the input which allows
|
|
285
|
+
for processing of a number of inputs (such as strings) but
|
|
286
|
+
might cut off floating point numbers.
|
|
287
|
+
|
|
288
|
+
sep : str
|
|
289
|
+
Separator; ``","`` by default.
|
|
290
|
+
|
|
384
291
|
Returns
|
|
385
292
|
-------
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
293
|
+
Text : str
|
|
294
|
+
String.
|
|
295
|
+
"""
|
|
296
|
+
if isinstance( integer, float ):
|
|
297
|
+
raise ValueError("float value provided", integer)
|
|
298
|
+
integer = int(integer)
|
|
299
|
+
if integer < 0:
|
|
300
|
+
return "-" + fmt_digits( -integer, sep )
|
|
301
|
+
assert integer >= 0
|
|
302
|
+
if integer < 1000:
|
|
303
|
+
return "%ld" % integer
|
|
396
304
|
else:
|
|
397
|
-
return fmt_digits(
|
|
305
|
+
return fmt_digits(integer//1000, sep) + ( sep + "%03ld" % (integer % 1000) )
|
|
398
306
|
|
|
399
307
|
def fmt_big_number( number : int ) -> str:
|
|
400
308
|
"""
|
|
401
309
|
Return a formatted big number string, e.g. 12.35M instead of all digits.
|
|
310
|
+
|
|
402
311
|
Uses decimal system and "B" for billions.
|
|
403
|
-
Use fmt_big_byte_number for byte sizes
|
|
312
|
+
Use :func:`cdxcore.util.fmt_big_byte_number` for byte sizes i.e. 1024 units.
|
|
404
313
|
|
|
405
314
|
Parameters
|
|
406
315
|
----------
|
|
407
|
-
|
|
316
|
+
number : int
|
|
317
|
+
Number to format.
|
|
318
|
+
|
|
408
319
|
Returns
|
|
409
320
|
-------
|
|
410
|
-
|
|
321
|
+
Text : str
|
|
322
|
+
String.
|
|
411
323
|
"""
|
|
412
324
|
if isinstance( number, float ):
|
|
413
325
|
raise ValueError("float value provided", number)
|
|
@@ -439,20 +351,31 @@ def fmt_big_number( number : int ) -> str:
|
|
|
439
351
|
return "%gK" % number
|
|
440
352
|
return str(number)
|
|
441
353
|
|
|
442
|
-
def fmt_big_byte_number( byte_cnt : int, str_B = True ) -> str:
|
|
354
|
+
def fmt_big_byte_number( byte_cnt : int, str_B : bool = True ) -> str:
|
|
443
355
|
"""
|
|
444
|
-
Return a formatted big
|
|
356
|
+
Return a formatted big byte string, e.g. 12.35MB.
|
|
357
|
+
Uses 1024 as base for KB.
|
|
358
|
+
|
|
359
|
+
Use :func:`cdxcore.util.fmt_big_number` for converting general numbers
|
|
360
|
+
using 1000 blocks instead.
|
|
445
361
|
|
|
446
362
|
Parameters
|
|
447
363
|
----------
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
364
|
+
byte_cnt : int
|
|
365
|
+
Number of bytes.
|
|
366
|
+
|
|
367
|
+
str_B : bool
|
|
368
|
+
If ``True``, return ``"GB"``, ``"MB"`` and ``"KB"`` units.
|
|
369
|
+
Moreover, if ``byte_cnt` is less than 10KB, then this will add ``"bytes"``
|
|
370
|
+
e.g. ``"1024 bytes"``.
|
|
371
|
+
|
|
372
|
+
If ``False``, return ``"G"``, ``"M"`` and ``"K"`` only, and do not
|
|
373
|
+
add ``"bytes"`` to smaller ``byte_cnt``.
|
|
374
|
+
|
|
453
375
|
Returns
|
|
454
376
|
-------
|
|
455
|
-
|
|
377
|
+
Text : str
|
|
378
|
+
String.
|
|
456
379
|
"""
|
|
457
380
|
if isinstance( byte_cnt, float ):
|
|
458
381
|
raise ValueError("float value provided", byte_cnt)
|
|
@@ -487,36 +410,45 @@ def fmt_big_byte_number( byte_cnt : int, str_B = True ) -> str:
|
|
|
487
410
|
return str(byte_cnt) if not str_B else f"{byte_cnt} bytes"
|
|
488
411
|
return s if not str_B else s+"B"
|
|
489
412
|
|
|
490
|
-
def fmt_datetime(dt : datetime.datetime, *,
|
|
413
|
+
def fmt_datetime(dt : datetime.datetime|datetime.date|datetime.time, *,
|
|
491
414
|
sep : str = ':',
|
|
492
415
|
ignore_ms : bool = False,
|
|
493
416
|
ignore_tz : bool = True
|
|
494
417
|
) -> str:
|
|
495
418
|
"""
|
|
496
|
-
|
|
497
|
-
or a the respective version for time or date.
|
|
419
|
+
Convert :class:`datetime.datetime` to a string of the form "YYYY-MM-DD HH:MM:SS".
|
|
498
420
|
|
|
499
|
-
|
|
500
|
-
|
|
421
|
+
If present, microseconds are added as digits::
|
|
422
|
+
|
|
423
|
+
YYYY-MM-DD HH:MM:SS,MICROSECONDS
|
|
424
|
+
|
|
425
|
+
Optinally a time zone is added via::
|
|
501
426
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
"YYYY-MM-DD HH:MM:SS+HH:MM"
|
|
427
|
+
YYYY-MM-DD HH:MM:SS+HH
|
|
428
|
+
YYYY-MM-DD HH:MM:SS+HH:MM
|
|
505
429
|
|
|
430
|
+
Output is reduced accordingly if ``dt`` is a :class:`datetime.time`
|
|
431
|
+
or :class:`datetime.date`.
|
|
432
|
+
|
|
506
433
|
Parameters
|
|
507
434
|
----------
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
435
|
+
dt : :class:`datetime.datetime`, :class:`datetime.date`, or :class:`datetime.time`
|
|
436
|
+
Input.
|
|
437
|
+
|
|
438
|
+
sep : str, optional
|
|
439
|
+
Seperator for hours, minutes, seconds. The default ``':'`` is most appropriate for viusalization
|
|
440
|
+
but is not suitable for filenames.
|
|
441
|
+
|
|
442
|
+
ignore_ms : bool, optional
|
|
443
|
+
Whether to ignore microseconds. Default ``False``.
|
|
444
|
+
|
|
445
|
+
ignore_tz : bool, optional
|
|
446
|
+
Whether to ignore the time zone. Default ``True``.
|
|
447
|
+
|
|
517
448
|
Returns
|
|
518
449
|
-------
|
|
519
|
-
|
|
450
|
+
Text : str
|
|
451
|
+
String.
|
|
520
452
|
"""
|
|
521
453
|
if not isinstance(dt, datetime.datetime):
|
|
522
454
|
if isinstance(dt, datetime.date):
|
|
@@ -552,12 +484,13 @@ def fmt_datetime(dt : datetime.datetime, *,
|
|
|
552
484
|
|
|
553
485
|
def fmt_date(dt : datetime.date) -> str:
|
|
554
486
|
"""
|
|
555
|
-
Returns string representation for date
|
|
556
|
-
|
|
487
|
+
Returns string representation for a date of the form "YYYY-MM-DD".
|
|
488
|
+
|
|
489
|
+
If passed a :class:`datetime.datetime`, it will format its :func:`datetime.datetime.date`.
|
|
557
490
|
"""
|
|
558
491
|
if isinstance(dt, datetime.datetime):
|
|
559
492
|
dt = dt.date()
|
|
560
|
-
assert isinstance(dt, datetime.date), "'dt' must be datetime.date
|
|
493
|
+
assert isinstance(dt, datetime.date), "'dt' must be :class:`datetime.date`. Found %s" % type(dt)
|
|
561
494
|
return f"{dt.year:04d}-{dt.month:02d}-{dt.day:02d}"
|
|
562
495
|
|
|
563
496
|
def fmt_time(dt : datetime.time, *,
|
|
@@ -565,34 +498,46 @@ def fmt_time(dt : datetime.time, *,
|
|
|
565
498
|
ignore_ms : bool = False
|
|
566
499
|
) -> str:
|
|
567
500
|
"""
|
|
568
|
-
|
|
501
|
+
Convers a time to a string with format "HH:MM:SS".
|
|
569
502
|
|
|
570
|
-
Microseconds are added as digits
|
|
571
|
-
|
|
503
|
+
Microseconds are added as digits::
|
|
504
|
+
|
|
505
|
+
HH:MM:SS,MICROSECONDS
|
|
572
506
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
507
|
+
If passed a :class:`datetime.datetime`, then this function will format
|
|
508
|
+
only its :func:`datetime.datetime.time` part.
|
|
509
|
+
|
|
510
|
+
**Time Zones**
|
|
511
|
+
|
|
512
|
+
Note that while :class:`datetime.time` objects may carry a ``tzinfo`` time zone object,
|
|
513
|
+
the corresponding :func:`datetime.time.otcoffset` function returns ``None`` if we donot
|
|
514
|
+
provide a ``dt`` parameter, see
|
|
515
|
+
`tzinfo documentation <https://docs.python.org/3/library/datetime.html#tzinfo-objects>`__.
|
|
516
|
+
That means :func:`datetime.time.otcoffset` is only useful if we have :class:`datetime.datetime`
|
|
517
|
+
object at hand.
|
|
518
|
+
That makes sense as a time zone can chnage date as well.
|
|
581
519
|
|
|
520
|
+
We therefore here do not allow ``dt`` to contain
|
|
521
|
+
a time zone.
|
|
522
|
+
|
|
523
|
+
Use :func:`cdxcore.util.fmt_datetime` for time zone support
|
|
582
524
|
|
|
583
525
|
Parameters
|
|
584
526
|
----------
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
527
|
+
dt : :class:`datetime.time`
|
|
528
|
+
Input.
|
|
529
|
+
sep : str, optional
|
|
530
|
+
|
|
531
|
+
Seperator for hours, minutes, seconds. The default ``':'`` is most appropriate for viusalization
|
|
532
|
+
but is not suitable for filenames.
|
|
533
|
+
|
|
534
|
+
ignore_ms : bool
|
|
535
|
+
Whether to ignore microseconds. Default is ``False``.
|
|
592
536
|
|
|
593
537
|
Returns
|
|
594
538
|
-------
|
|
595
|
-
|
|
539
|
+
Text : str
|
|
540
|
+
String.
|
|
596
541
|
"""
|
|
597
542
|
if isinstance(dt, datetime.datetime):
|
|
598
543
|
dt = dt.timetz()
|
|
@@ -606,28 +551,33 @@ def fmt_time(dt : datetime.time, *,
|
|
|
606
551
|
def fmt_timedelta(dt : datetime.timedelta, *,
|
|
607
552
|
sep : str = "" ) -> str:
|
|
608
553
|
"""
|
|
609
|
-
Returns string representation for a time delta in the form DD:HH:MM:SS,MS
|
|
610
|
-
|
|
554
|
+
Returns string representation for a time delta in the form "DD:HH:MM:SS,MS".
|
|
611
555
|
|
|
612
556
|
Parameters
|
|
613
557
|
----------
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
is
|
|
558
|
+
dt : :class:`datetime.timedelta`
|
|
559
|
+
Timedelta.
|
|
560
|
+
|
|
561
|
+
sep :
|
|
562
|
+
Identify the three separators: between days, and HMS and between microseconds:
|
|
563
|
+
|
|
564
|
+
.. code-block:: python
|
|
565
|
+
|
|
566
|
+
DD*HH*MM*SS*MS
|
|
567
|
+
0 1 1 2
|
|
568
|
+
|
|
569
|
+
* ``sep`` can be a string, in which case:
|
|
570
|
+
* If it is an empty string, all separators are ``''``.
|
|
571
|
+
* A single character will be reused for all separators.
|
|
572
|
+
* If the string has length 2, then the last character is used for ``'2'``.
|
|
573
|
+
* If the string has length 3, then the chracters are used accordingly.
|
|
574
|
+
|
|
575
|
+
* ``sep`` can also be a collection ie a ``tuple`` or ``list``. In this case each element is used accordingly.
|
|
627
576
|
|
|
628
577
|
Returns
|
|
629
578
|
-------
|
|
630
|
-
|
|
579
|
+
Text : str
|
|
580
|
+
String with leading sign. Returns "" if ``timedelta`` is 0.
|
|
631
581
|
"""
|
|
632
582
|
assert isinstance(dt, datetime.timedelta), "'dt' must be datetime.timedelta. Found %s" % type(dt)
|
|
633
583
|
|
|
@@ -690,10 +640,10 @@ def fmt_timedelta(dt : datetime.timedelta, *,
|
|
|
690
640
|
return f"{sign}{days}d{rest}"
|
|
691
641
|
|
|
692
642
|
def fmt_now() -> str:
|
|
693
|
-
""" Returns
|
|
643
|
+
""" Returns the :func:`cdxcore.util.fmt_datetime` applied to :func:`datetime.datetime.now` """
|
|
694
644
|
return fmt_datetime(datetime.datetime.now())
|
|
695
645
|
|
|
696
|
-
DEF_FILE_NAME_MAP = {
|
|
646
|
+
DEF_FILE_NAME_MAP = {
|
|
697
647
|
'/' : "_",
|
|
698
648
|
'\\': "_",
|
|
699
649
|
'|' : "_",
|
|
@@ -703,144 +653,109 @@ DEF_FILE_NAME_MAP = {
|
|
|
703
653
|
'?' : "!",
|
|
704
654
|
'*' : "@",
|
|
705
655
|
}
|
|
706
|
-
|
|
656
|
+
"""
|
|
657
|
+
Default map from characters which cannot be used for filenames under either
|
|
658
|
+
Windows or Linux to valid characters.
|
|
659
|
+
"""
|
|
707
660
|
|
|
708
|
-
def fmt_filename(
|
|
709
|
-
"""
|
|
710
|
-
Replaces invalid filename characters by a differnet character.
|
|
661
|
+
def fmt_filename( filename : str , by : str | Mapping = "default" ) -> str:
|
|
662
|
+
r"""
|
|
663
|
+
Replaces invalid filename characters such as `\\', ':', or '/' by a differnet character.
|
|
711
664
|
The returned string is technically a valid file name under both windows and linux.
|
|
712
665
|
|
|
713
666
|
However, that does not prevent the filename to be a reserved name, for example "." or "..".
|
|
714
667
|
|
|
715
668
|
Parameters
|
|
716
669
|
----------
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
670
|
+
filename : str
|
|
671
|
+
Input string.
|
|
672
|
+
|
|
673
|
+
by : str | Mapping, optional.
|
|
674
|
+
A dictionary of characters and their replacement.
|
|
675
|
+
The default value ``"default"`` leads to using :data:`cdxcore.util.DEF_FILE_NAME_MAP`.
|
|
676
|
+
|
|
677
|
+
Returns
|
|
678
|
+
-------
|
|
679
|
+
Text : str
|
|
680
|
+
Filename
|
|
721
681
|
"""
|
|
682
|
+
if not isinstance(by, Mapping):
|
|
683
|
+
if not isinstance(by, str):
|
|
684
|
+
raise ValueError(f"'by': must be a Mapping or 'default'. Found type {type(by).__qualname__}")
|
|
685
|
+
if by != "default":
|
|
686
|
+
raise ValueError(f"'by': must be a Mapping or 'default'. Found string '{by}'")
|
|
687
|
+
by = DEF_FILE_NAME_MAP
|
|
722
688
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
assert isinstance(by, str), ("by: 'str' or mapping expected", type(by))
|
|
728
|
-
for c in INVALID_FILE_NAME_CHARCTERS:
|
|
729
|
-
s = s.replace(c, by)
|
|
730
|
-
return s
|
|
689
|
+
for c, cby in by.items():
|
|
690
|
+
filename = filename.replace(c, cby)
|
|
691
|
+
return filename
|
|
692
|
+
fmt_filename.DEF_FILE_NAME_MAP = DEF_FILE_NAME_MAP
|
|
731
693
|
|
|
732
|
-
|
|
694
|
+
def is_filename( filename : str , by : str | Collection = "default" ) -> bool:
|
|
733
695
|
"""
|
|
734
|
-
|
|
735
|
-
This class is a thin wrapper around print(text + '\r', end='') or IPython.display.display()
|
|
736
|
-
to ensure the current line is cleared correctly when replaced with the next line.
|
|
737
|
-
|
|
738
|
-
Example 1 (how to use \r and \n)
|
|
739
|
-
write = WriteLine("Initializing...")
|
|
740
|
-
import time
|
|
741
|
-
for i in range(10):
|
|
742
|
-
time.sleep(1)
|
|
743
|
-
write("\rRunning %g%% ...", round(float(i+1)/float(10)*100,0))
|
|
744
|
-
write(" done.\nProcess finished.\n")
|
|
745
|
-
|
|
746
|
-
Example 2 (line length is getting shorter)
|
|
747
|
-
write = WriteLine("Initializing...")
|
|
748
|
-
import time
|
|
749
|
-
for i in range(10):
|
|
750
|
-
time.sleep(1)
|
|
751
|
-
write("\r" + ("#" * (9-i)))
|
|
752
|
-
write("\rProcess finished.\n")
|
|
753
|
-
"""
|
|
754
|
-
|
|
755
|
-
def __init__(self, text : str = "", *kargs, **kwargs):
|
|
756
|
-
"""
|
|
757
|
-
Creates a new WriteLine object which manages the current print output line.
|
|
758
|
-
Subsequent calls to __call__() will replace the text in the current line using `\r` in text mode, or a display() object in jupyter
|
|
759
|
-
|
|
760
|
-
Parameters
|
|
761
|
-
----------
|
|
762
|
-
text : str
|
|
763
|
-
Classic formatting text. 'text' may not contain newlines (\n) except at the end.
|
|
764
|
-
kargs, kwargs:
|
|
765
|
-
Formatting arguments.
|
|
766
|
-
"""
|
|
767
|
-
self._last_len = 0
|
|
768
|
-
if text != "":
|
|
769
|
-
self(text,*kargs,**kwargs)
|
|
696
|
+
Tests whether a filename is indeed a valid filename.
|
|
770
697
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
self._last_len = 0
|
|
799
|
-
|
|
800
|
-
def _write_line(self, line):
|
|
801
|
-
""" Write a line; no newlines """
|
|
802
|
-
assert not '\n' in line, "Error: found newline in '%s'" % line
|
|
803
|
-
if line == "":
|
|
804
|
-
return
|
|
805
|
-
i = line.rfind('\r')
|
|
806
|
-
if i == -1:
|
|
807
|
-
# no `\r': append text to current line
|
|
808
|
-
sys.stdout.write(line)
|
|
809
|
-
self._last_len += len(line)
|
|
810
|
-
else:
|
|
811
|
-
# found '\r': clear previous line and print new line
|
|
812
|
-
line = line[i+1:]
|
|
813
|
-
if len(line) < self._last_len:
|
|
814
|
-
sys.stdout.write("\r" + (" " * self._last_len)) # clear current line
|
|
815
|
-
sys.stdout.write("\r" + line)
|
|
816
|
-
self._last_len = len(line)
|
|
698
|
+
Parameters
|
|
699
|
+
----------
|
|
700
|
+
filename : str
|
|
701
|
+
Supposed filename.
|
|
702
|
+
|
|
703
|
+
by : str | Collection, optional
|
|
704
|
+
A collection of invalid characters.
|
|
705
|
+
The default value ``"default"`` leads to using
|
|
706
|
+
they keys of :data:`cdxcore.util.DEF_FILE_NAME_MAP`.
|
|
707
|
+
|
|
708
|
+
Returns
|
|
709
|
+
-------
|
|
710
|
+
Validity : vool
|
|
711
|
+
``True`` if ``filename`` does not contain any invalid characters contained in ``by``.
|
|
712
|
+
"""
|
|
713
|
+
|
|
714
|
+
if not isinstance(by, Mapping):
|
|
715
|
+
if not isinstance(by, str):
|
|
716
|
+
raise ValueError(f"'by': must be a Mapping or 'default'. Found type {type(by).__qualname__}")
|
|
717
|
+
if by != "default":
|
|
718
|
+
raise ValueError(f"'by': must be a Mapping or 'default'. Found string '{by}'")
|
|
719
|
+
by = DEF_FILE_NAME_MAP
|
|
720
|
+
|
|
721
|
+
for c in by:
|
|
722
|
+
if c in filename:
|
|
723
|
+
return False
|
|
724
|
+
return True
|
|
817
725
|
|
|
818
726
|
# =============================================================================
|
|
819
727
|
# Conversion of arbitrary python elements into re-usable versions
|
|
820
728
|
# =============================================================================
|
|
821
729
|
|
|
822
|
-
# deprecated
|
|
823
730
|
def plain( inn, *, sorted_dicts : bool = False,
|
|
824
731
|
native_np : bool = False,
|
|
825
732
|
dt_to_str : bool = False):
|
|
826
733
|
"""
|
|
827
734
|
Converts a python structure into a simple atomic/list/dictionary collection such
|
|
828
735
|
that it can be read without the specific imports used inside this program.
|
|
829
|
-
|
|
736
|
+
|
|
737
|
+
For example, objects are converted into dictionaries of their data fields.
|
|
830
738
|
|
|
831
739
|
Parameters
|
|
832
740
|
----------
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
741
|
+
inn :
|
|
742
|
+
some object.
|
|
743
|
+
sorted_dicts : bool, optional
|
|
744
|
+
use SortedDicts instead of dicts. Since Python 3.7 all dictionaries are sorted anyway.
|
|
745
|
+
native_np : bool, optional
|
|
746
|
+
convert numpy to Python natives.
|
|
747
|
+
dt_to_str : bool, optional
|
|
748
|
+
convert dates, times, and datetimes to strings.
|
|
837
749
|
|
|
838
|
-
|
|
750
|
+
Returns
|
|
751
|
+
-------
|
|
752
|
+
Text : str
|
|
753
|
+
Filename
|
|
839
754
|
"""
|
|
840
755
|
def rec_plain( x ):
|
|
841
756
|
return plain( x, sorted_dicts=sorted_dicts, native_np=native_np, dt_to_str=dt_to_str )
|
|
842
757
|
# basics
|
|
843
|
-
if
|
|
758
|
+
if is_atomic(inn) or inn is None:
|
|
844
759
|
return inn
|
|
845
760
|
if isinstance(inn,(datetime.time,datetime.date,datetime.datetime)):
|
|
846
761
|
return fmt_datetime(inn) if dt_to_str else inn
|
|
@@ -852,11 +767,11 @@ def plain( inn, *, sorted_dicts : bool = False,
|
|
|
852
767
|
elif isinstance(inn, np.floating):
|
|
853
768
|
return float(inn)
|
|
854
769
|
# can't handle functions --> return None
|
|
855
|
-
if
|
|
770
|
+
if is_function(inn) or isinstance(inn,property):
|
|
856
771
|
return None
|
|
857
772
|
# dictionaries
|
|
858
773
|
if isinstance(inn,Mapping):
|
|
859
|
-
r = { k: rec_plain(v) for k, v in inn.items() if not
|
|
774
|
+
r = { k: rec_plain(v) for k, v in inn.items() if not is_function(v) and not isinstance(v,property) }
|
|
860
775
|
return r if not sorted_dicts else SortedDict(r)
|
|
861
776
|
# pandas
|
|
862
777
|
if not pd is None and isinstance(inn,pd.DataFrame):
|
|
@@ -877,10 +792,12 @@ def plain( inn, *, sorted_dicts : bool = False,
|
|
|
877
792
|
# Misc Jupyter
|
|
878
793
|
# =============================================================================
|
|
879
794
|
|
|
880
|
-
def is_jupyter():
|
|
795
|
+
def is_jupyter() -> bool:
|
|
881
796
|
"""
|
|
882
|
-
|
|
883
|
-
Somewhat unreliable function. Use with care
|
|
797
|
+
Whether we operate in a jupter session.
|
|
798
|
+
Somewhat unreliable function. Use with care.
|
|
799
|
+
|
|
800
|
+
:meta private:
|
|
884
801
|
"""
|
|
885
802
|
parent_process = psutil.Process().parent().cmdline()[-1]
|
|
886
803
|
return 'jupyter' in parent_process
|
|
@@ -892,8 +809,10 @@ def is_jupyter():
|
|
|
892
809
|
class TrackTiming(object):
|
|
893
810
|
"""
|
|
894
811
|
Simplistic class to track the time it takes to run sequential tasks.
|
|
895
|
-
Usage:
|
|
896
812
|
|
|
813
|
+
Usage::
|
|
814
|
+
|
|
815
|
+
from cdxcore.util import TrackTiming
|
|
897
816
|
timer = TrackTiming() # clock starts
|
|
898
817
|
|
|
899
818
|
# do job 1
|
|
@@ -944,23 +863,28 @@ class TrackTiming(object):
|
|
|
944
863
|
""" Returns dictionary of tracked texts """
|
|
945
864
|
return self._tracked
|
|
946
865
|
|
|
947
|
-
def summary(self,
|
|
866
|
+
def summary(self, fmat : str = "%(text)s: %(fmt_seconds)s", jn_fmt : str = ", " ) -> str:
|
|
948
867
|
"""
|
|
949
868
|
Generate summary string by applying some formatting
|
|
950
869
|
|
|
951
870
|
Parameters
|
|
952
871
|
----------
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
872
|
+
fmat : str, optional
|
|
873
|
+
Format string using ``%()``. Arguments are ```text``, ``seconds`` (as int) and ``fmt_seconds`` (a string).
|
|
874
|
+
|
|
875
|
+
Default is ``"%(text)s: %(fmt_seconds)s"``.
|
|
876
|
+
|
|
877
|
+
jn_fmt : str, optional
|
|
878
|
+
String to be used between two texts. Default ``", " ``.
|
|
879
|
+
|
|
957
880
|
Returns
|
|
958
881
|
-------
|
|
882
|
+
Summary : str
|
|
959
883
|
The combined summary string
|
|
960
884
|
"""
|
|
961
885
|
s = ""
|
|
962
886
|
for text, seconds in self._tracked.items():
|
|
963
|
-
tr_txt =
|
|
887
|
+
tr_txt = fmat % dict( text=text, seconds=seconds, fmt_seconds=fmt_seconds(seconds))
|
|
964
888
|
s = tr_txt if s=="" else s+jn_fmt+tr_txt
|
|
965
889
|
return s
|
|
966
890
|
|
|
@@ -970,11 +894,14 @@ class TrackTiming(object):
|
|
|
970
894
|
|
|
971
895
|
class Timer(object):
|
|
972
896
|
"""
|
|
973
|
-
Micro utility
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
897
|
+
Micro utility to measure passage of time.
|
|
898
|
+
|
|
899
|
+
Example::
|
|
900
|
+
|
|
901
|
+
from cdxcore.util import Timer
|
|
902
|
+
with Timer() as t:
|
|
903
|
+
.... do somthing ...
|
|
904
|
+
print(f"This took {t}.")
|
|
978
905
|
"""
|
|
979
906
|
|
|
980
907
|
def __init__(self):
|
|
@@ -982,6 +909,7 @@ class Timer(object):
|
|
|
982
909
|
self.intv = None
|
|
983
910
|
|
|
984
911
|
def reset(self):
|
|
912
|
+
""" Resets the timer. """
|
|
985
913
|
self.time = time.time()
|
|
986
914
|
self.intv = None
|
|
987
915
|
|
|
@@ -990,19 +918,26 @@ class Timer(object):
|
|
|
990
918
|
return self
|
|
991
919
|
|
|
992
920
|
def __str__(self):
|
|
921
|
+
"""
|
|
922
|
+
Seconds elapsed since construction or :meth:`cdxcore.util.Timer.reset`,
|
|
923
|
+
formatted using :func:`cdxcore.util.Timer.fmt_seconds`
|
|
924
|
+
"""
|
|
993
925
|
return self.fmt_seconds
|
|
994
926
|
|
|
995
|
-
def interval_test( self, interval : float ):
|
|
996
|
-
"""
|
|
997
|
-
Tests if
|
|
998
|
-
If yes, reset timer and return True. Otherwise return False
|
|
927
|
+
def interval_test( self, interval : float ) -> bool:
|
|
928
|
+
r"""
|
|
929
|
+
Tests if `interval` seconds have passed.
|
|
930
|
+
If yes, reset timer and return True. Otherwise return False.
|
|
999
931
|
|
|
1000
|
-
Usage
|
|
1001
|
-
|
|
932
|
+
Usage::
|
|
933
|
+
|
|
934
|
+
from cdxcore.util import Timer
|
|
1002
935
|
tme = Timer()
|
|
1003
936
|
for i in range(n):
|
|
1004
|
-
if tme.test_dt_seconds(2.):
|
|
937
|
+
if tme.test_dt_seconds(2.):
|
|
938
|
+
print(f"\r{i+1}/{n} done. Time taken so far {tme}.", end='', flush=True)
|
|
1005
939
|
print("\rDone. This took {tme}.")
|
|
940
|
+
|
|
1006
941
|
"""
|
|
1007
942
|
if interval is None:
|
|
1008
943
|
self.intv = self.seconds
|
|
@@ -1017,18 +952,24 @@ class Timer(object):
|
|
|
1017
952
|
|
|
1018
953
|
@property
|
|
1019
954
|
def fmt_seconds(self):
|
|
955
|
+
"""
|
|
956
|
+
Seconds elapsed since construction or :meth:`cdxcore.util.Timer.reset`, formatted using :func:`cdxcore.util.fmt_seconds`
|
|
957
|
+
"""
|
|
1020
958
|
return fmt_seconds(self.seconds)
|
|
1021
959
|
|
|
1022
960
|
@property
|
|
1023
|
-
def seconds(self):
|
|
961
|
+
def seconds(self) -> float:
|
|
962
|
+
""" Seconds elapsed since construction or :meth:`cdxcore.util.Timer.reset` """
|
|
1024
963
|
return time.time() - self.time
|
|
1025
964
|
|
|
1026
965
|
@property
|
|
1027
|
-
def minutes(self):
|
|
966
|
+
def minutes(self) -> float:
|
|
967
|
+
""" Minutes passed since construction or :meth:`cdxcore.util.Timer.reset` """
|
|
1028
968
|
return self.seconds / 60.
|
|
1029
969
|
|
|
1030
970
|
@property
|
|
1031
|
-
def hours(self):
|
|
971
|
+
def hours(self) -> float:
|
|
972
|
+
""" Hours passed since construction or :meth:`cdxcore.util.Timer.reset` """
|
|
1032
973
|
return self.minutes / 60.
|
|
1033
974
|
|
|
1034
975
|
def __exit__(self, *kargs, **wargs):
|