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/crman.py CHANGED
@@ -1,52 +1,91 @@
1
- """
2
- crman
3
- Managing \r updates
4
- Hans Buehler 2023
1
+ r"""
2
+ Overview
3
+ --------
4
+
5
+ A very simple implementation of a tool that tracks the last printed line before a newline was encountered.
6
+ Helps with somewhat consistent progress reporting with "\\r" and "\\n" characters.
7
+
8
+ *This functionality does not quite work accross all terminal types which were tested. Main focus is to make
9
+ it work for Jupyer for now. Any feedback on
10
+ how to make this more generically operational is welcome.
11
+
12
+ Used by :class:`cdxcore.verbose.Context`.
13
+
14
+ Import
15
+ ------
16
+
17
+ .. code-block:: python
18
+
19
+ from cdxcore.crman import CRman
20
+
5
21
  """
6
22
 
7
23
  from collections.abc import Callable
8
- from .logger import Logger#
9
- _log = Logger(__file__)
10
24
 
11
25
  class CRMan(object):
12
- """
13
- Carraige Return ('\r') manager.
14
- This class is meant to enable efficient per-line updates using '\r' for text output with a focus on making it work with both Jupyter and the command shell.
15
- In particular, Jupyter does not support the ANSI \33[2K 'clear line' code.
26
+ r"""
27
+ Carriage Return ("\\r") manager.
16
28
 
29
+ This class is meant to enable efficient per-line updates using "\\r" for text output with a focus on making it work with both Jupyter and the command shell.
30
+ In particular, Jupyter does not support the ANSI `\\33[2K` 'clear line' code. To simulate clearing
31
+ lines, ``CRMan`` keeps track of the length of the current line, and clears it by appending spaces to a message
32
+ following "\\r"
33
+ accordingly.
34
+
35
+ *This functionality does not quite work accross all terminal types which were tested. Main focus is to make
36
+ it work for Jupyer for now. Any feedback on
37
+ how to make this more generically operational is welcome.*
38
+
39
+ .. code-block:: python
40
+
17
41
  crman = CRMan()
18
42
  print( crman("\rmessage 111111"), end='' )
19
43
  print( crman("\rmessage 2222"), end='' )
20
44
  print( crman("\rmessage 33"), end='' )
21
45
  print( crman("\rmessage 1\n"), end='' )
46
+
47
+ prints::
22
48
 
23
- --> message 1
24
-
49
+ message 1
50
+
51
+ While
52
+
53
+ .. code-block:: python
54
+
25
55
  print( crman("\rmessage 111111"), end='' )
26
56
  print( crman("\rmessage 2222"), end='' )
27
57
  print( crman("\rmessage 33"), end='' )
28
58
  print( crman("\rmessage 1"), end='' )
29
- print( crman("... and more"), end='' )
59
+ print( crman("... and more.") )
30
60
 
31
- --> message 1... and more
61
+ prints
62
+
63
+ .. code-block:: python.
64
+
65
+ message 1... and more
32
66
  """
33
67
 
34
68
  def __init__(self):
35
- """ See help(CRMan) """
69
+ """
70
+ See :class:`cdxcore.crman.CRMan`
71
+ :meta private:
72
+ """
36
73
  self._current = ""
37
74
 
38
75
  def __call__(self, message : str) -> str:
39
- """
40
- Convert 'message' containing '\r' and '\n' into a printable string which ensures that '\r' string do not lead to printed artifacts.
41
- Afterwards, the object will retain any text not terminated by '\n'
76
+ r"""
77
+ Convert `message` containing "\\r" and "\\n" into a printable string which ensures
78
+ that a "\\r" string does not lead to printed artifacts.
79
+ Afterwards, the object will retain any text not terminated by "\\n".
42
80
 
43
81
  Parameters
44
82
  ----------
45
- message : str
46
- message containing \r and \n.
47
-
83
+ message : str
84
+ message containing "\\r" and "\\n".
85
+
48
86
  Returns
49
87
  -------
88
+ Message: str
50
89
  Printable string.
51
90
  """
52
91
  if message is None:
@@ -56,6 +95,8 @@ class CRMan(object):
56
95
  output = ""
57
96
 
58
97
  # first line
98
+ # handle any `current` line
99
+
59
100
  line = lines[0]
60
101
  icr = line.rfind('\r')
61
102
  if icr == -1:
@@ -63,6 +104,7 @@ class CRMan(object):
63
104
  else:
64
105
  line = line[icr+1:]
65
106
  if len(self._current) > 0:
107
+ # print spaces to clear current line in terminals which do not support \33[2K'
66
108
  output += '\r' + ' '*len(self._current) + '\r' + '\33[2K' + '\r'
67
109
  output += line
68
110
  self._current = line
@@ -79,6 +121,7 @@ class CRMan(object):
79
121
  output += line + '\n'
80
122
 
81
123
  # final line
124
+ # keep track of any residuals in `current`
82
125
  line = lines[-1]
83
126
  if len(line) > 0:
84
127
  icr = line.rfind('\r')
@@ -89,13 +132,38 @@ class CRMan(object):
89
132
  return output
90
133
 
91
134
  def reset(self):
92
- """ Reset object """
135
+ """
136
+ Reset object.
137
+ """
93
138
  self._current = ""
94
139
 
95
- def write(self, text, end='', flush=True, channel : Callable = None ):
140
+ @property
141
+ def current(self) -> str:
96
142
  """
97
- Write to stdout using \r and \n translations.
98
- The 'end' and 'flush' parameters mirror those of print()
143
+ Return current string.
144
+
145
+ This is the string that ``CRMan``is currently visible to the user
146
+ since the last time a new line was printed.
147
+ """
148
+ return self._current
149
+
150
+ def write(self, text : str, end : str = '', flush : bool = True, channel : Callable = None ):
151
+ r"""
152
+ Write to a ``channel``,
153
+
154
+ Writes ``text`` to ``channel`` taking into account any ``current`` lines
155
+ and any "\\r" and "\\n" contained in ``text``.
156
+ The ``end`` and ``flush`` parameters mirror those of
157
+ :func:`print`.
158
+
159
+ Parameters
160
+ ----------
161
+ text : str
162
+ Text to print, containing "\\r" and "\\n".
163
+ end, flush : optional
164
+ ``end`` and ``flush`` parameters mirror those of :func:`print`.
165
+ channel : Callable
166
+ Callable to output the residual text. If ``None``, the default, use :func:`print` to write to ``stdout``.
99
167
  """
100
168
  text = self(text+end)
101
169
  if channel is None:
@@ -103,3 +171,5 @@ class CRMan(object):
103
171
  else:
104
172
  channel( text, flush=flush )
105
173
  return self
174
+
175
+
cdxcore/err.py ADDED
@@ -0,0 +1,371 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Overview
4
+ --------
5
+
6
+ Basic error handling and reporting functions.
7
+
8
+ The main use of this module are the functions
9
+ :func:`cdxcore.err.verify` and :func:`cdxcore.err.warn_if`.
10
+ Both test some runtime condition and will either
11
+ raise an ``Exception`` or issue a ``Warning`` if triggered. In both cases, required string formatting is only performed
12
+ if the event is actually triggered.
13
+
14
+ This way we are able to write neat code which produces robust,
15
+ informative errors and warnings without impeding runtime performance.
16
+
17
+ Example::
18
+
19
+ from cdxcore.err import verify, warn_if
20
+ import numpy as np
21
+
22
+ def f( x : np.ndarray ):
23
+ std = np.std(x,axis=0,keepdims=True)
24
+ verify( np.all( std>1E-8 ), "Cannot normalize 'x' by standard deviation: standard deviations are {std}", std=std )
25
+ x /= std
26
+
27
+ f( np.zeros((10,10)) )
28
+
29
+ raises a :class:`RuntimeError`
30
+
31
+ .. code-block:: python
32
+
33
+ Cannot normalize 'x' by standard deviation: standard deviations are [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
34
+
35
+ For warnings, we can use ``warn_if``::
36
+
37
+ from cdxcore.err import verify, warn_if
38
+ import numpy as np
39
+
40
+ def f( x : np.ndarray ):
41
+ std = np.std(x,axis=0,keepdims=True)
42
+ warn_if( not np.all( std>1E-8 ), lambda : f"Normalizing 'x' by standard deviation: standard deviations are {std}" )
43
+ x = np.where( std<1E-8, 0., x/np.where( std<1E-8, 1., std ) )
44
+
45
+ f( np.zeros((10,10)) )
46
+
47
+ issues a warning::
48
+
49
+ RuntimeWarning: Normalizing 'x' by standard deviation: standard deviations are [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
50
+ warn_if( not np.all( std>1E-8 ), "Normalizing 'x' by standard deviation: standard deviations are {std}", std=std )
51
+
52
+ Note that though we used two different approaches for message formatting, the the error messages in both cases
53
+ are only formatted if the condition in ``verify`` is not met.
54
+
55
+ Import
56
+ ------
57
+ .. code-block:: python
58
+
59
+ from cdxcore.err import verify, warn_if, error, warn
60
+
61
+ Documentation
62
+ -------------
63
+ """
64
+
65
+ import warnings as warnings
66
+ import os as os
67
+ from collections.abc import Callable
68
+
69
+ def _fmt( text : str, args = None, kwargs = None, f : Callable = None ) -> str:
70
+ """ Utility function. See [cdxcore.err.fmt][]() . 'f' not currently used.':meta private: """
71
+ args = None if not args is None and len(args) == 0 else args
72
+ kwargs = None if not kwargs is None and len(kwargs) == 0 else kwargs
73
+
74
+ # callable
75
+ if not isinstance(text, str) and callable(text):
76
+ # handle callable
77
+ # we pass args and kwargs as provided
78
+ if not args is None:
79
+ if not kwargs is None:
80
+ return text(* args, ** kwargs)
81
+ else:
82
+ return text(*args)
83
+ elif not kwargs is None :
84
+ return text(**kwargs)
85
+ return text()
86
+ text = str(text)
87
+
88
+ # text
89
+ # C-style positional parameters first
90
+ if not args is None:
91
+ # args are only valid for c-style %d, %s
92
+ if not kwargs is None:
93
+ raise ValueError("Cannot specify both 'args' and 'kwargs'", text)
94
+ try:
95
+ return text % tuple(args)
96
+ except TypeError as e:
97
+ raise TypeError(e, text, args)
98
+ # text
99
+ # python 2 and 3 mode
100
+ kwargs = dict() if kwargs is None else kwargs
101
+ if text.find("%(") == -1:
102
+ return text.format(**kwargs)
103
+ else:
104
+ return text % kwargs
105
+
106
+ def fmt(text : str|Callable, * args, ** kwargs) -> str:
107
+ """
108
+ Basic tool for delayed string formatting.
109
+
110
+ The main use case is that formatting is not executed until this function is called,
111
+ hence potential error messages are not generated until an error actually occurs.
112
+ See, for example, :func:`cdxcore.err.verify`.
113
+
114
+ The follwing example illustrates all four supported modi operandi::
115
+
116
+ from cdxcore.err import fmt
117
+ one = 1
118
+ fmt(lambda : f"one {one:d}) # using a lambda function
119
+ fmt("one {one:d}", one=one) # using python 3 string.format()
120
+ fmt("one %{one}ld", one=one) # using python 2 style
121
+ fmt("one %ld", one) # using c-style
122
+
123
+ As shown, do not use f-strings directly as they are immediately executed in the scope they are typed in
124
+ but wrap them with a ``lambda`` function.
125
+
126
+ Parameters
127
+ ----------
128
+ text : str | Callable
129
+ Error text which may contain one of the following string formatting patterns:
130
+
131
+ * Python 3 ```{parameter:d}```, in which case ``message.fmt(kwargs)`` for :meth:`str.format` is used
132
+ to obtain the output message.
133
+
134
+ * Python 2 ```%(parameter)d``` in which case ``message % kwargs`` is used to obtain the output message.
135
+
136
+ * Classic C-stype ```%d, %s, %f``` in which case ``message % args`` is used to obtain the output message.
137
+
138
+ * If ``message`` is a ``Callable`` such as a ``lambda`` function, then ``message( * args, ** kwargs )``
139
+ is called to obtain the output message.
140
+
141
+ A common use case is using an f-string wrapped in a ``lambda`` function; see example above.
142
+
143
+ * args, ** kwargs:
144
+ See above
145
+
146
+ Returns
147
+ -------
148
+ Text : str
149
+ The formatted message.
150
+ """
151
+ return _fmt(text=text,args=args,kwargs=kwargs,f=fmt)
152
+
153
+ def error( text : str|Callable, *args, exception : Exception = RuntimeError, **kwargs ):
154
+ """
155
+ Raise an exception with string formatting.
156
+
157
+ See also :func:`cdxcore.err.fmt` for formatting comments.
158
+ The point of this function is to have an interface which is consistent with
159
+ :func:`cdxcore.err.verify`.
160
+
161
+ Examples::
162
+
163
+ from cdxcore.err import error
164
+ one = 1
165
+ error(lambda : f"one {one:d}") # wrapped f-string
166
+ error("one {one:d}", one=one) # using python 3 string.format()
167
+ error("one %{one}ld", one=one) # using python 2 style
168
+ error("one %ld", one) # using c-style
169
+
170
+ As shown, do not use f-strings directly as they are immediately executed in the scope they are typed in
171
+ but wrap them with a ``lambda`` function.
172
+
173
+ Parameters
174
+ ----------
175
+ text : str | Callable
176
+ Error text which may contain one of the following string formatting patterns:
177
+
178
+ * Python 3 ```{parameter:d}```, in which case ``message.fmt(kwargs)`` for :meth:`str.format` is used
179
+ to obtain the output message.
180
+
181
+ * Python 2 ```%(parameter)d``` in which case ``message % kwargs`` is used to obtain the output message.
182
+
183
+ * Classic C-stype ```%d, %s, %f``` in which case ``message % args`` is used to obtain the output message.
184
+
185
+ * If ``message`` is a ``Callable`` such as a ``lambda`` function, then ``message( * args, ** kwargs )``
186
+ is called to obtain the output message.
187
+
188
+ A common use case is using an f-string wrapped in a ``lambda`` function; see example above.
189
+
190
+ exception : Exception, optional
191
+ Which type of exception to raise. Defaults to :class:`RuntimeError`.
192
+
193
+ * args, ** kwargs:
194
+ See above
195
+
196
+ Raises
197
+ ------
198
+ exception : exception
199
+ """
200
+ text = _fmt(text=text,args=args,kwargs=kwargs,f=error)
201
+ raise exception( text )
202
+
203
+ def verify( cond : bool, text : str|Callable, *args, exception : Exception = RuntimeError, **kwargs ):
204
+ """
205
+ Raise an exception using delayed error string formatting if a condition is not met.
206
+
207
+ The point of this function is to only format an error message if a condition ``cond`` is not met
208
+ and an error is to be raised.
209
+
210
+ Examples::
211
+
212
+ from cdxcore.err import verify
213
+ one = 1
214
+ good = False # some condition
215
+ verify(good, lambda : f"one {one:d}") # wrapped f-string
216
+ verify(good, "one {one:d}", one=one) # using python 3 string.format()
217
+ verify(good, "one %{one}ld", one=one) # using python 2 style
218
+ verify(good, "one %ld", one) # using c-style
219
+
220
+ As shown, do not use f-strings directly as they are immediately executed in the scope they are typed in
221
+ but wrap them with a ``lambda`` function.
222
+
223
+ Parameters
224
+ ----------
225
+ cond : bool
226
+ Condition to test.
227
+
228
+ text : str | Callable
229
+ Error text which may contain one of the following string formatting patterns:
230
+
231
+ * Python 3 ```{parameter:d}```, in which case ``message.fmt(kwargs)`` for :meth:`str.format` is used
232
+ to obtain the output message.
233
+
234
+ * Python 2 ```%(parameter)d``` in which case ``message % kwargs`` is used to obtain the output message.
235
+
236
+ * Classic C-stype ```%d, %s, %f``` in which case ``message % args`` is used to obtain the output message.
237
+
238
+ * If ``message`` is a ``Callable`` such as a ``lambda`` function, then ``message( * args, ** kwargs )``
239
+ is called to obtain the output message.
240
+
241
+ A common use case is using an f-string wrapped in a ``lambda`` function; see example above.
242
+
243
+ exception : Exception, optiona
244
+ Which type of exception to raise. Defaults to :class:`RuntimeError`.
245
+
246
+ * args, ** kwargs:
247
+ See above
248
+
249
+ Raises
250
+ ------
251
+ exception : exception
252
+ """
253
+ if not cond:
254
+ text = _fmt(text=text,args=args,kwargs=kwargs,f=verify)
255
+ raise exception( fmt(text, * args, ** kwargs) )
256
+
257
+ _warn_skips = (os.path.dirname(__file__),)
258
+
259
+ def warn( text : str|Callable, *args, warning = RuntimeWarning, stack_level : int = 1, **kwargs ):
260
+ """
261
+ Issue a warning.
262
+
263
+ The point of this function is to have an interface consistent with :func:`cdxcore.err.warn_if`.
264
+
265
+ Examples::
266
+
267
+ from cdxcore.err import warn
268
+ one = 1
269
+ warn(lambda : f"one {one:d}") # wrapped f-string
270
+ warn("one {one:d}", one=one) # using python 3 string.format()
271
+ warn("one %{one}ld", one=one) # using python 2 style
272
+ warn("one %ld", one) # using c-style
273
+
274
+ As shown, do not use f-strings directly as they are immediately executed in the scope they are typed in
275
+ but wrap them with a ``lambda`` function.
276
+
277
+ Parameters
278
+ ----------
279
+ text : str | Callable
280
+ Error text which may contain one of the following string formatting patterns:
281
+
282
+ * Python 3 ```{parameter:d}```, in which case ``message.fmt(kwargs)`` for :meth:`str.format` is used
283
+ to obtain the output message.
284
+
285
+ * Python 2 ```%(parameter)d``` in which case ``message % kwargs`` is used to obtain the output message.
286
+
287
+ * Classic C-stype ```%d, %s, %f``` in which case ``message % args`` is used to obtain the output message.
288
+
289
+ * If ``message`` is a ``Callable`` such as a ``lambda`` function, then ``message( * args, ** kwargs )``
290
+ is called to obtain the output message.
291
+
292
+ A common use case is using an f-string wrapped in a ``lambda`` function; see example above.
293
+
294
+ warning : optional
295
+ Which type of warning to issue.
296
+ This corresponds to the ``category`` parameter for :func:`warnings.warn`.
297
+ Default is :class:`RuntimeWarning`.
298
+
299
+ stack_level : int, optional
300
+ What stack to report; see :func:`warnings.warn`.
301
+ Default is 1, which means ``warn`` itself is not reported as part of the stack trace.
302
+
303
+ * args, ** kwargs:
304
+ See above
305
+ """
306
+ warnings.warn( message=text,
307
+ category=warning,
308
+ stacklevel=stack_level,
309
+ skip_file_prefixes=_warn_skips )
310
+
311
+ def warn_if( cond : bool, text : str|Callable, *args, warning = RuntimeWarning, stack_level : int = 1, **kwargs ):
312
+ """
313
+ Issue a warning with delayed string formatting if a condition is met.
314
+
315
+ The point of this function is to only format an error message if a condition ``cond`` is met
316
+ and a warning is to be issued.
317
+
318
+ Examples::
319
+
320
+ from cdxcore.err import warn_if
321
+ one = 1
322
+ bad = True # some conditon
323
+ warn_if(bad,lambda : f"one {one:d}") # wrapped f-string
324
+ warn_if(bad,"one {one:d}", one=one) # using python 3 string.format()
325
+ warn_if(bad,"one %{one}ld", one=one) # using python 2 style
326
+ warn_if(bad,"one %ld", one) # using c-style
327
+
328
+ As shown, do not use f-strings directly as they are immediately executed in the scope they are typed in
329
+ but wrap them with a ``lambda`` function.
330
+
331
+ Parameters
332
+ ----------
333
+ cond : bool
334
+ Condition to test.
335
+
336
+ text : str | Callable
337
+ Error text which may contain one of the following string formatting patterns:
338
+
339
+ * Python 3 ```{parameter:d}```, in which case ``message.fmt(kwargs)`` for :meth:`str.format` is used
340
+ to obtain the output message.
341
+
342
+ * Python 2 ```%(parameter)d``` in which case ``message % kwargs`` is used to obtain the output message.
343
+
344
+ * Classic C-stype ```%d, %s, %f``` in which case ``message % args`` is used to obtain the output message.
345
+
346
+ * If ``message`` is a ``Callable`` such as a ``lambda`` function, then ``message( * args, ** kwargs )``
347
+ is called to obtain the output message.
348
+
349
+ A common use case is using an f-string wrapped in a ``lambda`` function; see example above.
350
+
351
+ warning : optional
352
+ Which type of warning to issue.
353
+ This corresponds to the ``category`` parameter for :func:`warnings.warn`.
354
+ Default is :class:`RuntimeWarning`.
355
+
356
+ stack_level : int, optional
357
+ What stack to report; see :func:`warnings.warn`.
358
+ Default is 1, which means ``warn`` itself is not reported as part of the stack trace.
359
+
360
+ * args, ** kwargs:
361
+ See above
362
+ """
363
+ if cond:
364
+ text = _fmt(text=text,args=args,kwargs=kwargs,f=warn_if)
365
+ warnings.warn( message=text,
366
+ category=warning,
367
+ stacklevel=stack_level,
368
+ skip_file_prefixes=_warn_skips )
369
+
370
+
371
+