anyscale 0.26.9__py3-none-any.whl → 0.26.11__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.
- anyscale/anyscale_halo/LICENSE +21 -0
- anyscale/anyscale_halo/README.md +1 -0
- anyscale/anyscale_halo/__init__.py +10 -0
- anyscale/anyscale_halo/_utils.py +148 -0
- anyscale/anyscale_halo/cursor.py +48 -0
- anyscale/anyscale_halo/halo.py +609 -0
- anyscale/anyscale_halo/halo_notebook.py +122 -0
- anyscale/cli_logger.py +1 -1
- anyscale/client/README.md +0 -35
- anyscale/client/openapi_client/__init__.py +0 -28
- anyscale/client/openapi_client/api/default_api.py +107 -878
- anyscale/client/openapi_client/models/__init__.py +0 -28
- anyscale/version.py +1 -1
- {anyscale-0.26.9.dist-info → anyscale-0.26.11.dist-info}/METADATA +5 -2
- {anyscale-0.26.9.dist-info → anyscale-0.26.11.dist-info}/RECORD +20 -41
- anyscale/client/openapi_client/models/card.py +0 -181
- anyscale/client/openapi_client/models/card_id.py +0 -108
- anyscale/client/openapi_client/models/card_list_response.py +0 -147
- anyscale/client/openapi_client/models/cluster_features.py +0 -152
- anyscale/client/openapi_client/models/clusterfeatures_response.py +0 -121
- anyscale/client/openapi_client/models/dismissal_type.py +0 -100
- anyscale/client/openapi_client/models/feature_compatibility.py +0 -152
- anyscale/client/openapi_client/models/onboarding_user_cards_query.py +0 -122
- anyscale/client/openapi_client/models/project_collaborators_put_message.py +0 -121
- anyscale/client/openapi_client/models/project_create_message.py +0 -148
- anyscale/client/openapi_client/models/project_delete_message.py +0 -121
- anyscale/client/openapi_client/models/project_patch_message.py +0 -121
- anyscale/client/openapi_client/models/session_autosync_sessions_update_message.py +0 -121
- anyscale/client/openapi_client/models/session_create_message.py +0 -148
- anyscale/client/openapi_client/models/session_delete_message.py +0 -121
- anyscale/client/openapi_client/models/session_execute_message.py +0 -121
- anyscale/client/openapi_client/models/session_finish_command_message.py +0 -175
- anyscale/client/openapi_client/models/session_kill_command_message.py +0 -121
- anyscale/client/openapi_client/models/session_patch_message.py +0 -121
- anyscale/client/openapi_client/models/session_state_change_message.py +0 -121
- anyscale/client/openapi_client/models/snapshot_create_message.py +0 -148
- anyscale/client/openapi_client/models/snapshot_delete_message.py +0 -148
- anyscale/client/openapi_client/models/snapshot_patch_message.py +0 -148
- anyscale/client/openapi_client/models/socket_message_schemas.py +0 -499
- anyscale/client/openapi_client/models/socket_message_types.py +0 -113
- anyscale/client/openapi_client/models/socketmessageschemas_response.py +0 -121
- anyscale/client/openapi_client/models/socketmessagetypes_response.py +0 -121
- anyscale/client/openapi_client/models/visibility.py +0 -100
- {anyscale-0.26.9.dist-info → anyscale-0.26.11.dist-info}/LICENSE +0 -0
- {anyscale-0.26.9.dist-info → anyscale-0.26.11.dist-info}/NOTICE +0 -0
- {anyscale-0.26.9.dist-info → anyscale-0.26.11.dist-info}/WHEEL +0 -0
- {anyscale-0.26.9.dist-info → anyscale-0.26.11.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.9.dist-info → anyscale-0.26.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,609 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# pylint: disable=unsubscriptable-object
|
3
|
+
"""Beautiful terminal spinners in Python.
|
4
|
+
"""
|
5
|
+
from __future__ import absolute_import, unicode_literals
|
6
|
+
|
7
|
+
import atexit
|
8
|
+
import functools
|
9
|
+
import sys
|
10
|
+
import threading
|
11
|
+
import time
|
12
|
+
|
13
|
+
import anyscale.anyscale_halo.cursor as cursor
|
14
|
+
|
15
|
+
from log_symbols.symbols import LogSymbols
|
16
|
+
from spinners.spinners import Spinners
|
17
|
+
|
18
|
+
from anyscale.anyscale_halo._utils import (
|
19
|
+
colored_frame,
|
20
|
+
decode_utf_8_text,
|
21
|
+
get_environment,
|
22
|
+
get_terminal_columns,
|
23
|
+
is_supported,
|
24
|
+
is_text_type,
|
25
|
+
encode_utf_8_text,
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
class Halo(object):
|
30
|
+
"""Halo library.
|
31
|
+
Attributes
|
32
|
+
----------
|
33
|
+
CLEAR_LINE : str
|
34
|
+
Code to clear the line
|
35
|
+
"""
|
36
|
+
|
37
|
+
CLEAR_LINE = "\033[K"
|
38
|
+
SPINNER_PLACEMENTS = (
|
39
|
+
"left",
|
40
|
+
"right",
|
41
|
+
)
|
42
|
+
|
43
|
+
def __init__(
|
44
|
+
self,
|
45
|
+
text="",
|
46
|
+
color="cyan",
|
47
|
+
text_color=None,
|
48
|
+
spinner=None,
|
49
|
+
animation=None,
|
50
|
+
placement="left",
|
51
|
+
interval=-1,
|
52
|
+
enabled=True,
|
53
|
+
stream=sys.stdout,
|
54
|
+
):
|
55
|
+
"""Constructs the Halo object.
|
56
|
+
Parameters
|
57
|
+
----------
|
58
|
+
text : str, optional
|
59
|
+
Text to display.
|
60
|
+
text_color : str, optional
|
61
|
+
Color of the text.
|
62
|
+
color : str, optional
|
63
|
+
Color of the text to display.
|
64
|
+
spinner : str|dict, optional
|
65
|
+
String or dictionary representing spinner. String can be one of 60+ spinners
|
66
|
+
supported.
|
67
|
+
animation: str, optional
|
68
|
+
Animation to apply if text is too large. Can be one of `bounce`, `marquee`.
|
69
|
+
Defaults to ellipses.
|
70
|
+
placement: str, optional
|
71
|
+
Side of the text to place the spinner on. Can be `left` or `right`.
|
72
|
+
Defaults to `left`.
|
73
|
+
interval : integer, optional
|
74
|
+
Interval between each frame of the spinner in milliseconds.
|
75
|
+
enabled : boolean, optional
|
76
|
+
Spinner enabled or not.
|
77
|
+
stream : io, optional
|
78
|
+
Output.
|
79
|
+
"""
|
80
|
+
self._color = color
|
81
|
+
self._animation = animation
|
82
|
+
|
83
|
+
self.spinner = spinner
|
84
|
+
self.text = text
|
85
|
+
self._text_color = text_color
|
86
|
+
|
87
|
+
self._interval = (
|
88
|
+
int(interval) if int(interval) > 0 else self._spinner["interval"]
|
89
|
+
)
|
90
|
+
self._stream = stream
|
91
|
+
|
92
|
+
self.placement = placement
|
93
|
+
self._frame_index = 0
|
94
|
+
self._text_index = 0
|
95
|
+
self._spinner_thread = None
|
96
|
+
self._stop_spinner = None
|
97
|
+
self._spinner_id = None
|
98
|
+
self.enabled = enabled
|
99
|
+
|
100
|
+
environment = get_environment()
|
101
|
+
|
102
|
+
def clean_up():
|
103
|
+
"""Handle cell execution"""
|
104
|
+
self.stop()
|
105
|
+
|
106
|
+
if environment in ("ipython", "jupyter"):
|
107
|
+
from IPython import get_ipython
|
108
|
+
|
109
|
+
ip = get_ipython()
|
110
|
+
ip.events.register("post_run_cell", clean_up)
|
111
|
+
else: # default terminal
|
112
|
+
atexit.register(clean_up)
|
113
|
+
|
114
|
+
def __enter__(self):
|
115
|
+
"""Starts the spinner on a separate thread. For use in context managers.
|
116
|
+
Returns
|
117
|
+
-------
|
118
|
+
self
|
119
|
+
"""
|
120
|
+
return self.start()
|
121
|
+
|
122
|
+
def __exit__(self, type, value, traceback):
|
123
|
+
"""Stops the spinner. For use in context managers."""
|
124
|
+
self.stop()
|
125
|
+
|
126
|
+
def __call__(self, f):
|
127
|
+
"""Allow the Halo object to be used as a regular function decorator."""
|
128
|
+
|
129
|
+
@functools.wraps(f)
|
130
|
+
def wrapped(*args, **kwargs):
|
131
|
+
with self:
|
132
|
+
return f(*args, **kwargs)
|
133
|
+
|
134
|
+
return wrapped
|
135
|
+
|
136
|
+
@property
|
137
|
+
def spinner(self):
|
138
|
+
"""Getter for spinner property.
|
139
|
+
Returns
|
140
|
+
-------
|
141
|
+
dict
|
142
|
+
spinner value
|
143
|
+
"""
|
144
|
+
return self._spinner
|
145
|
+
|
146
|
+
@spinner.setter
|
147
|
+
def spinner(self, spinner=None):
|
148
|
+
"""Setter for spinner property.
|
149
|
+
Parameters
|
150
|
+
----------
|
151
|
+
spinner : dict, str
|
152
|
+
Defines the spinner value with frame and interval
|
153
|
+
"""
|
154
|
+
|
155
|
+
self._spinner = self._get_spinner(spinner)
|
156
|
+
self._frame_index = 0
|
157
|
+
self._text_index = 0
|
158
|
+
|
159
|
+
@property
|
160
|
+
def text(self):
|
161
|
+
"""Getter for text property.
|
162
|
+
Returns
|
163
|
+
-------
|
164
|
+
str
|
165
|
+
text value
|
166
|
+
"""
|
167
|
+
return self._text["original"]
|
168
|
+
|
169
|
+
@text.setter
|
170
|
+
def text(self, text):
|
171
|
+
"""Setter for text property.
|
172
|
+
Parameters
|
173
|
+
----------
|
174
|
+
text : str
|
175
|
+
Defines the text value for spinner
|
176
|
+
"""
|
177
|
+
self._text = self._get_text(text)
|
178
|
+
|
179
|
+
@property
|
180
|
+
def text_color(self):
|
181
|
+
"""Getter for text color property.
|
182
|
+
Returns
|
183
|
+
-------
|
184
|
+
str
|
185
|
+
text color value
|
186
|
+
"""
|
187
|
+
return self._text_color
|
188
|
+
|
189
|
+
@text_color.setter
|
190
|
+
def text_color(self, text_color):
|
191
|
+
"""Setter for text color property.
|
192
|
+
Parameters
|
193
|
+
----------
|
194
|
+
text_color : str
|
195
|
+
Defines the text color value for spinner
|
196
|
+
"""
|
197
|
+
self._text_color = text_color
|
198
|
+
|
199
|
+
@property
|
200
|
+
def color(self):
|
201
|
+
"""Getter for color property.
|
202
|
+
Returns
|
203
|
+
-------
|
204
|
+
str
|
205
|
+
color value
|
206
|
+
"""
|
207
|
+
return self._color
|
208
|
+
|
209
|
+
@color.setter
|
210
|
+
def color(self, color):
|
211
|
+
"""Setter for color property.
|
212
|
+
Parameters
|
213
|
+
----------
|
214
|
+
color : str
|
215
|
+
Defines the color value for spinner
|
216
|
+
"""
|
217
|
+
self._color = color
|
218
|
+
|
219
|
+
@property
|
220
|
+
def placement(self):
|
221
|
+
"""Getter for placement property.
|
222
|
+
Returns
|
223
|
+
-------
|
224
|
+
str
|
225
|
+
spinner placement
|
226
|
+
"""
|
227
|
+
return self._placement
|
228
|
+
|
229
|
+
@placement.setter
|
230
|
+
def placement(self, placement):
|
231
|
+
"""Setter for placement property.
|
232
|
+
Parameters
|
233
|
+
----------
|
234
|
+
placement: str
|
235
|
+
Defines the placement of the spinner
|
236
|
+
"""
|
237
|
+
if placement not in self.SPINNER_PLACEMENTS:
|
238
|
+
raise ValueError(
|
239
|
+
"Unknown spinner placement '{0}', available are {1}".format(
|
240
|
+
placement, self.SPINNER_PLACEMENTS
|
241
|
+
)
|
242
|
+
)
|
243
|
+
self._placement = placement
|
244
|
+
|
245
|
+
@property
|
246
|
+
def spinner_id(self):
|
247
|
+
"""Getter for spinner id
|
248
|
+
Returns
|
249
|
+
-------
|
250
|
+
str
|
251
|
+
Spinner id value
|
252
|
+
"""
|
253
|
+
return self._spinner_id
|
254
|
+
|
255
|
+
@property
|
256
|
+
def animation(self):
|
257
|
+
"""Getter for animation property.
|
258
|
+
Returns
|
259
|
+
-------
|
260
|
+
str
|
261
|
+
Spinner animation
|
262
|
+
"""
|
263
|
+
return self._animation
|
264
|
+
|
265
|
+
@animation.setter
|
266
|
+
def animation(self, animation):
|
267
|
+
"""Setter for animation property.
|
268
|
+
Parameters
|
269
|
+
----------
|
270
|
+
animation: str
|
271
|
+
Defines the animation of the spinner
|
272
|
+
"""
|
273
|
+
self._animation = animation
|
274
|
+
self._text = self._get_text(self._text["original"])
|
275
|
+
|
276
|
+
def _check_stream(self):
|
277
|
+
"""Returns whether the stream is open, and if applicable, writable
|
278
|
+
Returns
|
279
|
+
-------
|
280
|
+
bool
|
281
|
+
Whether the stream is open
|
282
|
+
"""
|
283
|
+
if self._stream.closed:
|
284
|
+
return False
|
285
|
+
|
286
|
+
try:
|
287
|
+
# Attribute access kept separate from invocation, to avoid
|
288
|
+
# swallowing AttributeErrors from the call which should bubble up.
|
289
|
+
check_stream_writable = self._stream.writable
|
290
|
+
except AttributeError:
|
291
|
+
pass
|
292
|
+
else:
|
293
|
+
return check_stream_writable()
|
294
|
+
|
295
|
+
return True
|
296
|
+
|
297
|
+
def _write(self, s):
|
298
|
+
"""Write to the stream, if writable
|
299
|
+
Parameters
|
300
|
+
----------
|
301
|
+
s : str
|
302
|
+
Characters to write to the stream
|
303
|
+
"""
|
304
|
+
if self._check_stream():
|
305
|
+
self._stream.write(s)
|
306
|
+
|
307
|
+
def _hide_cursor(self):
|
308
|
+
"""Disable the user's blinking cursor
|
309
|
+
"""
|
310
|
+
if self._check_stream() and self._stream.isatty():
|
311
|
+
cursor.hide(stream=self._stream)
|
312
|
+
|
313
|
+
def _show_cursor(self):
|
314
|
+
"""Re-enable the user's blinking cursor
|
315
|
+
"""
|
316
|
+
if self._check_stream() and self._stream.isatty():
|
317
|
+
cursor.show(stream=self._stream)
|
318
|
+
|
319
|
+
def _get_spinner(self, spinner):
|
320
|
+
"""Extracts spinner value from options and returns value
|
321
|
+
containing spinner frames and interval, defaults to 'dots' spinner.
|
322
|
+
Parameters
|
323
|
+
----------
|
324
|
+
spinner : dict, str
|
325
|
+
Contains spinner value or type of spinner to be used
|
326
|
+
Returns
|
327
|
+
-------
|
328
|
+
dict
|
329
|
+
Contains frames and interval defining spinner
|
330
|
+
"""
|
331
|
+
default_spinner = Spinners["dots"].value
|
332
|
+
|
333
|
+
if spinner and type(spinner) == dict:
|
334
|
+
return spinner
|
335
|
+
|
336
|
+
if is_supported():
|
337
|
+
if all([is_text_type(spinner), spinner in Spinners.__members__]):
|
338
|
+
return Spinners[spinner].value
|
339
|
+
else:
|
340
|
+
return default_spinner
|
341
|
+
else:
|
342
|
+
return Spinners["line"].value
|
343
|
+
|
344
|
+
def _get_text(self, text):
|
345
|
+
"""Creates frames based on the selected animation
|
346
|
+
Returns
|
347
|
+
-------
|
348
|
+
self
|
349
|
+
"""
|
350
|
+
animation = self._animation
|
351
|
+
stripped_text = text.strip()
|
352
|
+
|
353
|
+
# Check which frame of the animation is the widest
|
354
|
+
max_spinner_length = max([len(i) for i in self._spinner["frames"]])
|
355
|
+
|
356
|
+
# Subtract to the current terminal size the max spinner length
|
357
|
+
# (-1 to leave room for the extra space between spinner and text)
|
358
|
+
terminal_width = get_terminal_columns() - max_spinner_length - 1
|
359
|
+
text_length = len(stripped_text)
|
360
|
+
|
361
|
+
frames = []
|
362
|
+
|
363
|
+
if terminal_width < text_length and animation:
|
364
|
+
if animation == "bounce":
|
365
|
+
"""
|
366
|
+
Make the text bounce back and forth
|
367
|
+
"""
|
368
|
+
for x in range(0, text_length - terminal_width + 1):
|
369
|
+
frames.append(stripped_text[x : terminal_width + x])
|
370
|
+
frames.extend(list(reversed(frames)))
|
371
|
+
elif "marquee":
|
372
|
+
"""
|
373
|
+
Make the text scroll like a marquee
|
374
|
+
"""
|
375
|
+
stripped_text = stripped_text + " " + stripped_text[:terminal_width]
|
376
|
+
for x in range(0, text_length + 1):
|
377
|
+
frames.append(stripped_text[x : terminal_width + x])
|
378
|
+
elif terminal_width < text_length and not animation:
|
379
|
+
# Add ellipsis if text is larger than terminal width and no animation was specified
|
380
|
+
frames = [stripped_text[: terminal_width - 6] + " (...)"]
|
381
|
+
else:
|
382
|
+
frames = [stripped_text]
|
383
|
+
|
384
|
+
return {"original": text, "frames": frames}
|
385
|
+
|
386
|
+
def clear(self):
|
387
|
+
"""Clears the line and returns cursor to the start.
|
388
|
+
of line
|
389
|
+
Returns
|
390
|
+
-------
|
391
|
+
self
|
392
|
+
"""
|
393
|
+
self._write("\r")
|
394
|
+
self._write(self.CLEAR_LINE)
|
395
|
+
return self
|
396
|
+
|
397
|
+
def _render_frame(self):
|
398
|
+
"""Renders the frame on the line after clearing it.
|
399
|
+
"""
|
400
|
+
if not self.enabled:
|
401
|
+
# in case we're disabled or stream is closed while still rendering,
|
402
|
+
# we render the frame and increment the frame index, so the proper
|
403
|
+
# frame is rendered if we're reenabled or the stream opens again.
|
404
|
+
return
|
405
|
+
|
406
|
+
self.clear()
|
407
|
+
frame = self.frame()
|
408
|
+
output = "\r{}".format(frame)
|
409
|
+
try:
|
410
|
+
self._write(output)
|
411
|
+
except UnicodeEncodeError:
|
412
|
+
self._write(encode_utf_8_text(output))
|
413
|
+
|
414
|
+
def render(self):
|
415
|
+
"""Runs the render until thread flag is set.
|
416
|
+
Returns
|
417
|
+
-------
|
418
|
+
self
|
419
|
+
"""
|
420
|
+
while not self._stop_spinner.is_set():
|
421
|
+
self._render_frame()
|
422
|
+
time.sleep(0.001 * self._interval)
|
423
|
+
|
424
|
+
return self
|
425
|
+
|
426
|
+
def frame(self):
|
427
|
+
"""Builds and returns the frame to be rendered
|
428
|
+
Returns
|
429
|
+
-------
|
430
|
+
self
|
431
|
+
"""
|
432
|
+
frames = self._spinner["frames"]
|
433
|
+
frame = frames[self._frame_index]
|
434
|
+
|
435
|
+
if self._color:
|
436
|
+
frame = colored_frame(frame, self._color)
|
437
|
+
|
438
|
+
self._frame_index += 1
|
439
|
+
self._frame_index = self._frame_index % len(frames)
|
440
|
+
|
441
|
+
text_frame = self.text_frame()
|
442
|
+
return "{0} {1}".format(
|
443
|
+
*[
|
444
|
+
(text_frame, frame)
|
445
|
+
if self._placement == "right"
|
446
|
+
else (frame, text_frame)
|
447
|
+
][0]
|
448
|
+
)
|
449
|
+
|
450
|
+
def text_frame(self):
|
451
|
+
"""Builds and returns the text frame to be rendered
|
452
|
+
Returns
|
453
|
+
-------
|
454
|
+
self
|
455
|
+
"""
|
456
|
+
if len(self._text["frames"]) == 1:
|
457
|
+
if self._text_color:
|
458
|
+
return colored_frame(self._text["frames"][0], self._text_color)
|
459
|
+
|
460
|
+
# Return first frame (can't return original text because at this point it might be ellipsed)
|
461
|
+
return self._text["frames"][0]
|
462
|
+
|
463
|
+
frames = self._text["frames"]
|
464
|
+
frame = frames[self._text_index]
|
465
|
+
|
466
|
+
self._text_index += 1
|
467
|
+
self._text_index = self._text_index % len(frames)
|
468
|
+
|
469
|
+
if self._text_color:
|
470
|
+
return colored_frame(frame, self._text_color)
|
471
|
+
|
472
|
+
return frame
|
473
|
+
|
474
|
+
def start(self, text=None):
|
475
|
+
"""Starts the spinner on a separate thread.
|
476
|
+
Parameters
|
477
|
+
----------
|
478
|
+
text : None, optional
|
479
|
+
Text to be used alongside spinner
|
480
|
+
Returns
|
481
|
+
-------
|
482
|
+
self
|
483
|
+
"""
|
484
|
+
if text is not None:
|
485
|
+
self.text = text
|
486
|
+
|
487
|
+
if self._spinner_id is not None:
|
488
|
+
return self
|
489
|
+
|
490
|
+
if not (self.enabled and self._check_stream()):
|
491
|
+
return self
|
492
|
+
|
493
|
+
self._hide_cursor()
|
494
|
+
|
495
|
+
self._stop_spinner = threading.Event()
|
496
|
+
self._spinner_thread = threading.Thread(target=self.render)
|
497
|
+
self._spinner_thread.setDaemon(True)
|
498
|
+
self._render_frame()
|
499
|
+
self._spinner_id = self._spinner_thread.name
|
500
|
+
self._spinner_thread.start()
|
501
|
+
|
502
|
+
return self
|
503
|
+
|
504
|
+
def stop(self):
|
505
|
+
"""Stops the spinner and clears the line.
|
506
|
+
Returns
|
507
|
+
-------
|
508
|
+
self
|
509
|
+
"""
|
510
|
+
if self._spinner_thread and self._spinner_thread.is_alive():
|
511
|
+
self._stop_spinner.set()
|
512
|
+
self._spinner_thread.join()
|
513
|
+
|
514
|
+
if self.enabled:
|
515
|
+
self.clear()
|
516
|
+
|
517
|
+
self._frame_index = 0
|
518
|
+
self._spinner_id = None
|
519
|
+
self._show_cursor()
|
520
|
+
return self
|
521
|
+
|
522
|
+
def succeed(self, text=None):
|
523
|
+
"""Shows and persists success symbol and text and exits.
|
524
|
+
Parameters
|
525
|
+
----------
|
526
|
+
text : None, optional
|
527
|
+
Text to be shown alongside success symbol.
|
528
|
+
Returns
|
529
|
+
-------
|
530
|
+
self
|
531
|
+
"""
|
532
|
+
return self.stop_and_persist(symbol=LogSymbols.SUCCESS.value, text=text)
|
533
|
+
|
534
|
+
def fail(self, text=None):
|
535
|
+
"""Shows and persists fail symbol and text and exits.
|
536
|
+
Parameters
|
537
|
+
----------
|
538
|
+
text : None, optional
|
539
|
+
Text to be shown alongside fail symbol.
|
540
|
+
Returns
|
541
|
+
-------
|
542
|
+
self
|
543
|
+
"""
|
544
|
+
return self.stop_and_persist(symbol=LogSymbols.ERROR.value, text=text)
|
545
|
+
|
546
|
+
def warn(self, text=None):
|
547
|
+
"""Shows and persists warn symbol and text and exits.
|
548
|
+
Parameters
|
549
|
+
----------
|
550
|
+
text : None, optional
|
551
|
+
Text to be shown alongside warn symbol.
|
552
|
+
Returns
|
553
|
+
-------
|
554
|
+
self
|
555
|
+
"""
|
556
|
+
return self.stop_and_persist(symbol=LogSymbols.WARNING.value, text=text)
|
557
|
+
|
558
|
+
def info(self, text=None):
|
559
|
+
"""Shows and persists info symbol and text and exits.
|
560
|
+
Parameters
|
561
|
+
----------
|
562
|
+
text : None, optional
|
563
|
+
Text to be shown alongside info symbol.
|
564
|
+
Returns
|
565
|
+
-------
|
566
|
+
self
|
567
|
+
"""
|
568
|
+
return self.stop_and_persist(symbol=LogSymbols.INFO.value, text=text)
|
569
|
+
|
570
|
+
def stop_and_persist(self, symbol=" ", text=None):
|
571
|
+
"""Stops the spinner and persists the final frame to be shown.
|
572
|
+
Parameters
|
573
|
+
----------
|
574
|
+
symbol : str, optional
|
575
|
+
Symbol to be shown in final frame
|
576
|
+
text: str, optional
|
577
|
+
Text to be shown in final frame
|
578
|
+
|
579
|
+
Returns
|
580
|
+
-------
|
581
|
+
self
|
582
|
+
"""
|
583
|
+
if not self.enabled:
|
584
|
+
return self
|
585
|
+
|
586
|
+
symbol = decode_utf_8_text(symbol)
|
587
|
+
|
588
|
+
if text is not None:
|
589
|
+
text = decode_utf_8_text(text)
|
590
|
+
else:
|
591
|
+
text = self._text["original"]
|
592
|
+
|
593
|
+
text = text.strip()
|
594
|
+
|
595
|
+
if self._text_color:
|
596
|
+
text = colored_frame(text, self._text_color)
|
597
|
+
|
598
|
+
self.stop()
|
599
|
+
|
600
|
+
output = "{0} {1}\n".format(
|
601
|
+
*[(text, symbol) if self._placement == "right" else (symbol, text)][0]
|
602
|
+
)
|
603
|
+
|
604
|
+
try:
|
605
|
+
self._write(output)
|
606
|
+
except UnicodeEncodeError:
|
607
|
+
self._write(encode_utf_8_text(output))
|
608
|
+
|
609
|
+
return self
|