Source code for idiap_devtools.utils

# Copyright © 2022 Idiap Research Institute <contact@idiap.ch>
#
# SPDX-License-Identifier: BSD-3-Clause

import logging
import os
import subprocess
import sys
import time
import typing

logger = logging.getLogger(__name__)

_INTERVALS = (
    ("weeks", 604800),  # 60 * 60 * 24 * 7
    ("days", 86400),  # 60 * 60 * 24
    ("hours", 3600),  # 60 * 60
    ("minutes", 60),
    ("seconds", 1),
)
"""Time intervals that make up human readable time slots."""


[docs] def set_environment( name: str, value: str, env: dict[str, str] | os._Environ[str] = os.environ ) -> str: """Set up the environment variable and print debug message. Parameters ---------- name The name of the environment variable to set value The value to set the environment variable to env Optional environment (dictionary) where to set the variable at Returns ------- The value just set. """ env[name] = value logger.info(f"environ['{name}'] = '{value}'") return value
[docs] def human_time(seconds: int | float, granularity: int = 2) -> str: """Return a human readable time string like "1 day, 2 hours". This function will convert the provided time in seconds into weeks, days, hours, minutes and seconds. Parameters ---------- seconds The number of seconds to convert granularity The granularity corresponds to how many elements will output. For a granularity of 2, only the first two non-zero entries are output. Returns ------- A string, that contains the human readable time. """ result: list[str | None] = [] for name, count in _INTERVALS: value = seconds // count if value: seconds -= value * count if value == 1: name = name.rstrip("s") result.append(f"{int(value)} {name}") else: # Add a blank if we're in the middle of other values if len(result) > 0: result.append(None) if not result: if seconds < 1.0: return "%.2f seconds" % seconds if seconds == 1: return "1 second" return "%d seconds" % seconds return ", ".join([x for x in result[:granularity] if x is not None])
[docs] def run_cmdline( cmd: list[str], logger: logging.Logger, **kwargs, ) -> int: """Run a command on a environment, logs output and reports status. Parameters ---------- cmd The command to run, with parameters separated on a list of strings logger A logger to log messages to console kwargs Further kwargs to be set on the call to :py:class:`subprocess.Popen`. Returns ------- The exit status of the command. """ logger.info("(system) %s" % " ".join(cmd)) start = time.time() p = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True, **kwargs, ) for line in iter(p.stdout.readline, ""): sys.stdout.write(line) sys.stdout.flush() if p.wait() != 0: raise RuntimeError( "command `%s' exited with error state (%d)" % (" ".join(cmd), p.returncode) ) total = time.time() - start logger.info("command took %s" % human_time(total)) return p.pid
[docs] def uniq( seq: list[typing.Any], idfun: typing.Callable | None = None ) -> list[typing.Any]: """Very fast, order preserving uniq function.""" # order preserving idfun = idfun or (lambda x: x) seen = {} result = [] for item in seq: marker = idfun(item) # in old Python versions: # if seen.has_key(marker) # but in new ones: if marker in seen: continue seen[marker] = 1 result.append(item) return result