spinner.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. from typing import cast, List, Optional, TYPE_CHECKING
  2. from ._spinners import SPINNERS
  3. from .measure import Measurement
  4. from .table import Table
  5. from .text import Text
  6. if TYPE_CHECKING:
  7. from .console import Console, ConsoleOptions, RenderResult, RenderableType
  8. from .style import StyleType
  9. class Spinner:
  10. def __init__(
  11. self,
  12. name: str,
  13. text: "RenderableType" = "",
  14. *,
  15. style: Optional["StyleType"] = None,
  16. speed: float = 1.0,
  17. ) -> None:
  18. """A spinner animation.
  19. Args:
  20. name (str): Name of spinner (run python -m rich.spinner).
  21. text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
  22. style (StyleType, optional): Style for spinner animation. Defaults to None.
  23. speed (float, optional): Speed factor for animation. Defaults to 1.0.
  24. Raises:
  25. KeyError: If name isn't one of the supported spinner animations.
  26. """
  27. try:
  28. spinner = SPINNERS[name]
  29. except KeyError:
  30. raise KeyError(f"no spinner called {name!r}")
  31. self.text = Text.from_markup(text) if isinstance(text, str) else text
  32. self.frames = cast(List[str], spinner["frames"])[:]
  33. self.interval = cast(float, spinner["interval"])
  34. self.start_time: Optional[float] = None
  35. self.style = style
  36. self.speed = speed
  37. self.frame_no_offset: float = 0.0
  38. self._update_speed = 0.0
  39. def __rich_console__(
  40. self, console: "Console", options: "ConsoleOptions"
  41. ) -> "RenderResult":
  42. yield self.render(console.get_time())
  43. def __rich_measure__(
  44. self, console: "Console", options: "ConsoleOptions"
  45. ) -> Measurement:
  46. text = self.render(0)
  47. return Measurement.get(console, options, text)
  48. def render(self, time: float) -> "RenderableType":
  49. """Render the spinner for a given time.
  50. Args:
  51. time (float): Time in seconds.
  52. Returns:
  53. RenderableType: A renderable containing animation frame.
  54. """
  55. if self.start_time is None:
  56. self.start_time = time
  57. frame_no = ((time - self.start_time) * self.speed) / (
  58. self.interval / 1000.0
  59. ) + self.frame_no_offset
  60. frame = Text(
  61. self.frames[int(frame_no) % len(self.frames)], style=self.style or ""
  62. )
  63. if self._update_speed:
  64. self.frame_no_offset = frame_no
  65. self.start_time = time
  66. self.speed = self._update_speed
  67. self._update_speed = 0.0
  68. if not self.text:
  69. return frame
  70. elif isinstance(self.text, (str, Text)):
  71. return Text.assemble(frame, " ", self.text)
  72. else:
  73. table = Table.grid(padding=1)
  74. table.add_row(frame, self.text)
  75. return table
  76. def update(
  77. self,
  78. *,
  79. text: "RenderableType" = "",
  80. style: Optional["StyleType"] = None,
  81. speed: Optional[float] = None,
  82. ) -> None:
  83. """Updates attributes of a spinner after it has been started.
  84. Args:
  85. text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
  86. style (StyleType, optional): Style for spinner animation. Defaults to None.
  87. speed (float, optional): Speed factor for animation. Defaults to None.
  88. """
  89. if text:
  90. self.text = Text.from_markup(text) if isinstance(text, str) else text
  91. if style:
  92. self.style = style
  93. if speed:
  94. self._update_speed = speed
  95. if __name__ == "__main__": # pragma: no cover
  96. from time import sleep
  97. from .columns import Columns
  98. from .panel import Panel
  99. from .live import Live
  100. all_spinners = Columns(
  101. [
  102. Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
  103. for spinner_name in sorted(SPINNERS.keys())
  104. ],
  105. column_first=True,
  106. expand=True,
  107. )
  108. with Live(
  109. Panel(all_spinners, title="Spinners", border_style="blue"),
  110. refresh_per_second=20,
  111. ) as live:
  112. while True:
  113. sleep(0.1)