Module timequota

Manage the time of your python script.

[PyPI] [GitHub] [Demo Notebook] [Changelog]

Expand source code
"""
**Manage the time of your python script.**

[[PyPI]](https://pypi.org/project/timequota/)
[[GitHub]](https://github.com/AravRS/timequota)
[[Demo Notebook]](https://github.com/AravRS/timequota/blob/main/demo.ipynb)
[[Changelog]](https://github.com/AravRS/timequota/blob/main/CHANGELOG.md)
"""

import sys
import time
from statistics import mean

from tabulate import tabulate
from colorama import Fore, Style

from collections import defaultdict
from typing import Any, List, Iterable, Iterator, Callable, Optional
from typeguard import typechecked


# provide compability with python<3.8
if sys.version_info[1] < 8:
    UnitType = str
    DisplayUnitType = str
else:
    from typing import Literal

    UnitType = Literal["s", "m", "h"]
    DisplayUnitType = Literal["s", "m", "h", "p"]


_time_dict = {
    "s": 1,
    "m": 60,
    "h": 3600,
}

_color_dict = {
    "g": Fore.GREEN,
    "c": Fore.CYAN,
    "y": Fore.YELLOW,
    "r": Fore.RED,
    "R": Style.RESET_ALL,
}


@typechecked
class TimeQuota:
    def __init__(
        self,
        quota: float,
        unit: UnitType = "s",
        display_unit: DisplayUnitType = None,
        *,
        name: str = "tq",
        step_aggr_fn: Callable[[List[float]], float] = mean,
        timer_fn: Callable[[], float] = time.perf_counter,
        logger_fn: Optional[Callable[[str], None]] = print,
        precision: int = 4,
        color: bool = True,
        verbose: bool = True,
    ) -> None:
        """
        Args:
            quota (float): Maximum time limit.
            unit (Literal[s, m, h], optional): Unit of time of *quota* given, can be one of 's', 'm' or 'h' for seconds, minutes, or hours respectively . Defaults to 's'.
            display_unit (Literal[s, m, h, p], optional): Unit of time for logging messages, can be one of 's', 'm' or 'h' for seconds, minutes, or hours respectively; or 'p' for pretty format. Defaults to *unit*.
            name (str, optional): Custom name for quota timer. Defaults to 'tq'.
            step_aggr_fn (Callable[[list[float]], float], optional): Function to aggregate individual time steps, used for overflow prediction. Defaults to mean.
            timer_fn (Callable[[], float], optional): Function timer called before and after code execution to calculate the time taken. Defaults to time.perf_counter.
            logger_fn (Optional[Callable[[str], None]], optional): Custom info logger function. Defaults to print.
            precision (int, optional): Custom value precision for logging messages. Defaults to 4.
            color (bool, optional): Enable or disable color. Defaults to True.
            verbose (bool, optional): Enable or disable logging messages entirely. Defaults to True.
        """

        self.unit = unit.lower()
        self.display_unit = self.unit if display_unit is None else display_unit.lower()
        self.quota = float(quota) * _time_dict[self.unit]

        self.name = name
        self.step_aggr_fn = step_aggr_fn
        self.timer_fn = timer_fn
        self.logger_fn = logger_fn

        self.precision = precision
        self._color_dict = _color_dict if color else defaultdict(str)
        self.verbose = verbose

        self.reset()

    def reset(
        self,
    ) -> None:
        """
        Resets time quota to initial values.
        """

        self.time_elapsed: float = 0
        self.time_remaining: float = self.quota

        self.overflow: bool = False
        self.predicted_overflow: bool = False
        self.time_exceeded: bool = False

        self.time_steps: list[float] = []
        self.time_per_step: float = 0
        self.time_this_step: float = 0

        self.time_since: float = self.timer_fn()

    def _update_quota(
        self,
        track: bool = False,
    ) -> None:
        self.time_this_step = self.timer_fn() - self.time_since

        self.time_elapsed += self.time_this_step
        self.time_remaining -= self.time_this_step

        self.overflow = bool(self.time_remaining < 0)

        if track:
            self.time_steps.append(self.time_this_step)
            self.time_per_step = self.step_aggr_fn(self.time_steps)
            self.predicted_overflow = bool(self.time_per_step > self.time_remaining)

        self.time_exceeded = bool(self.overflow or self.predicted_overflow)

    def _get_display_string(
        self,
        seconds: float,
    ) -> str:

        if self.display_unit == "p":
            return self._get_pretty_string(seconds)

        if seconds == float("inf"):
            display_string = "inf"
        else:
            time_sign = "-" if seconds < 0 else ""
            seconds = abs(seconds)

            s = seconds / _time_dict["s"]
            m = seconds / _time_dict["m"]
            h = seconds / _time_dict["h"]

            if self.display_unit == "h" and h > 0:
                display_string = f"{time_sign}{h:.{self.precision}f}h"
            elif self.display_unit == "m" and m > 0:
                display_string = f"{time_sign}{m:.{self.precision}f}m"
            elif self.display_unit == "s" and s > 0:
                display_string = f"{time_sign}{s:.{self.precision}f}s"
            else:
                display_string = "―"

        return self._color_dict["c"] + display_string + self._color_dict["R"]

    def _get_pretty_string(
        self,
        seconds: float,
        display: bool = False,
    ) -> str:

        if seconds == float("inf"):
            pretty_string = "inf"
        else:
            time_sign = "-" if seconds < 0 else ""
            seconds = abs(round(seconds))

            d = seconds // (3600 * 24)
            h = seconds // 3600 % 24
            m = seconds % 3600 // 60
            s = seconds % 3600 % 60

            if display or d > 0:
                pretty_string = f"{time_sign}{d:02d}d {h:02d}h {m:02d}m {s:02d}s"
            elif h > 0:
                pretty_string = f"{time_sign}{h:02d}h {m:02d}m {s:02d}s"
            elif m > 0:
                pretty_string = f"{time_sign}{m:02d}m {s:02d}s"
            elif s > 0:
                pretty_string = f"{time_sign}{s:02d}s"
            else:
                pretty_string = "―"

        return self._color_dict["c"] + pretty_string + self._color_dict["R"]

    def _get_time_exceeded_string(self):
        if self.overflow:
            return (
                f"{self._color_dict['g']}{self.name} {self._color_dict['r']}⚠ TIME EXCEEDED!{self._color_dict['R']} "
                f"Elapsed: {self._get_display_string(self.time_elapsed)}"
                f", Overflowed: {self._get_display_string(abs(self.time_remaining))}"
            )
        elif self.predicted_overflow:
            predicted_overflow_time = self.time_elapsed + self.time_per_step
            return (
                f"{self._color_dict['g']}{self.name} {self._color_dict['r']}⚠ TIME EXCEEDED! [Predicted]{self._color_dict['R']} "
                f"Estimated: {self._get_display_string(predicted_overflow_time)}"
                f", Overflow: {self._get_display_string(predicted_overflow_time - self.quota)}"
            )

    def _get_info_string(self, track=False):
        info_string = (
            f"{self._color_dict['g']}{self.name} » {self._color_dict['R']}"
            f"remaining: {self._get_display_string(self.time_remaining)}"
            f", elapsed: {self._get_display_string(self.time_elapsed)}"
        )

        if track:
            info_string += (
                f", this step: {self._get_display_string(self.time_this_step)}"
                + f", per step: {self._get_display_string(self.time_per_step)}"
            )

        return info_string

    def update(
        self,
        *,
        verbose: bool = True,
    ) -> bool:
        """
        Updates the quota considering the time taken from its creation to call.

        Args:
            verbose (bool, optional): Enable or disable logging messages. Defaults to True.

        Returns:
            bool: States if quota is exceeded.
        """

        self._update_quota()

        if (self.verbose and verbose) and self.logger_fn is not None:
            if self.time_exceeded:
                self.logger_fn(self._get_time_exceeded_string())
            else:
                self.logger_fn(self._get_info_string())

        self.time_since = self.timer_fn()
        return self.time_exceeded

    def track(
        self,
        *,
        verbose: bool = True,
    ) -> bool:
        """
        Tracks, stores and updates the time taken every call, also used for quota overflow prediction. To be used in loops or repetitive calls.

        Args:
            verbose (bool, optional): Enable or disable logging messages. Defaults to True.
        Returns:
            bool: States if quota is exceeded.
        """

        self._update_quota(track=True)

        if (self.verbose and verbose) and self.logger_fn is not None:
            self.logger_fn(self._get_info_string(track=True))
            if self.time_exceeded:
                self.logger_fn(self._get_time_exceeded_string())

        self.time_since = self.timer_fn()
        return self.time_exceeded

    def iter(
        self,
        iterable: Iterable[Any],
        *,
        time_exceeded_fn: Optional[Callable] = None,
        time_exceeded_break: bool = True,
        verbose: bool = True,
    ) -> Iterator[Any]:
        """
        Time limited iterator of the iterable. When called, updates the quota and tracks time taken for each iteration, upon quota (predicted) exhaustion stops iteration (default).

        Iteration stops (default) under two conditions:
        (i) Exhaustion - Time taken by the iteration has already  exceeded the time quota and overflow occurs.
        (ii) Predicted exhaustion - The time taken by the next iteration (calculated by the *step_aggr_fn*) will exceed the time quota.

        Args:
            iterable (Iterable[Any]): Iterable to be iterated.
            time_exceeded_fn (Optional[Callable], optional): Function to be executed if time exceeds. Defaults to None.
            time_exceeded_break (bool, optional): To break out of the loop if time exceeds. Defaults to True.
            verbose (bool, optional): Enable or disable logging messages. Defaults to True.

        Yields:
            Iterator[Any]: Element in the iterable.
        """

        if not self.update(verbose=verbose):
            for i in iterable:
                yield i

                if self.track(verbose=verbose):
                    if time_exceeded_fn is not None:
                        time_exceeded_fn()

                    if time_exceeded_break:
                        break

    def range(
        self,
        *args: Any,
        time_exceeded_fn: Optional[Callable] = None,
        time_exceeded_break: bool = True,
        verbose: bool = True,
        **kwargs: Any,
    ) -> Iterator[int]:
        """
        Time limited range. When called, updates the quota and tracks time taken for each iteration, upon quota (predicted) exhaustion stops iteration (default).

        Iteration stops (default) under two conditions:
            (i) Exhaustion - Time taken by the iteration has already  exceeded the time quota and overflow occurs.
            (ii) Predicted exhaustion - The time taken by the next iteration (calculated by the *step_aggr_fn*) will exceed the time quota.

        Args:
            time_exceeded_fn (Optional[Callable], optional): Function to be executed if time exceeds. Defaults to None.
            time_exceeded_break (bool, optional): To break out of the loop if time exceeds. Defaults to True.
            verbose (bool, optional): Enable or disable logging messages. Defaults to True.

        Yields:
            Iterator[int]: Sequence in the given number range.
        """

        return self.iter(
            iterable=range(*args, **kwargs),
            time_exceeded_fn=time_exceeded_fn,
            time_exceeded_break=time_exceeded_break,
            verbose=verbose,
        )

    def __str__(
        self,
    ) -> str:
        headers = [
            f"{self._color_dict['g']}{self.name}{self._color_dict['R']}",
            f"{self._color_dict['y']}Time{self._color_dict['R']}",
            f"{self._color_dict['y']}Time ({self.display_unit}){self._color_dict['R']}",
        ]

        if self.overflow:
            time_exceeded_string = (
                self._color_dict["r"] + "True" + self._color_dict["R"]
            )
        elif self.predicted_overflow:
            time_exceeded_string = (
                self._color_dict["r"] + "Predicted" + self._color_dict["R"]
            )
        else:
            time_exceeded_string = (
                self._color_dict["c"] + "False" + self._color_dict["R"]
            )

        table = [
            [
                "Time Quota",
                self._get_pretty_string(self.quota, display=True),
                self._get_display_string(self.quota),
            ],
            [
                "Time Elapsed",
                self._get_pretty_string(self.time_elapsed, display=True),
                self._get_display_string(self.time_elapsed),
            ],
            [
                "Time Remaining",
                self._get_pretty_string(self.time_remaining, display=True),
                self._get_display_string(self.time_remaining),
            ],
            [
                "Time Per Step",
                self._get_pretty_string(self.time_per_step, display=True),
                self._get_display_string(self.time_per_step),
            ],
            [
                "Time Exceeded",
                time_exceeded_string,
                time_exceeded_string,
            ],
        ]

        return tabulate(
            table,
            headers,
            colalign=("left", "right", "right"),
            tablefmt="simple",
        )

    def __repr__(
        self,
    ) -> str:
        _quota = self.quota / _time_dict[self.unit]

        return (
            f"{self.__class__.__name__}"
            "("
            f"{_quota!r}, {self.unit!r}, {self.display_unit!r}, "
            f"name={self.name!r}, "
            f"step_aggr_fn={self.step_aggr_fn!r}, "
            f"timer_fn={self.timer_fn!r}, "
            f"logger_fn={self.logger_fn!r}, "
            f"precision={self.precision!r}, "
            f"color={any(self._color_dict.values())!r}, "
            f"verbose={self.verbose!r}"
            ")"
        )

Classes

class TimeQuota (quota: float, unit: Literal['s', 'm', 'h'] = 's', display_unit: Literal['s', 'm', 'h', 'p'] = None, *, name: str = 'tq', step_aggr_fn: Callable[[List[float]], float] = <function mean>, timer_fn: Callable[[], float] = <built-in function perf_counter>, logger_fn: Optional[Callable[[str], None]] = <built-in function print>, precision: int = 4, color: bool = True, verbose: bool = True)

Args

quota : float
Maximum time limit.
unit : Literal[s, m, h], optional
Unit of time of quota given, can be one of 's', 'm' or 'h' for seconds, minutes, or hours respectively . Defaults to 's'.
display_unit : Literal[s, m, h, p], optional
Unit of time for logging messages, can be one of 's', 'm' or 'h' for seconds, minutes, or hours respectively; or 'p' for pretty format. Defaults to unit.
name : str, optional
Custom name for quota timer. Defaults to 'tq'.
step_aggr_fn : Callable[[list[float]], float], optional
Function to aggregate individual time steps, used for overflow prediction. Defaults to mean.
timer_fn : Callable[[], float], optional
Function timer called before and after code execution to calculate the time taken. Defaults to time.perf_counter.
logger_fn : Optional[Callable[[str], None]], optional
Custom info logger function. Defaults to print.
precision : int, optional
Custom value precision for logging messages. Defaults to 4.
color : bool, optional
Enable or disable color. Defaults to True.
verbose : bool, optional
Enable or disable logging messages entirely. Defaults to True.
Expand source code
@typechecked
class TimeQuota:
    def __init__(
        self,
        quota: float,
        unit: UnitType = "s",
        display_unit: DisplayUnitType = None,
        *,
        name: str = "tq",
        step_aggr_fn: Callable[[List[float]], float] = mean,
        timer_fn: Callable[[], float] = time.perf_counter,
        logger_fn: Optional[Callable[[str], None]] = print,
        precision: int = 4,
        color: bool = True,
        verbose: bool = True,
    ) -> None:
        """
        Args:
            quota (float): Maximum time limit.
            unit (Literal[s, m, h], optional): Unit of time of *quota* given, can be one of 's', 'm' or 'h' for seconds, minutes, or hours respectively . Defaults to 's'.
            display_unit (Literal[s, m, h, p], optional): Unit of time for logging messages, can be one of 's', 'm' or 'h' for seconds, minutes, or hours respectively; or 'p' for pretty format. Defaults to *unit*.
            name (str, optional): Custom name for quota timer. Defaults to 'tq'.
            step_aggr_fn (Callable[[list[float]], float], optional): Function to aggregate individual time steps, used for overflow prediction. Defaults to mean.
            timer_fn (Callable[[], float], optional): Function timer called before and after code execution to calculate the time taken. Defaults to time.perf_counter.
            logger_fn (Optional[Callable[[str], None]], optional): Custom info logger function. Defaults to print.
            precision (int, optional): Custom value precision for logging messages. Defaults to 4.
            color (bool, optional): Enable or disable color. Defaults to True.
            verbose (bool, optional): Enable or disable logging messages entirely. Defaults to True.
        """

        self.unit = unit.lower()
        self.display_unit = self.unit if display_unit is None else display_unit.lower()
        self.quota = float(quota) * _time_dict[self.unit]

        self.name = name
        self.step_aggr_fn = step_aggr_fn
        self.timer_fn = timer_fn
        self.logger_fn = logger_fn

        self.precision = precision
        self._color_dict = _color_dict if color else defaultdict(str)
        self.verbose = verbose

        self.reset()

    def reset(
        self,
    ) -> None:
        """
        Resets time quota to initial values.
        """

        self.time_elapsed: float = 0
        self.time_remaining: float = self.quota

        self.overflow: bool = False
        self.predicted_overflow: bool = False
        self.time_exceeded: bool = False

        self.time_steps: list[float] = []
        self.time_per_step: float = 0
        self.time_this_step: float = 0

        self.time_since: float = self.timer_fn()

    def _update_quota(
        self,
        track: bool = False,
    ) -> None:
        self.time_this_step = self.timer_fn() - self.time_since

        self.time_elapsed += self.time_this_step
        self.time_remaining -= self.time_this_step

        self.overflow = bool(self.time_remaining < 0)

        if track:
            self.time_steps.append(self.time_this_step)
            self.time_per_step = self.step_aggr_fn(self.time_steps)
            self.predicted_overflow = bool(self.time_per_step > self.time_remaining)

        self.time_exceeded = bool(self.overflow or self.predicted_overflow)

    def _get_display_string(
        self,
        seconds: float,
    ) -> str:

        if self.display_unit == "p":
            return self._get_pretty_string(seconds)

        if seconds == float("inf"):
            display_string = "inf"
        else:
            time_sign = "-" if seconds < 0 else ""
            seconds = abs(seconds)

            s = seconds / _time_dict["s"]
            m = seconds / _time_dict["m"]
            h = seconds / _time_dict["h"]

            if self.display_unit == "h" and h > 0:
                display_string = f"{time_sign}{h:.{self.precision}f}h"
            elif self.display_unit == "m" and m > 0:
                display_string = f"{time_sign}{m:.{self.precision}f}m"
            elif self.display_unit == "s" and s > 0:
                display_string = f"{time_sign}{s:.{self.precision}f}s"
            else:
                display_string = "―"

        return self._color_dict["c"] + display_string + self._color_dict["R"]

    def _get_pretty_string(
        self,
        seconds: float,
        display: bool = False,
    ) -> str:

        if seconds == float("inf"):
            pretty_string = "inf"
        else:
            time_sign = "-" if seconds < 0 else ""
            seconds = abs(round(seconds))

            d = seconds // (3600 * 24)
            h = seconds // 3600 % 24
            m = seconds % 3600 // 60
            s = seconds % 3600 % 60

            if display or d > 0:
                pretty_string = f"{time_sign}{d:02d}d {h:02d}h {m:02d}m {s:02d}s"
            elif h > 0:
                pretty_string = f"{time_sign}{h:02d}h {m:02d}m {s:02d}s"
            elif m > 0:
                pretty_string = f"{time_sign}{m:02d}m {s:02d}s"
            elif s > 0:
                pretty_string = f"{time_sign}{s:02d}s"
            else:
                pretty_string = "―"

        return self._color_dict["c"] + pretty_string + self._color_dict["R"]

    def _get_time_exceeded_string(self):
        if self.overflow:
            return (
                f"{self._color_dict['g']}{self.name} {self._color_dict['r']}⚠ TIME EXCEEDED!{self._color_dict['R']} "
                f"Elapsed: {self._get_display_string(self.time_elapsed)}"
                f", Overflowed: {self._get_display_string(abs(self.time_remaining))}"
            )
        elif self.predicted_overflow:
            predicted_overflow_time = self.time_elapsed + self.time_per_step
            return (
                f"{self._color_dict['g']}{self.name} {self._color_dict['r']}⚠ TIME EXCEEDED! [Predicted]{self._color_dict['R']} "
                f"Estimated: {self._get_display_string(predicted_overflow_time)}"
                f", Overflow: {self._get_display_string(predicted_overflow_time - self.quota)}"
            )

    def _get_info_string(self, track=False):
        info_string = (
            f"{self._color_dict['g']}{self.name} » {self._color_dict['R']}"
            f"remaining: {self._get_display_string(self.time_remaining)}"
            f", elapsed: {self._get_display_string(self.time_elapsed)}"
        )

        if track:
            info_string += (
                f", this step: {self._get_display_string(self.time_this_step)}"
                + f", per step: {self._get_display_string(self.time_per_step)}"
            )

        return info_string

    def update(
        self,
        *,
        verbose: bool = True,
    ) -> bool:
        """
        Updates the quota considering the time taken from its creation to call.

        Args:
            verbose (bool, optional): Enable or disable logging messages. Defaults to True.

        Returns:
            bool: States if quota is exceeded.
        """

        self._update_quota()

        if (self.verbose and verbose) and self.logger_fn is not None:
            if self.time_exceeded:
                self.logger_fn(self._get_time_exceeded_string())
            else:
                self.logger_fn(self._get_info_string())

        self.time_since = self.timer_fn()
        return self.time_exceeded

    def track(
        self,
        *,
        verbose: bool = True,
    ) -> bool:
        """
        Tracks, stores and updates the time taken every call, also used for quota overflow prediction. To be used in loops or repetitive calls.

        Args:
            verbose (bool, optional): Enable or disable logging messages. Defaults to True.
        Returns:
            bool: States if quota is exceeded.
        """

        self._update_quota(track=True)

        if (self.verbose and verbose) and self.logger_fn is not None:
            self.logger_fn(self._get_info_string(track=True))
            if self.time_exceeded:
                self.logger_fn(self._get_time_exceeded_string())

        self.time_since = self.timer_fn()
        return self.time_exceeded

    def iter(
        self,
        iterable: Iterable[Any],
        *,
        time_exceeded_fn: Optional[Callable] = None,
        time_exceeded_break: bool = True,
        verbose: bool = True,
    ) -> Iterator[Any]:
        """
        Time limited iterator of the iterable. When called, updates the quota and tracks time taken for each iteration, upon quota (predicted) exhaustion stops iteration (default).

        Iteration stops (default) under two conditions:
        (i) Exhaustion - Time taken by the iteration has already  exceeded the time quota and overflow occurs.
        (ii) Predicted exhaustion - The time taken by the next iteration (calculated by the *step_aggr_fn*) will exceed the time quota.

        Args:
            iterable (Iterable[Any]): Iterable to be iterated.
            time_exceeded_fn (Optional[Callable], optional): Function to be executed if time exceeds. Defaults to None.
            time_exceeded_break (bool, optional): To break out of the loop if time exceeds. Defaults to True.
            verbose (bool, optional): Enable or disable logging messages. Defaults to True.

        Yields:
            Iterator[Any]: Element in the iterable.
        """

        if not self.update(verbose=verbose):
            for i in iterable:
                yield i

                if self.track(verbose=verbose):
                    if time_exceeded_fn is not None:
                        time_exceeded_fn()

                    if time_exceeded_break:
                        break

    def range(
        self,
        *args: Any,
        time_exceeded_fn: Optional[Callable] = None,
        time_exceeded_break: bool = True,
        verbose: bool = True,
        **kwargs: Any,
    ) -> Iterator[int]:
        """
        Time limited range. When called, updates the quota and tracks time taken for each iteration, upon quota (predicted) exhaustion stops iteration (default).

        Iteration stops (default) under two conditions:
            (i) Exhaustion - Time taken by the iteration has already  exceeded the time quota and overflow occurs.
            (ii) Predicted exhaustion - The time taken by the next iteration (calculated by the *step_aggr_fn*) will exceed the time quota.

        Args:
            time_exceeded_fn (Optional[Callable], optional): Function to be executed if time exceeds. Defaults to None.
            time_exceeded_break (bool, optional): To break out of the loop if time exceeds. Defaults to True.
            verbose (bool, optional): Enable or disable logging messages. Defaults to True.

        Yields:
            Iterator[int]: Sequence in the given number range.
        """

        return self.iter(
            iterable=range(*args, **kwargs),
            time_exceeded_fn=time_exceeded_fn,
            time_exceeded_break=time_exceeded_break,
            verbose=verbose,
        )

    def __str__(
        self,
    ) -> str:
        headers = [
            f"{self._color_dict['g']}{self.name}{self._color_dict['R']}",
            f"{self._color_dict['y']}Time{self._color_dict['R']}",
            f"{self._color_dict['y']}Time ({self.display_unit}){self._color_dict['R']}",
        ]

        if self.overflow:
            time_exceeded_string = (
                self._color_dict["r"] + "True" + self._color_dict["R"]
            )
        elif self.predicted_overflow:
            time_exceeded_string = (
                self._color_dict["r"] + "Predicted" + self._color_dict["R"]
            )
        else:
            time_exceeded_string = (
                self._color_dict["c"] + "False" + self._color_dict["R"]
            )

        table = [
            [
                "Time Quota",
                self._get_pretty_string(self.quota, display=True),
                self._get_display_string(self.quota),
            ],
            [
                "Time Elapsed",
                self._get_pretty_string(self.time_elapsed, display=True),
                self._get_display_string(self.time_elapsed),
            ],
            [
                "Time Remaining",
                self._get_pretty_string(self.time_remaining, display=True),
                self._get_display_string(self.time_remaining),
            ],
            [
                "Time Per Step",
                self._get_pretty_string(self.time_per_step, display=True),
                self._get_display_string(self.time_per_step),
            ],
            [
                "Time Exceeded",
                time_exceeded_string,
                time_exceeded_string,
            ],
        ]

        return tabulate(
            table,
            headers,
            colalign=("left", "right", "right"),
            tablefmt="simple",
        )

    def __repr__(
        self,
    ) -> str:
        _quota = self.quota / _time_dict[self.unit]

        return (
            f"{self.__class__.__name__}"
            "("
            f"{_quota!r}, {self.unit!r}, {self.display_unit!r}, "
            f"name={self.name!r}, "
            f"step_aggr_fn={self.step_aggr_fn!r}, "
            f"timer_fn={self.timer_fn!r}, "
            f"logger_fn={self.logger_fn!r}, "
            f"precision={self.precision!r}, "
            f"color={any(self._color_dict.values())!r}, "
            f"verbose={self.verbose!r}"
            ")"
        )

Methods

def iter(self, iterable: Iterable[Any], *, time_exceeded_fn: Optional[Callable] = None, time_exceeded_break: bool = True, verbose: bool = True) ‑> Iterator[Any]

Time limited iterator of the iterable. When called, updates the quota and tracks time taken for each iteration, upon quota (predicted) exhaustion stops iteration (default).

Iteration stops (default) under two conditions: (i) Exhaustion - Time taken by the iteration has already exceeded the time quota and overflow occurs. (ii) Predicted exhaustion - The time taken by the next iteration (calculated by the step_aggr_fn) will exceed the time quota.

Args

iterable : Iterable[Any]
Iterable to be iterated.
time_exceeded_fn : Optional[Callable], optional
Function to be executed if time exceeds. Defaults to None.
time_exceeded_break : bool, optional
To break out of the loop if time exceeds. Defaults to True.
verbose : bool, optional
Enable or disable logging messages. Defaults to True.

Yields

Iterator[Any]
Element in the iterable.
Expand source code
def iter(
    self,
    iterable: Iterable[Any],
    *,
    time_exceeded_fn: Optional[Callable] = None,
    time_exceeded_break: bool = True,
    verbose: bool = True,
) -> Iterator[Any]:
    """
    Time limited iterator of the iterable. When called, updates the quota and tracks time taken for each iteration, upon quota (predicted) exhaustion stops iteration (default).

    Iteration stops (default) under two conditions:
    (i) Exhaustion - Time taken by the iteration has already  exceeded the time quota and overflow occurs.
    (ii) Predicted exhaustion - The time taken by the next iteration (calculated by the *step_aggr_fn*) will exceed the time quota.

    Args:
        iterable (Iterable[Any]): Iterable to be iterated.
        time_exceeded_fn (Optional[Callable], optional): Function to be executed if time exceeds. Defaults to None.
        time_exceeded_break (bool, optional): To break out of the loop if time exceeds. Defaults to True.
        verbose (bool, optional): Enable or disable logging messages. Defaults to True.

    Yields:
        Iterator[Any]: Element in the iterable.
    """

    if not self.update(verbose=verbose):
        for i in iterable:
            yield i

            if self.track(verbose=verbose):
                if time_exceeded_fn is not None:
                    time_exceeded_fn()

                if time_exceeded_break:
                    break
def range(self, *args: Any, time_exceeded_fn: Optional[Callable] = None, time_exceeded_break: bool = True, verbose: bool = True, **kwargs: Any) ‑> Iterator[int]

Time limited range. When called, updates the quota and tracks time taken for each iteration, upon quota (predicted) exhaustion stops iteration (default).

Iteration stops (default) under two conditions: (i) Exhaustion - Time taken by the iteration has already exceeded the time quota and overflow occurs. (ii) Predicted exhaustion - The time taken by the next iteration (calculated by the step_aggr_fn) will exceed the time quota.

Args

time_exceeded_fn : Optional[Callable], optional
Function to be executed if time exceeds. Defaults to None.
time_exceeded_break : bool, optional
To break out of the loop if time exceeds. Defaults to True.
verbose : bool, optional
Enable or disable logging messages. Defaults to True.

Yields

Iterator[int]
Sequence in the given number range.
Expand source code
def range(
    self,
    *args: Any,
    time_exceeded_fn: Optional[Callable] = None,
    time_exceeded_break: bool = True,
    verbose: bool = True,
    **kwargs: Any,
) -> Iterator[int]:
    """
    Time limited range. When called, updates the quota and tracks time taken for each iteration, upon quota (predicted) exhaustion stops iteration (default).

    Iteration stops (default) under two conditions:
        (i) Exhaustion - Time taken by the iteration has already  exceeded the time quota and overflow occurs.
        (ii) Predicted exhaustion - The time taken by the next iteration (calculated by the *step_aggr_fn*) will exceed the time quota.

    Args:
        time_exceeded_fn (Optional[Callable], optional): Function to be executed if time exceeds. Defaults to None.
        time_exceeded_break (bool, optional): To break out of the loop if time exceeds. Defaults to True.
        verbose (bool, optional): Enable or disable logging messages. Defaults to True.

    Yields:
        Iterator[int]: Sequence in the given number range.
    """

    return self.iter(
        iterable=range(*args, **kwargs),
        time_exceeded_fn=time_exceeded_fn,
        time_exceeded_break=time_exceeded_break,
        verbose=verbose,
    )
def reset(self) ‑> None

Resets time quota to initial values.

Expand source code
def reset(
    self,
) -> None:
    """
    Resets time quota to initial values.
    """

    self.time_elapsed: float = 0
    self.time_remaining: float = self.quota

    self.overflow: bool = False
    self.predicted_overflow: bool = False
    self.time_exceeded: bool = False

    self.time_steps: list[float] = []
    self.time_per_step: float = 0
    self.time_this_step: float = 0

    self.time_since: float = self.timer_fn()
def track(self, *, verbose: bool = True) ‑> bool

Tracks, stores and updates the time taken every call, also used for quota overflow prediction. To be used in loops or repetitive calls.

Args

verbose : bool, optional
Enable or disable logging messages. Defaults to True.

Returns

bool
States if quota is exceeded.
Expand source code
def track(
    self,
    *,
    verbose: bool = True,
) -> bool:
    """
    Tracks, stores and updates the time taken every call, also used for quota overflow prediction. To be used in loops or repetitive calls.

    Args:
        verbose (bool, optional): Enable or disable logging messages. Defaults to True.
    Returns:
        bool: States if quota is exceeded.
    """

    self._update_quota(track=True)

    if (self.verbose and verbose) and self.logger_fn is not None:
        self.logger_fn(self._get_info_string(track=True))
        if self.time_exceeded:
            self.logger_fn(self._get_time_exceeded_string())

    self.time_since = self.timer_fn()
    return self.time_exceeded
def update(self, *, verbose: bool = True) ‑> bool

Updates the quota considering the time taken from its creation to call.

Args

verbose : bool, optional
Enable or disable logging messages. Defaults to True.

Returns

bool
States if quota is exceeded.
Expand source code
def update(
    self,
    *,
    verbose: bool = True,
) -> bool:
    """
    Updates the quota considering the time taken from its creation to call.

    Args:
        verbose (bool, optional): Enable or disable logging messages. Defaults to True.

    Returns:
        bool: States if quota is exceeded.
    """

    self._update_quota()

    if (self.verbose and verbose) and self.logger_fn is not None:
        if self.time_exceeded:
            self.logger_fn(self._get_time_exceeded_string())
        else:
            self.logger_fn(self._get_info_string())

    self.time_since = self.timer_fn()
    return self.time_exceeded