anyscale 0.26.9__py3-none-any.whl → 0.26.10__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.
Files changed (29) hide show
  1. anyscale/anyscale_halo/LICENSE +21 -0
  2. anyscale/anyscale_halo/README.md +1 -0
  3. anyscale/anyscale_halo/__init__.py +10 -0
  4. anyscale/anyscale_halo/_utils.py +148 -0
  5. anyscale/anyscale_halo/cursor.py +48 -0
  6. anyscale/anyscale_halo/halo.py +609 -0
  7. anyscale/anyscale_halo/halo_notebook.py +122 -0
  8. anyscale/cli_logger.py +1 -1
  9. anyscale/client/README.md +0 -14
  10. anyscale/client/openapi_client/__init__.py +0 -9
  11. anyscale/client/openapi_client/api/default_api.py +12 -577
  12. anyscale/client/openapi_client/models/__init__.py +0 -9
  13. anyscale/version.py +1 -1
  14. {anyscale-0.26.9.dist-info → anyscale-0.26.10.dist-info}/METADATA +5 -2
  15. {anyscale-0.26.9.dist-info → anyscale-0.26.10.dist-info}/RECORD +20 -22
  16. anyscale/client/openapi_client/models/card.py +0 -181
  17. anyscale/client/openapi_client/models/card_id.py +0 -108
  18. anyscale/client/openapi_client/models/card_list_response.py +0 -147
  19. anyscale/client/openapi_client/models/cluster_features.py +0 -152
  20. anyscale/client/openapi_client/models/clusterfeatures_response.py +0 -121
  21. anyscale/client/openapi_client/models/dismissal_type.py +0 -100
  22. anyscale/client/openapi_client/models/feature_compatibility.py +0 -152
  23. anyscale/client/openapi_client/models/onboarding_user_cards_query.py +0 -122
  24. anyscale/client/openapi_client/models/visibility.py +0 -100
  25. {anyscale-0.26.9.dist-info → anyscale-0.26.10.dist-info}/LICENSE +0 -0
  26. {anyscale-0.26.9.dist-info → anyscale-0.26.10.dist-info}/NOTICE +0 -0
  27. {anyscale-0.26.9.dist-info → anyscale-0.26.10.dist-info}/WHEEL +0 -0
  28. {anyscale-0.26.9.dist-info → anyscale-0.26.10.dist-info}/entry_points.txt +0 -0
  29. {anyscale-0.26.9.dist-info → anyscale-0.26.10.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