Module saev.helpers

Useful helpers for saev.

Functions

def flattened(dct: dict[str, object], *, sep: str = '.') ‑> dict[str, str | int | float | bool | None]

Flatten a potentially nested dict to a single-level dict with .-separated keys.

def get(dct: dict[str, object], key: str, *, sep: str = '.') ‑> object
def get_cache_dir() ‑> str

Get cache directory from environment variables, defaulting to the current working directory (.)

Returns

A path to a cache directory (might not exist yet).

Classes

class progress (it, *, every: int = 10, desc: str = 'progress', total: int = 0)

Wraps an iterable with a logger like tqdm but doesn't use any control codes to manipulate a progress bar, which doesn't work well when your output is redirected to a file. Instead, simple logging statements are used, but it includes quality-of-life features like iteration speed and predicted time to finish.

Args

it
Iterable to wrap.
every
How many iterations between logging progress.
desc
What to name the logger.
total
If non-zero, how long the iterable is.
Expand source code
@beartype.beartype
class progress:
    def __init__(self, it, *, every: int = 10, desc: str = "progress", total: int = 0):
        """
        Wraps an iterable with a logger like tqdm but doesn't use any control codes to manipulate a progress bar, which doesn't work well when your output is redirected to a file. Instead, simple logging statements are used, but it includes quality-of-life features like iteration speed and predicted time to finish.

        Args:
            it: Iterable to wrap.
            every: How many iterations between logging progress.
            desc: What to name the logger.
            total: If non-zero, how long the iterable is.
        """
        self.it = it
        self.every = every
        self.logger = logging.getLogger(desc)
        self.total = total

    def __iter__(self):
        start = time.time()

        try:
            total = len(self)
        except TypeError:
            total = None

        for i, obj in enumerate(self.it):
            yield obj

            if (i + 1) % self.every == 0:
                now = time.time()
                duration_s = now - start
                per_min = (i + 1) / (duration_s / 60)

                if total is not None:
                    pred_min = (total - (i + 1)) / per_min
                    self.logger.info(
                        "%d/%d (%.1f%%) | %.1f it/m (expected finish in %.1fm)",
                        i + 1,
                        total,
                        (i + 1) / total * 100,
                        per_min,
                        pred_min,
                    )
                else:
                    self.logger.info("%d/? | %.1f it/m", i + 1, per_min)

    def __len__(self) -> int:
        if self.total > 0:
            return self.total

        # Will throw exception.
        return len(self.it)