Skip to content

Commit

Permalink
docs: add docstrings to some methods
Browse files Browse the repository at this point in the history
  • Loading branch information
pavdmyt committed Oct 19, 2024
1 parent 112b337 commit 1c56ff6
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 5 deletions.
11 changes: 11 additions & 0 deletions yaspin/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ def foo():


def kbi_safe_yaspin(*args: Any, **kwargs: Any) -> Yaspin:
"""
Create a Yaspin instance with a default signal handler for SIGINT.
Wraps the Yaspin initialization to ensure that a default
signal handler for SIGINT is set, which allows for safe interruption
(KeyboardInterrupt) handling.
Returns:
Yaspin: An instance of the Yaspin spinner with the specified arguments and
a default SIGINT handler.
"""
kwargs["sigmap"] = {signal.SIGINT: default_handler}
return Yaspin(*args, **kwargs)

Expand Down
150 changes: 145 additions & 5 deletions yaspin/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,18 @@ def elapsed_time(self) -> float:
# Public
#
def start(self) -> None:
"""
Start the spinner animation in a separate thread.
Initializes and starts the spinner animation by hiding the cursor, recording
the start time, and creating a new thread to run the spinner. It also
sets up necessary threading events to control the spinner's behavior.
If signal handlers are registered, they will be set up before starting the spinner.
In case of any failure that prevents the spinner from starting, the cursor will
be shown to ensure it is not left hidden.
"""
if self._sigmap:
self._register_signal_handlers()

Expand All @@ -313,6 +325,16 @@ def start(self) -> None:
self._show_cursor()

def stop(self) -> None:
"""
Stops the spinner and performs necessary cleanup.
Records the stop time, resets signal handlers to their default if
they were modified, stops the spinning thread, clears the spinner
line, and shows the cursor.
Raises:
RuntimeError: If the stop_spin event is None.
"""
self._stop_time = time.time()

if self._dfl_sigmap:
Expand All @@ -329,7 +351,17 @@ def stop(self) -> None:
self._show_cursor()

def hide(self) -> None:
"""Hide the spinner to allow for custom writing to the terminal."""
"""
Hide the spinner to allow for custom writing to the terminal.
Sets a flag to indicate that the spinner should be hidden, clears
the current line in the terminal, and flushes the stdout buffer.
It ensures that the spinner thread is alive and the hide flag is
not already set before performing these actions.
Raises:
RuntimeError: If the hide_spin attribute is None.
"""
thr_is_alive = self._spin_thread and self._spin_thread.is_alive()
if self._hide_spin is None:
raise RuntimeError("hide_spin is None")
Expand All @@ -346,7 +378,15 @@ def hide(self) -> None:

@contextmanager
def hidden(self) -> Generator[None, None, None]:
"""Hide the spinner within a block, can be nested"""
"""
Temporarily hides the spinner within a context block. This method can be nested.
When the context is entered, the spinner is hidden if it is not already hidden.
When the context is exited, the spinner is shown again if it was hidden by this method.
Yields:
None: This method is a generator that yields control back to the caller.
"""
if self._hidden_level == 0:
self.hide()
self._hidden_level += 1
Expand All @@ -358,7 +398,17 @@ def hidden(self) -> Generator[None, None, None]:
self.show()

def show(self) -> None:
"""Show the hidden spinner."""
"""
Show the hidden spinner.
Checks if the spinner thread is alive and if the spinner is currently
hidden. If both conditions are met, it clears the hidden spinner
flag and clears the current line to ensure the spinner is not
appended to it.
Raises:
RuntimeError: If the `_hide_spin` attribute is `None`.
"""
thr_is_alive = self._spin_thread and self._spin_thread.is_alive()
if self._hide_spin is None:
raise RuntimeError("hide_spin is None")
Expand All @@ -371,7 +421,15 @@ def show(self) -> None:
self._clear_line()

def write(self, text: str) -> None:
"""Write text in the terminal without breaking the spinner."""
"""
Write text in the terminal without breaking the spinner.
Ensures that the spinner is temporarily cleared, the text
is written to the terminal, and then the spinner is restored.
Args:
text (str): The text to be written to the terminal.
"""
# similar to tqdm.write()
# https://pypi.python.org/pypi/tqdm#writing-messages
with self._stdout_lock:
Expand Down Expand Up @@ -403,7 +461,18 @@ def _warn_color_disabled() -> None:
)

def _freeze(self, final_text: str) -> None:
"""Stop spinner, compose last frame and 'freeze' it."""
"""
Stop the spinner and display the final frame.
Stops the spinner, composes the last frame with the provided final text,
and 'freezes' it by writing the final frame to the standard output.
Args:
final_text (str): The final text to be displayed when the spinner stops.
Raises:
RuntimeError: If the last frame is None.
"""
text = to_unicode(final_text)
self._last_frame = self._compose_out(text, mode="last")

Expand All @@ -417,6 +486,16 @@ def _freeze(self, final_text: str) -> None:
self._cur_line_len = 0

def _spin(self) -> None:
"""
Handles the spinning animation.
Continuously updates the spinner's output on the terminal until
the `_stop_spin` event is set. If the `_hide_spin` event is set,
it temporarily pauses the spinning.
Raises:
RuntimeError: If `_stop_spin` is None.
"""
if self._stop_spin is None:
raise RuntimeError("stop_spin is None")

Expand All @@ -441,6 +520,13 @@ def _spin(self) -> None:
self._stop_spin.wait(self._interval)

def _compose_color_func(self) -> Optional[Callable[..., str]]:
"""
Compose a color function based on the current environment.
If the environment is Jupyter, returns None as ANSI color control sequences
are problematic in Jupyter notebooks. Otherwise, returns a partial function
that applies the specified color, background color, and attributes to text.
"""
if self.is_jupyter():
# ANSI Color Control Sequences are problematic in Jupyter
return None
Expand All @@ -453,6 +539,24 @@ def _compose_color_func(self) -> Optional[Callable[..., str]]:
)

def _compose_out(self, frame: str, mode: Optional[str] = None) -> str:
"""
Compose the output string for the spinner.
Args:
frame (str): The current frame of the spinner animation.
mode (str, optional): The mode in which the output is generated. If None,
the output is generated on the same line with a carriage
return. If a value is provided, the output is generated
on a new line.
Returns:
str: The composed output string including the spinner frame, text, timer,
and any specified colors and positions.
Raises:
ValueError: If the terminal size is too small to display the spinner with
the given settings.
"""
text = str(self._text)

# Timer
Expand Down Expand Up @@ -489,13 +593,34 @@ def _compose_out(self, frame: str, mode: Optional[str] = None) -> str:
return out

def _get_max_text_length(self, frame_width: int, timer_width: int) -> int:
"""
Calculate the maximum length of text that can be displayed within the terminal width.
Args:
frame_width (int): The width of the frame.
timer_width (int): The width of the timer.
Returns:
int: The maximum length of text that can be displayed.
"""
ellipsis_width = len(self._ellipsis)
# There is always a space between frame and text
frame_width += 1

return self._terminal_width - frame_width - timer_width - ellipsis_width

def _register_signal_handlers(self) -> None:
"""
Registers custom signal handlers for the spinner.
Sets up signal handlers defined in the `_sigmap` attribute.
It ensures that SIGKILL is not included. For each signal in
`_sigmap`, stores the default signal handler for later restoration
during the cleanup phase.
Raises:
ValueError: If an attempt is made to set a handler for the SIGKILL signal.
"""
# SIGKILL cannot be caught or ignored, and the receiving
# process cannot perform any clean-up upon receiving this
# signal.
Expand Down Expand Up @@ -524,6 +649,7 @@ def _register_signal_handlers(self) -> None:
signal.signal(sig, sig_handler)

def _reset_signal_handlers(self) -> None:
"""Resets the signal handlers to their default values."""
for sig, sig_handler in self._dfl_sigmap.items():
signal.signal(sig, sig_handler)

Expand Down Expand Up @@ -591,6 +717,20 @@ def _set_side(side: str) -> str:

@staticmethod
def _set_frames(spinner: Spinner, reversal: bool) -> Union[str, Sequence[str]]:
"""
Set the frames for the spinner, optionally reversing them.
Args:
spinner (Spinner): The spinner object containing the frames.
reversal (bool): If True, the frames will be reversed.
Returns:
Union[str, Sequence[str]]: The frames to be used for the spinner.
This can be a single string of frames or a sequence of frame strings.
Raises:
ValueError: If no frames are found in the spinner.
"""
uframes = None # unicode frames
uframes_seq = None # sequence of unicode frames

Expand Down

0 comments on commit 1c56ff6

Please sign in to comment.