From fa54880ca62e4d4d5fda645456e2e60fbe380068 Mon Sep 17 00:00:00 2001 From: Caleb Aguirre-Leon Date: Thu, 5 Jun 2025 16:40:35 +0100 Subject: [PATCH] Implementing Typing for Alive_Progress By implementing explicit typing to modules used in Mission Blue, we will have an easier time debugging and refactoring code. --- typings/alive_progress/__init__.pyi | 13 + .../alive_progress/animations/__init__.pyi | 9 + typings/alive_progress/animations/bars.pyi | 69 +++++ .../animations/spinner_compiler.pyi | 150 +++++++++++ .../alive_progress/animations/spinners.pyi | 127 ++++++++++ typings/alive_progress/animations/utils.pyi | 58 +++++ typings/alive_progress/core/calibration.pyi | 30 +++ typings/alive_progress/core/configuration.pyi | 14 ++ typings/alive_progress/core/hook_manager.pyi | 40 +++ typings/alive_progress/core/progress.pyi | 238 ++++++++++++++++++ typings/alive_progress/styles/exhibit.pyi | 58 +++++ typings/alive_progress/styles/internal.pyi | 7 + typings/alive_progress/utils/__init__.pyi | 10 + typings/alive_progress/utils/cells.pyi | 142 +++++++++++ typings/alive_progress/utils/colors.pyi | 36 +++ .../utils/terminal/__init__.pyi | 16 ++ .../alive_progress/utils/terminal/jupyter.pyi | 7 + .../alive_progress/utils/terminal/non_tty.pyi | 7 + typings/alive_progress/utils/terminal/tty.pyi | 7 + .../alive_progress/utils/terminal/void.pyi | 22 ++ typings/alive_progress/utils/timing.pyi | 41 +++ 21 files changed, 1101 insertions(+) create mode 100644 typings/alive_progress/__init__.pyi create mode 100644 typings/alive_progress/animations/__init__.pyi create mode 100644 typings/alive_progress/animations/bars.pyi create mode 100644 typings/alive_progress/animations/spinner_compiler.pyi create mode 100644 typings/alive_progress/animations/spinners.pyi create mode 100644 typings/alive_progress/animations/utils.pyi create mode 100644 typings/alive_progress/core/calibration.pyi create mode 100644 typings/alive_progress/core/configuration.pyi create mode 100644 typings/alive_progress/core/hook_manager.pyi create mode 100644 typings/alive_progress/core/progress.pyi create mode 100644 typings/alive_progress/styles/exhibit.pyi create mode 100644 typings/alive_progress/styles/internal.pyi create mode 100644 typings/alive_progress/utils/__init__.pyi create mode 100644 typings/alive_progress/utils/cells.pyi create mode 100644 typings/alive_progress/utils/colors.pyi create mode 100644 typings/alive_progress/utils/terminal/__init__.pyi create mode 100644 typings/alive_progress/utils/terminal/jupyter.pyi create mode 100644 typings/alive_progress/utils/terminal/non_tty.pyi create mode 100644 typings/alive_progress/utils/terminal/tty.pyi create mode 100644 typings/alive_progress/utils/terminal/void.pyi create mode 100644 typings/alive_progress/utils/timing.pyi diff --git a/typings/alive_progress/__init__.pyi b/typings/alive_progress/__init__.pyi new file mode 100644 index 0000000..7a79a1d --- /dev/null +++ b/typings/alive_progress/__init__.pyi @@ -0,0 +1,13 @@ +""" +This type stub file was generated by pyright. +""" + +from .core.configuration import config_handler +from .core.progress import alive_bar, alive_it + +VERSION = ... +__author__ = ... +__email__ = ... +__version__ = ... +__description__ = ... +__all__ = ('alive_bar', 'alive_it', 'config_handler') diff --git a/typings/alive_progress/animations/__init__.pyi b/typings/alive_progress/animations/__init__.pyi new file mode 100644 index 0000000..3b24b64 --- /dev/null +++ b/typings/alive_progress/animations/__init__.pyi @@ -0,0 +1,9 @@ +""" +This type stub file was generated by pyright. +""" + +from .bars import bar_factory +from .spinners import alongside_spinner_factory, bouncing_spinner_factory, delayed_spinner_factory, frame_spinner_factory, scrolling_spinner_factory, sequential_spinner_factory +from .utils import spinner_player + +__all__ = ('bar_factory', 'spinner_player', 'frame_spinner_factory', 'scrolling_spinner_factory', 'bouncing_spinner_factory', 'sequential_spinner_factory', 'alongside_spinner_factory', 'delayed_spinner_factory') diff --git a/typings/alive_progress/animations/bars.pyi b/typings/alive_progress/animations/bars.pyi new file mode 100644 index 0000000..1b6bf7c --- /dev/null +++ b/typings/alive_progress/animations/bars.pyi @@ -0,0 +1,69 @@ +""" +This type stub file was generated by pyright. +""" + +from ..utils.colors import ORANGE_BOLD + +def bar_factory(chars=..., *, tip=..., background=..., borders=..., errors=...): # -> Callable[..., tuple[Callable[..., tuple[Any | tuple[()] | tuple[Any | Literal[' '], ...], tuple[Any, *tuple[None, ...]] | tuple[()] | Any | None]], Callable[..., tuple[None, tuple[Any | tuple[Any | Literal[' '], ...]]]], Callable[..., tuple[tuple[Any, *tuple[None, ...]] | tuple[()] | None, tuple[*tuple[tuple[Any, *tuple[None, ...]] | tuple[()], ...], Any]]], Callable[..., tuple[Any, None]]]]: + """Create a factory of a bar with the given styling parameters. + Supports unicode grapheme clusters and emoji chars (those that has length one but when on + screen occupies two cells). + + Now supports transparent fills! Just send a tip, and leave `chars` as None. + Also tips are now considered for the 100%, which means it smoothly enters and exits the + frame to get to 100%!! The effect is super cool, use a multi-char tip to see. + + Args: + chars (Optional[str]): the sequence of increasing glyphs to fill the bar + can be None for a transparent fill, unless tip is also None. + tip (Optional[str): the tip in front of the bar + can be None, unless chars is also None. + background (Optional[str]): the pattern to be used underneath the bar + borders (Optional[Union[str, Tuple[str, str]]): the pattern or patterns to be used + before and after the bar + errors (Optional[Union[str, Tuple[str, str]]): the pattern or patterns to be used + when an underflow or overflow occurs + + Returns: + a styled bar factory + + """ + ... + +def bar_controller(inner_bar_factory): # -> Callable[..., Callable[..., Any]]: + ... + +def check(bar, t_compile, verbosity=..., *, steps=...): # -> None: + """Check the data, codepoints, and even the animation of this bar. + + Args: + verbosity (int): change the verbosity level + 0 for brief data only (default) + / \\ + / 3 to include animation + / \\ + 1 to unfold bar data ---------- 4 to unfold bar data + | | + 2 to reveal codepoints -------- 5 to reveal codepoints + steps (int): number of steps to display the bar progress + + """ + ... + +SECTION = ORANGE_BOLD +HELP_MSG = ... +def spec_data(bar): # -> None: + ... + +def format_codepoints(frame): # -> str: + ... + +def render_data(bar, show_codepoints, steps): # -> None: + ... + +def bar_repr(bar, p): # -> tuple[tuple[Any, ...], LiteralString, str]: + ... + +def animate(bar): # -> None: + ... + diff --git a/typings/alive_progress/animations/spinner_compiler.pyi b/typings/alive_progress/animations/spinner_compiler.pyi new file mode 100644 index 0000000..3277e36 --- /dev/null +++ b/typings/alive_progress/animations/spinner_compiler.pyi @@ -0,0 +1,150 @@ +""" +This type stub file was generated by pyright. +""" + +from ..utils.colors import ORANGE_BOLD + +def spinner_controller(*, natural, skip_compiler=...): # -> Callable[..., Callable[..., Any | Callable[[], Generator[Any, Any, None]]]]: + ... + +def extra_command(is_compiler): # -> Callable[..., Any]: + ... + +EXTRA_COMMANDS = ... +@compiler_command +def replace(spec, old, new): # -> None: + """Replace a portion of the frames by another with the same length. + + Args: + old (str): the old string to be replaced + new (str): the new string + + """ + ... + +@compiler_command +def pause(spec, edges=..., center=..., other=...): # -> None: + """Make the animation appear to pause at the edges or at the middle, or make it slower as + a whole, or both. + + Use without arguments to get their defaults, which gives a small pause at the edges, + very nice for bouncing text with `hide=False`. Please note that the defaults only apply + if none of the params are set. + + In the future, I'd like to make this a `pace` command, which would receive a sequence + of ints of any length, and apply it bouncing across the cycle. For example to smoothly + decelerate it could be (6, 3, 2, 1), which would become (6, 3, 2, 1, 1, ..., 1, 2, 3, 6). + + Args: + edges (Optional[int]): how many times the first and last frames of a cycle repeats + default is 8. + center (Optional[int]): how many times the middle frame of a cycle repeats + default is 1. + other (Optional[int]): how many times all the other frames of a cycle repeats + default is 1. + + """ + ... + +@compiler_command +def reshape(spec, num_frames): # -> None: + """Reshape frame data into another grouping. It can be used to simplify content + description, or for artistic effects. + + Args: + num_frames (int): the number of consecutive frames to group + + """ + ... + +@compiler_command +def bounce(spec): # -> None: + """Make the animation bounce its cycles.""" + ... + +@compiler_command +def transpose(spec): # -> None: + """Transpose the frame content matrix, exchanging columns for rows. It can be used + to simplify content description, or for artistic effects.""" + ... + +@runner_command +def sequential(spec): # -> None: + """Configure the runner to play the compiled cycles in sequential order.""" + ... + +@runner_command +def randomize(spec, cycles=...): # -> None: + """Configure the runner to play the compiled cycles in random order. + + Args: + cycles (Optional[int]): number of cycles to play randomized + + """ + ... + +def apply_extra_commands(spec, extra_commands): # -> None: + ... + +def spinner_compiler(gen, natural, extra_commands): # -> SimpleNamespace: + """Optimized spinner compiler, which compiles ahead of time all frames of all cycles + of a spinner. + + Args: + gen (Generator): the generator expressions that defines the cycles and their frames + natural (int): the natural length of the spinner + extra_commands (tuple[tuple[cmd, list[Any], dict[Any]]]): requested extra commands + + Returns: + the spec of a compiled animation + + """ + ... + +def spinner_runner_factory(spec, t_compile, extra_commands): # -> Callable[[], Generator[Any, Any, None]]: + """Optimized spinner runner, which receives the spec of an animation, and controls + the flow of cycles and frames already compiled to a certain screen length and with + wide chars fixed, thus avoiding any overhead in runtime within complex spinners, + while allowing their factories to be garbage collected. + + Args: + spec (SimpleNamespace): the spec of an animation + t_compile (about_time.Handler): the compile time information + extra_commands (tuple[tuple[cmd, list[Any], dict[Any]]]): requested extra commands + + Returns: + a spinner runner + + """ + ... + +def check(spec, verbosity=...): # -> None: + """Check the specs, contents, codepoints, and even the animation of this compiled spinner. + + Args: + verbosity (int): change the verbosity level + 0 for specs only (default) + / \\ + / 3 to include animation + / \\ + 1 to unfold frame data -------- 4 to unfold frame data + | | + 2 to reveal codepoints -------- 5 to reveal codepoints + + """ + ... + +SECTION = ORANGE_BOLD +HELP_MSG = ... +def spec_data(spec): # -> None: + ... + +def format_codepoints(frame): # -> str: + ... + +def render_data(spec, show_codepoints): # -> None: + ... + +def animate(spec): # -> None: + ... + diff --git a/typings/alive_progress/animations/spinners.pyi b/typings/alive_progress/animations/spinners.pyi new file mode 100644 index 0000000..a020ba5 --- /dev/null +++ b/typings/alive_progress/animations/spinners.pyi @@ -0,0 +1,127 @@ +""" +This type stub file was generated by pyright. +""" + +def frame_spinner_factory(*frames): # -> Callable[..., Generator[Generator[Any, Any, None], None, None]]: + """Create a factory of a spinner that delivers frames in sequence, split by cycles. + Supports unicode grapheme clusters and emoji chars (those that has length one but when on + screen occupies two cells), as well as all other spinners. + + Args: + frames (Union[str, Tuple[str, ...]): the frames to be displayed, split by cycles + if sent only a string, it is interpreted as frames of a single char each. + + Returns: + a styled spinner factory + + Examples: + To define one cycle: + >>> frame_spinner_factory(('cool',)) # only one frame. + >>> frame_spinner_factory(('ooo', '---')) # two frames. + >>> frame_spinner_factory('|/_') # three frames of one char each, same as below. + >>> frame_spinner_factory(('|', '/', '_')) + + To define two cycles: + >>> frame_spinner_factory(('super',), ('cool',)) # one frame each. + >>> frame_spinner_factory(('ooo', '-'), ('vvv', '^')) # two frames each. + >>> frame_spinner_factory('|/_', '▁▄█') # three frames each, same as below. + >>> frame_spinner_factory(('|', '/', '_'), ('▁', '▄', '█')) + + Mix and match at will: + >>> frame_spinner_factory(('oo', '-'), 'cool', ('it', 'is', 'alive!')) + + """ + ... + +def scrolling_spinner_factory(chars, length=..., block=..., background=..., *, right=..., hide=..., wrap=..., overlay=...): # -> Callable[..., Generator[Generator[tuple[str, ...], Any, None], None, None]]: + """Create a factory of a spinner that scrolls characters from one side to + the other, configurable with various constraints. + Supports unicode grapheme clusters and emoji chars, those that has length one but when on + screen occupies two cells. + + Args: + chars (str): the characters to be scrolled, either together or split in blocks + length (Optional[int]): the natural length that should be used in the style + block (Optional[int]): if defined, split chars in blocks with this size + background (Optional[str]): the pattern to be used besides or underneath the animations + right (bool): the scroll direction to animate + hide (bool): controls whether the animation goes through the borders or not + wrap (bool): makes the animation wrap borders or stop when not hiding. + overlay (bool): fixes the background in place if overlay, scrolls it otherwise + + Returns: + a styled spinner factory + + """ + ... + +def bouncing_spinner_factory(chars, length=..., block=..., background=..., *, right=..., hide=..., overlay=...): # -> Callable[..., Generator[Generator[Any, Any, None], None, None]]: + """Create a factory of a spinner that scrolls characters from one side to + the other and bounce back, configurable with various constraints. + Supports unicode grapheme clusters and emoji chars, those that has length one but when on + screen occupies two cells. + + Args: + chars (Union[str, Tuple[str, str]]): the characters to be scrolled, either + together or split in blocks. Also accepts a tuple of two strings, + which are used one in each direction. + length (Optional[int]): the natural length that should be used in the style + block (Union[int, Tuple[int, int], None]): if defined, split chars in blocks + background (Optional[str]): the pattern to be used besides or underneath the animations + right (bool): the scroll direction to start the animation + hide (bool): controls whether the animation goes through the borders or not + overlay (bool): fixes the background in place if overlay, scrolls it otherwise + + Returns: + a styled spinner factory + + """ + ... + +def sequential_spinner_factory(*spinner_factories, intermix=...): # -> Callable[..., Generator[Generator[Any, Any, None], None, None]]: + """Create a factory of a spinner that combines other spinners together, playing them + one at a time sequentially, either intermixing their cycles or until depletion. + + Args: + spinner_factories (spinner): the spinners to be combined + intermix (bool): intermixes the cycles if True, generating all possible combinations; + runs each one until depletion otherwise. + + Returns: + a styled spinner factory + + """ + ... + +def alongside_spinner_factory(*spinner_factories, pivot=...): # -> Callable[..., Generator[Generator[Any | tuple[()], Any, None], None, None]]: + """Create a factory of a spinner that combines other spinners together, playing them + alongside simultaneously. Each one uses its own natural length, which is spread weighted + to the available space. + + Args: + spinner_factories (spinner): the spinners to be combined + pivot (Optional[int]): the index of the spinner to dictate the animation cycles + if None, the whole animation will be compiled into a unique cycle. + + Returns: + a styled spinner factory + + """ + ... + +def delayed_spinner_factory(spinner_factory, copies, offset=..., *, dynamic=...): # -> Any | Callable[..., Any]: + """Create a factory of a spinner that combines itself several times alongside, + with an increasing iteration offset on each one. + + Args: + spinner_factory (spinner): the source spinner + copies (int): the number of copies + offset (int): the offset to be applied incrementally to each copy + dynamic (bool): dynamically changes the number of copies based on available space + + Returns: + a styled spinner factory + + """ + ... + diff --git a/typings/alive_progress/animations/utils.pyi b/typings/alive_progress/animations/utils.pyi new file mode 100644 index 0000000..ca085e5 --- /dev/null +++ b/typings/alive_progress/animations/utils.pyi @@ -0,0 +1,58 @@ +""" +This type stub file was generated by pyright. +""" + +from typing import Callable + +def spinner_player(spinner): # -> Generator[Any, Any, NoReturn]: + """Create an infinite generator that plays all cycles of a spinner indefinitely.""" + ... + +def bordered(borders, default): # -> Callable[..., _Wrapped[Callable[..., Any], Any, Callable[..., Any], Any | tuple[()]]]: + """Decorator to include controllable borders in the outputs of a function.""" + ... + +def extract_fill_graphemes(text, default): # -> Generator[tuple[Any, *tuple[None, ...]] | tuple[()], None, None]: + """Extract the exact same number of graphemes as default, filling missing ones.""" + ... + +def static_sliding_window(sep, gap, contents, length, right, initial): # -> Generator[tuple[str, ...], Any, NoReturn]: + """Implement a sliding window over some content interspersed with a separator. + It is very efficient, storing data in only one string. + + Note that the implementation is "static" in the sense that the content is pre- + calculated and maintained static, but actually when the window slides both the + separator and content seem to be moved. + Also keep in mind that `right` is for the content, not the window. + """ + ... + +def overlay_sliding_window(background, gap, contents, length, right, initial): # -> Generator[tuple[Any | str, ...], Any, None]: + """Implement a sliding window over some content on top of a background. + It uses internally a static sliding window, but dynamically swaps the separator + characters for the background ones, thus making it appear immobile, with the + contents sliding over it. + """ + ... + +def combinations(nums): + """Calculate the number of total combinations a few spinners should have together, + can be used for example with cycles or with frames played at the same time.""" + ... + +def split_options(options): # -> tuple[Any, ...] | tuple[Any, Any]: + """Split options that apply to dual elements, either duplicating or splitting.""" + ... + +def spread_weighted(actual_length, naturals): # -> tuple[Any, ...]: + """Calculate the weighted spreading of the available space for all natural lengths.""" + ... + +def fix_signature(func: Callable, source: Callable, skip_n_params: int): # -> Callable[..., Any]: + """Override signature to hide first n parameters.""" + ... + +def round_even(n): # -> int: + """Round a number to the nearest even integer.""" + ... + diff --git a/typings/alive_progress/core/calibration.pyi b/typings/alive_progress/core/calibration.pyi new file mode 100644 index 0000000..54e7901 --- /dev/null +++ b/typings/alive_progress/core/calibration.pyi @@ -0,0 +1,30 @@ +""" +This type stub file was generated by pyright. +""" + +def calibrated_fps(calibrate): # -> Callable[..., float]: + """Calibration of the dynamic frames per second engine. + + I've started with the equation y = log10(x + m) * k + n, where: + y is the desired fps, m and n are horizontal and vertical translation, + k is a calibration factor, computed from some user input c (see readme for details). + + Considering minfps and maxfps as given constants, I came to: + fps = log10(x + 1) * k + minfps, which must be equal to maxfps for x = c, + so the factor k = (maxfps - minfps) / log10(c + 1), and + fps = log10(x + 1) * (maxfps - minfps) / log10(c + 1) + minfps + + Neat! ;) + + Args: + calibrate (float): user provided + + Returns: + a callable to calculate the fps + + """ + ... + +def custom_fps(refresh_secs): # -> Callable[..., Any]: + ... + diff --git a/typings/alive_progress/core/configuration.pyi b/typings/alive_progress/core/configuration.pyi new file mode 100644 index 0000000..2aa562c --- /dev/null +++ b/typings/alive_progress/core/configuration.pyi @@ -0,0 +1,14 @@ +""" +This type stub file was generated by pyright. +""" + +""" +This module must always be importable, even without the required libs for install! +It's because I import metadata from main init, directly in setup.py, which imports this. +""" +ERROR = ... +Config = ... +def create_config(): # -> Callable[..., Config]: + ... + +config_handler = ... diff --git a/typings/alive_progress/core/hook_manager.pyi b/typings/alive_progress/core/hook_manager.pyi new file mode 100644 index 0000000..56b397f --- /dev/null +++ b/typings/alive_progress/core/hook_manager.pyi @@ -0,0 +1,40 @@ +""" +This type stub file was generated by pyright. +""" + +ENCODING = ... +def buffered_hook_manager(header_template, get_pos, offset, cond_refresh, term): # -> SimpleNamespace: + """Create and maintain a buffered hook manager, used for instrumenting print + statements and logging. + + Args: + header_template (): the template for enriching output + get_pos (Callable[..., Any]): the container to retrieve the current position + offset (int): the offset to add to the current position + cond_refresh: Condition object to force a refresh when printing + term: the current terminal + + Returns: + a closure with several functions + + """ + class Hook(BaseHook): + ... + + + +class BaseHook: + def __init__(self, stream) -> None: + ... + + def __getattr__(self, item): # -> Any: + ... + + + +def passthrough_hook_manager(): # -> Callable[[], ...]: + ... + +def gen_header(header_template, get_pos, offset): # -> Callable[[], Any]: + ... + diff --git a/typings/alive_progress/core/progress.pyi b/typings/alive_progress/core/progress.pyi new file mode 100644 index 0000000..1572a6a --- /dev/null +++ b/typings/alive_progress/core/progress.pyi @@ -0,0 +1,238 @@ +""" +This type stub file was generated by pyright. +""" + +from typing import Any, Callable, Collection, Iterable, Optional, TypeVar + +""" +This module must always be importable, even without the required libs for install! +It's because I import metadata from main init, directly in setup.py, which imports this. +""" +def alive_bar(total: Optional[int] = ..., *, calibrate: Optional[int] = ..., **options: Any): # -> _GeneratorContextManager[Any, None, None]: + """An alive progress bar to keep track of lengthy operations. + It has a spinner indicator, elapsed time, throughput and ETA. + When the operation finishes, a receipt is displayed with statistics. + + If the code is executed in a headless environment, ie without a + connected tty, all features are disabled but the final receipt. + + Another cool feature is that it tracks the actual count in regard of the + expected count. So it will look different if you send more (or less) than + expected. + + Also, the bar installs a hook in the system print function that cleans + any garbage out of the terminal, allowing you to print() effortlessly + while using the bar. + + Use it like this: + + >>> from alive_progress import alive_bar + ... with alive_bar(123, title='Title') as bar: # <-- expected total and bar title + ... for item in : + ... # process item + ... bar() # makes the bar go forward + + The `bar()` method should be called whenever you want the bar to go forward. + You usually call it in every iteration, but you could do it only when some + criteria match, depending on what you want to monitor. + + While in a progress bar context, you have two ways to output messages: + - the usual Python `print()` statement, which will properly clean the line, + print an enriched message (including the current bar position) and + continue the bar right below it; + - the `bar.text('message')` call, which sets a situational message right within + the bar, usually to display something about the items being processed or the + phase the processing is in. + + If the bar is over or underused, it will warn you! + To test all supported scenarios, you can do this: + >>> for x in 1000, 1500, 700, 0: + ... with alive_bar(x) as bar: + ... for i in range(1000): + ... time.sleep(.005) + ... bar() + Expected results are these (but you have to see them in motion!): +|████████████████████████████████████████| 1000/1000 [100%] in 6.0s (167.93/s) +|██████████████████████████▋⚠ | (!) 1000/1500 [67%] in 6.0s (167.57/s) +|████████████████████████████████████████✗ (!) 1000/700 [143%] in 6.0s (167.96/s) +|████████████████████████████████████████| 1000 in 5.8s (171.91/s) + + Args: + total (Optional[int]): the total expected count + calibrate (float): maximum theoretical throughput to calibrate animation speed + **options: custom configuration options, which override the global configuration: + title (Optional[str]): an optional, always visible bar title + length (int): the number of cols to render the actual bar in alive_bar + max_cols (int): the maximum cols to use if not possible to fetch it, like in jupyter + spinner (Union[None, str, object]): the spinner style to be rendered next to the bar + accepts a predefined spinner name, a custom spinner factory, or None + bar (Union[None, str, object]): the bar style to be rendered in known modes + accepts a predefined bar name, a custom bar factory, or None + unknown (Union[str, object]): the bar style to be rendered in the unknown mode + accepts a predefined spinner name, or a custom spinner factory (cannot be None) + theme (str): a set of matching spinner, bar and unknown + accepts a predefined theme name + force_tty (Optional[int|bool]): forces a specific kind of terminal: + False -> disables animations, keeping only the the final receipt + True -> enables animations, and auto-detects Jupyter Notebooks! + None (default) -> auto select, according to the terminal/Jupyter + file (object): use `sys.stdout`, `sys.stderr`, or a similar `TextIOWrapper` object + disable (bool): if True, completely disables all output, do not install hooks + manual (bool): set to manually control the bar position + enrich_print (bool): enriches print() and logging messages with the bar position + enrich_offset (int): the offset to apply to enrich_print + receipt (bool): prints the nice final receipt, disables if False + receipt_text (bool): set to repeat the last text message in the final receipt + monitor (bool|str): configures the monitor widget `152/200 [76%]` + send a string with `{count}`, `{total}` and `{percent}` to customize it + elapsed (bool|str): configures the elapsed time widget `in 12s` + send a string with `{elapsed}` to customize it + stats (bool|str): configures the stats widget `(~12s, 123.4/s)` + send a string with `{rate}` and `{eta}` to customize it + monitor_end (bool|str): configures the monitor widget within final receipt + same as monitor, the default format is dynamic, it inherits monitor's one + elapsed_end (bool|str): configures the elapsed time widget within final receipt + same as elapsed, the default format is dynamic, it inherits elapsed's one + stats_end (bool|str): configures the stats widget within final receipt + send a string with `{rate}` to customize it (no relation to stats) + title_length (int): fixes the title lengths, or 0 for unlimited + title will be truncated if longer, and a cool ellipsis "…" will appear at the end + spinner_length (int): forces the spinner length, or `0` for its natural one + refresh_secs (int): forces the refresh period, `0` for the reactive visual feedback + ctrl_c (bool): if False, disables CTRL+C (captures it) + dual_line (bool): if True, places the text below the bar + unit (str): any text that labels your entities + scale (any): the scaling to apply to units: 'SI', 'IEC', 'SI2' + precision (int): how many decimals do display when scaling + + """ + ... + +class _Widget: + def __init__(self, func, value, default) -> None: + ... + + def __call__(self): + ... + + + +class _ReadOnlyProperty: + def __set_name__(self, owner, name): # -> None: + ... + + def __get__(self, obj, objtype=...): # -> Any: + ... + + def __set__(self, obj, value): + ... + + + +class _GatedFunction(_ReadOnlyProperty): + def __get__(self, obj, objtype=...): # -> Any | Callable[..., None]: + ... + + + +class _GatedAssignFunction(_GatedFunction): + def __set__(self, obj, value): # -> None: + ... + + + +class __AliveBarHandle: + pause = ... + current = ... + text = ... + title = ... + monitor = ... + rate = ... + eta = ... + def __init__(self, pause, set_title, set_text, get_current, get_monitor, get_rate, get_eta) -> None: + ... + + def __call__(self, *args, **kwargs): # -> None: + ... + + + +T = TypeVar('T') +def alive_it(it: Collection[T], total: Optional[int] = ..., *, finalize: Callable[[Any], None] = ..., calibrate: Optional[int] = ..., **options: Any) -> Iterable[T]: + """New iterator adapter in 2.0, which makes it simpler to monitor any processing. + + Simply wrap your iterable with `alive_it`, and process your items normally! + >>> from alive_progress import alive_it + ... + ... items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ... for item in alive_it(items): + ... # process item. + + And the bar will just work, it's that simple! + + All `alive_bar` parameters apply as usual, except `total` (which is smarter: if not supplied + it will be inferred from the iterable using len or length_hint), and `manual` (which can't + be used in this mode at all). + To force unknown mode, even when the total would be available, send `total=0`. + + If you want to use other alive_bar's more advanced features, like for example setting + situational messages, you can simply assign it to a variable. + + >>> from alive_progress import alive_it + ... + ... items = range(100000) + ... bar = alive_it(items) + ... for item in bar: + ... bar.text = f'Wow, it works! Item: {item}' + ... # process item. + + You can also send a `finalize` function to set the final receipt title and text, and any other + alive_bar options you'd like! + + >>> from alive_progress import alive_it + ... + ... def ending(bar): + ... bar.title = 'DB updated' + ... bar.text = f'{bar.current} entries changed' + ... + ... items = range(100000) + ... for item in alive_it(items, finalize=ending, length=20, receipt_text=True) + ... # process item. + + This prints: +DB updated |████████████████████| 100k/100k [100%] in 2.6s (38.7k/s) 100000 entries changed + + Args: + it: the input iterable to be processed + total: same as alive_bar + finalize: a function to be called when the bar is going to finalize + calibrate: same as alive_bar + options: same as alive_bar + + See Also: + alive_bar + + Returns: + Generator + + """ + ... + +class __AliveBarIteratorAdapter(Iterable[T]): + def __init__(self, it, finalize, inner_bar) -> None: + ... + + def __iter__(self): # -> Generator[Any, Any, None]: + ... + + def __call__(self, *args, **kwargs): + ... + + def __getattr__(self, item): # -> Any: + ... + + def __setattr__(self, key, value): # -> None: + ... + + + diff --git a/typings/alive_progress/styles/exhibit.pyi b/typings/alive_progress/styles/exhibit.pyi new file mode 100644 index 0000000..dd94d63 --- /dev/null +++ b/typings/alive_progress/styles/exhibit.pyi @@ -0,0 +1,58 @@ +""" +This type stub file was generated by pyright. +""" + +Show = ... +def showtime(show=..., *, fps=..., length=..., pattern=...): # -> None: + """Start a show, rendering all styles simultaneously in your screen. + + Args: + fps (float): the desired frames per second refresh rate + show (Show): chooses which show will run + length (int): the bar length, as in configuration options + pattern (Pattern): to filter objects displayed + + """ + ... + +Info = ... +def show_spinners(*, fps=..., length=..., pattern=...): # -> None: + """Start a spinner show, rendering all styles simultaneously in your screen. + + Args: + fps (float): the desired frames per second rendition + length (int): the bar length, as in configuration options + pattern (Pattern): to filter objects displayed + + """ + ... + +def show_bars(*, fps=..., length=..., pattern=...): # -> None: + """Start a bar show, rendering all styles simultaneously in your screen. + + Args: + fps (float): the desired frames per second rendition + length (int): the bar length, as in configuration options + pattern (Pattern): to filter objects displayed + + """ + ... + +def show_themes(*, fps=..., length=..., pattern=...): # -> None: + """Start a theme show, rendering all styles simultaneously in your screen. + + Args: + fps (float): the desired frames per second rendition + length (int): the bar length, as in configuration options + pattern (Pattern): to filter objects displayed + + """ + ... + +_INFO = ... +def exhibit_spinner(spinner): # -> Generator[Any, Any, NoReturn]: + ... + +def exhibit_bar(bar, fps): # -> Generator[tuple[Any, float], Any, NoReturn]: + ... + diff --git a/typings/alive_progress/styles/internal.pyi b/typings/alive_progress/styles/internal.pyi new file mode 100644 index 0000000..b4b501c --- /dev/null +++ b/typings/alive_progress/styles/internal.pyi @@ -0,0 +1,7 @@ +""" +This type stub file was generated by pyright. +""" + +SPINNERS = ... +BARS = ... +THEMES = ... diff --git a/typings/alive_progress/utils/__init__.pyi b/typings/alive_progress/utils/__init__.pyi new file mode 100644 index 0000000..c1d85c3 --- /dev/null +++ b/typings/alive_progress/utils/__init__.pyi @@ -0,0 +1,10 @@ +""" +This type stub file was generated by pyright. +""" + +import re + +PATTERN_SANITIZE = ... +def sanitize(text): # -> str: + ... + diff --git a/typings/alive_progress/utils/cells.pyi b/typings/alive_progress/utils/cells.pyi new file mode 100644 index 0000000..8da3c83 --- /dev/null +++ b/typings/alive_progress/utils/cells.pyi @@ -0,0 +1,142 @@ +""" +This type stub file was generated by pyright. +""" + +""" +Implements support for grapheme clusters and cells (columns on screen). +Graphemes are sequences of codepoints, which are interpreted together based on the Unicode +standard. Grapheme clusters are sequences of graphemes, glued together by Zero Width Joiners. +These graphemes may occupy one or two cells on screen, depending on their glyph size. + +Support for these cool chars, like Emojis 😃, was so damn hard to implement because: +1. Python don't know chars that occupy two columns on screen, nor grapheme clusters that are + rendered as a single char (wide or not), it only understands codepoints; +2. Alive-progress needs to visually align all frames, to keep its progress bars' lengths from + spiking up and down while running. For this I must somehow know which chars are wide and + counterbalance them; +3. To generate all those cool animations, I need several basic operations, like len, iterating, + indexing, slicing, concatenating and reversing, which suddenly don't work anymore, since they + do not know anything about these new concepts of graphemes and cells! Argh. +4. As the first step, I needed to parse the codepoints into Unicode graphemes. I tried to parse them + myself, but soon realized it was tricky and finicky, in addition to changing every year... +5. Then I looked into some lib dependencies, tested several, created the validate tool to help me + test some Unicode versions, and chose one lib to use; +6. I finally implemented the operations I needed, to the best of my current knowledge, but it + still wouldn't work. So I tried several spinners to check their alignments, until I finally + realized what was wrong: I actually needed to align cells, not lengths nor even graphemes! + + Look this for example: Note that in your editor both strings below are perfectly aligned, + although they have 6 and 16 as their Python lengths!!! How come? + Graphemes didn't help either, 6 and 3 respectively... Then how does the editor know that they + align? I'm not sure exactly, but I created this "cell" concept to map this into, and finally + they both have the same: 6 cells!! 💡😜 + + string \\ length python graphemes cells + nonono 6 6 6 + 🏴󠁧󠁢󠁥󠁮󠁧󠁿👉🏾🏴󠁧󠁢󠁥󠁮󠁧󠁿 16 3 6 + +7. With that knowledge, I implemented "wide" marks on graphemes (so I could know whether a grapheme + glyph would occupy 1 or 2 cells on screen), and refactored all needed operations. It seemed fine + but still didn't work... I then realized that my animations made those wide chars dynamically + enter and leave the frame, which can split strings AT ANY POINT, even between the two cells of + wide-graphemes, yikes!!! To make the animations as fluid as always, I had to continue moving + only one cell per tick time, so somehow I would have to draw "half" flags and "half" smiling- + face-with-smiling-eyes!! +8. So, I had to support printing "half-graphemes", so I could produce frames in an animation with + always the same sizes!! This has led me to implement a fixer for dynamically broken graphemes, + which detects whether the head or tail cells were missing, and inserted a space in its place! +9. It worked! But I would have to run that algorithm throughout the whole animation, in any and all + displayed frame, in real time... I feared for the performance. + I needed something that could cache and "see" all the frames at once, so I could equalize their + sizes only once!! So I created the cool spinner compiler, an ingenious piece of software that + generates the entire animation ahead of time, fixes all the frames, and leverages a super light + and fast runner, which is able to "play" this compiled artifact!! +10. Finally, I refactored the frame spinner factory, the simplest one to test the idea, and WOW... + It worked!!! The joy of success filled me.......... +11. To make the others work, I created the check tool, another ingenious software, which allowed me + to "see" a spinner's contents, in a tabular way, directly from the compiled data! Then I could + visually ensure whether ALL generated frames of ALL animations I could think of, had the exact + same size; +12. A lot of time later, everything was working! But look at that, the spinner compiler has enabled + me to make several improvements in the spinners' codes themselves, since it ended up gaining + other cool functionalities like reshaping and transposing data, or randomizing anything playing! + The concepts of "styling" and "operational" parameters got stronger with new commands, which + enabled simpler compound animations, without any code duplication! + And this has culminated in the creation of the newer sequential and alongside spinners, way more + advanced than before, with configurations like intermixing and pivoting of cycles! +13. Then, it was time I moved on to the missing components in this new Cell Architecture: the bar, + title, exhibit, and of course the alive_bar rendering itself... All of them needed to learn this + new architecture: mainly change ordinary strings into tuples of cells (marked graphemes)... +14. And finally... Profit!!! Only no, this project only feels my soul, not my pocket... + But what a ride! 😅 + +""" +VS_15 = ... +def print_cells(fragments, cols, term, last_line_len=...): + """Print a tuple of fragments of tuples of cells on the terminal, until a given number of + cols is achieved, slicing over cells when needed. + + Spaces used to be inserted automatically between fragments, but not anymore. + + Args: + fragments (Tuple[Union[str, Tuple[str, ...]]): the fragments of message + cols (int): maximum columns to use + term: the terminal to be used + last_line_len (int): if the fragments fit within the last line, send a clear end line + + Returns: + the number of actually used cols. + + """ + ... + +def join_cells(fragment): # -> LiteralString: + """Beware, this looses the cell information, converting to a simple string again. + Don't use unless it is a special case.""" + ... + +def combine_cells(*fragments): # -> tuple[()]: + """Combine several fragments of cells into one. + Remember that the fragments get a space between them, so this is mainly to avoid it when + not desired.""" + ... + +def is_wide(g): # -> bool: + """Try to detect wide chars. + + This is tricky, I've seen several graphemes that have Neutral width (and thus use one + cell), but actually render as two cells, like shamrock and heart ☘️❤️. + I've talked to George Nachman, the creator of iTerm2, which has explained to me [1] the fix + would be to insert a space after these cases, but I can't possibly know if this + behavior is spread among all terminals, it probably has to do with the Unicode version too, + so I'm afraid of fixing it. + Use the `alive_progress.tools.print_chars` tool, and check the section around `0x1f300` + for more examples. + + [1]: https://gitlab.com/gnachman/iterm2/-/issues/9185 + + Args: + g (str): the grapheme sequence to be tested + + """ + ... + +def fix_cells(chars): # -> tuple[Any | Literal[' '], ...]: + """Fix truncated cells, removing whole clusters when needed.""" + ... + +def to_cells(text): # -> tuple[Any, *tuple[None, ...]] | tuple[()]: + ... + +def split_graphemes(text): # -> tuple[Any | None, ...]: + ... + +def mark_graphemes(gs): # -> tuple[Any, *tuple[None, ...]] | tuple[()]: + ... + +def strip_marks(chars): # -> Generator[Any, None, None]: + ... + +def has_wide(text): # -> bool: + ... + diff --git a/typings/alive_progress/utils/colors.pyi b/typings/alive_progress/utils/colors.pyi new file mode 100644 index 0000000..23574e1 --- /dev/null +++ b/typings/alive_progress/utils/colors.pyi @@ -0,0 +1,36 @@ +""" +This type stub file was generated by pyright. +""" + +""" +Very basic color implementation, just to print fixed messages on screen. + +It's very hard to support colors inside any moving parts of alive-progress, as I would need +to implement operations like len, indexing, slicing, concatenation and revert, while maintaining +the color information correct! There's an impedance mismatch between what we see and how we +represent it (yeah, similar to unicode grapheme clusters 😅). + +I'd say it is possible now with the new cell architecture, I'd just need some new rules. +I would need to split color information just like grapheme clusters and mark them like cells, +but with the opposite effect. In cell the marks increase the len for the wide chars, but here +they must not change it somehow. +Also, the ansi escape codes the terminal receives changes its state, so I couldn't simply truncate +the line anymore, it'd need another escape code to finalize that one, and return it to its previous +state! +So, these special cells would require yet more work. + +""" +def color_factory(color_code): # -> Callable[..., str]: + ... + +BLUE = ... +GREEN = ... +YELLOW = ... +RED = ... +MAGENTA = ... +CYAN = ... +ORANGE = ... +BOLD = ... +DIM = ... +ITALIC = ... +UNDERLINE = ... diff --git a/typings/alive_progress/utils/terminal/__init__.pyi b/typings/alive_progress/utils/terminal/__init__.pyi new file mode 100644 index 0000000..caa94a0 --- /dev/null +++ b/typings/alive_progress/utils/terminal/__init__.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +import sys +from types import SimpleNamespace +from . import jupyter, non_tty, tty, void + +if sys.platform == 'win32': + ... +def get_void(): # -> SimpleNamespace: + ... + +def get_term(file=..., force_tty=..., cols=...): # -> SimpleNamespace: + ... + diff --git a/typings/alive_progress/utils/terminal/jupyter.pyi b/typings/alive_progress/utils/terminal/jupyter.pyi new file mode 100644 index 0000000..36db627 --- /dev/null +++ b/typings/alive_progress/utils/terminal/jupyter.pyi @@ -0,0 +1,7 @@ +""" +This type stub file was generated by pyright. +""" + +def get_from(parent): # -> SimpleNamespace: + ... + diff --git a/typings/alive_progress/utils/terminal/non_tty.pyi b/typings/alive_progress/utils/terminal/non_tty.pyi new file mode 100644 index 0000000..36db627 --- /dev/null +++ b/typings/alive_progress/utils/terminal/non_tty.pyi @@ -0,0 +1,7 @@ +""" +This type stub file was generated by pyright. +""" + +def get_from(parent): # -> SimpleNamespace: + ... + diff --git a/typings/alive_progress/utils/terminal/tty.pyi b/typings/alive_progress/utils/terminal/tty.pyi new file mode 100644 index 0000000..e7f6191 --- /dev/null +++ b/typings/alive_progress/utils/terminal/tty.pyi @@ -0,0 +1,7 @@ +""" +This type stub file was generated by pyright. +""" + +def new(original, max_cols): # -> SimpleNamespace: + ... + diff --git a/typings/alive_progress/utils/terminal/void.pyi b/typings/alive_progress/utils/terminal/void.pyi new file mode 100644 index 0000000..91276f5 --- /dev/null +++ b/typings/alive_progress/utils/terminal/void.pyi @@ -0,0 +1,22 @@ +""" +This type stub file was generated by pyright. +""" + +def write(_text): # -> Literal[0]: + ... + +def flush(): # -> None: + ... + +clear_line = ... +clear_end_line = ... +clear_end_screen = ... +hide_cursor = ... +show_cursor = ... +def factory_cursor_up(_): # -> Callable[..., None]: + ... + +def cols(): # -> Literal[0]: + ... + +carriage_return = ... diff --git a/typings/alive_progress/utils/timing.pyi b/typings/alive_progress/utils/timing.pyi new file mode 100644 index 0000000..ead84cc --- /dev/null +++ b/typings/alive_progress/utils/timing.pyi @@ -0,0 +1,41 @@ +""" +This type stub file was generated by pyright. +""" + +from typing import Callable + +TimeDisplay = ... +RUN = ... +END = ... +ETA = ... +class Refresh: + def tick(self) -> Refresh: + ... + + + +def time_display(seconds: float, conf: TimeDisplay) -> str: + ... + +def eta_text(seconds: float) -> str: + ... + +def fn_simple_eta(logic_total): # -> Callable[..., Any]: + ... + +def gen_simple_exponential_smoothing(alpha: float, fn: Callable[[float, float], float]): # -> Generator[float, Any, NoReturn]: + """Implements a generator with a simple exponential smoothing of some function. + Given alpha and y_hat (t-1), we can calculate the next y_hat: + y_hat = alpha * y + (1 - alpha) * y_hat + y_hat = alpha * y + y_hat - alpha * y_hat + y_hat = y_hat + alpha * (y - y_hat) + + Args: + alpha: the smoothing coefficient + fn: the function + + Returns: + + """ + ... +