"""
Conversion utilities for mapping memory, disk, core declarations from strings to numbers and vice versa.
Also contains general conversion functions
"""
import math
from typing import SupportsInt, Tuple, Union
# See https://en.wikipedia.org/wiki/Binary_prefix
BINARY_PREFIXES = ['ki', 'mi', 'gi', 'ti', 'pi', 'ei', 'kib', 'mib', 'gib', 'tib', 'pib', 'eib']
DECIMAL_PREFIXES = ['b', 'k', 'm', 'g', 't', 'p', 'e', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb']
VALID_PREFIXES = BINARY_PREFIXES + DECIMAL_PREFIXES
[docs]
def bytes_in_unit(unit: str = 'B') -> int:
num_bytes = 1
if unit.lower() in ['ki', 'kib']:
num_bytes = 1 << 10
if unit.lower() in ['mi', 'mib']:
num_bytes = 1 << 20
if unit.lower() in ['gi', 'gib']:
num_bytes = 1 << 30
if unit.lower() in ['ti', 'tib']:
num_bytes = 1 << 40
if unit.lower() in ['pi', 'pib']:
num_bytes = 1 << 50
if unit.lower() in ['ei', 'eib']:
num_bytes = 1 << 60
if unit.lower() in ['k', 'kb']:
num_bytes = 1000
if unit.lower() in ['m', 'mb']:
num_bytes = 1000 ** 2
if unit.lower() in ['g', 'gb']:
num_bytes = 1000 ** 3
if unit.lower() in ['t', 'tb']:
num_bytes = 1000 ** 4
if unit.lower() in ['p', 'pb']:
num_bytes = 1000 ** 5
if unit.lower() in ['e', 'eb']:
num_bytes = 1000 ** 6
return num_bytes
[docs]
def convert_units(num: float,
src_unit: str,
dst_unit: str = 'B') -> float:
"""Returns a float representing the converted input in dst_units."""
if not src_unit.lower() in VALID_PREFIXES:
raise RuntimeError(f"{src_unit} not a valid unit, valid units are {VALID_PREFIXES}.")
if not dst_unit.lower() in VALID_PREFIXES:
raise RuntimeError(f"{dst_unit} not a valid unit, valid units are {VALID_PREFIXES}.")
return (num * bytes_in_unit(src_unit)) / bytes_in_unit(dst_unit)
[docs]
def parse_memory_string(string: str) -> Tuple[float, str]:
"""
Given a string representation of some memory (i.e. '1024 Mib'), return the
number and unit.
"""
for i, character in enumerate(string):
# find the first character of the unit
if character not in '0123456789.-_ ':
units = string[i:].strip()
if not units.lower() in VALID_PREFIXES:
raise RuntimeError(f"{units} not a valid unit, valid units are {VALID_PREFIXES}.")
return float(string[:i]), units
return float(string), 'b'
[docs]
def human2bytes(string: str) -> int:
"""
Given a string representation of some memory (i.e. '1024 Mib'), return the
integer number of bytes.
"""
value, unit = parse_memory_string(string)
return int(convert_units(value, src_unit=unit, dst_unit='b'))
[docs]
def bytes2human(n: SupportsInt) -> str:
"""Return a binary value as a human readable string with units."""
n = int(n)
if n < 0:
raise ValueError("n < 0")
elif n < 1:
return '0 b'
power_level = math.floor(math.log(n, 1024))
units = ('b', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei')
unit = units[power_level if power_level < len(units) else -1]
value = convert_units(n, "b", unit)
return f'{value:.1f} {unit}'
[docs]
def b_to_mib(n: Union[int, float]) -> float:
"""
Convert a number from bytes to mibibytes.
"""
return convert_units(n, 'b', 'mib')
[docs]
def mib_to_b(n: Union[int, float]) -> float:
"""
Convert a number from mibibytes to bytes.
"""
return convert_units(n, 'mib', 'b')
#General Conversions
[docs]
def hms_duration_to_seconds(hms: str) -> float:
"""
Parses a given time string in hours:minutes:seconds,
returns an equivalent total seconds value
"""
vals_to_convert = hms.split(':')
seconds = 0.0
for val in vals_to_convert:
if(float(val) < 0):
raise ValueError("Invalid Time, negative value")
if(len(vals_to_convert) != 3):
raise ValueError("Invalid amount of fields, function takes input in 'hh:mm:ss'")
seconds += float(vals_to_convert[0]) * 60 * 60
seconds += float(vals_to_convert[1]) * 60
seconds += float(vals_to_convert[2])
return seconds