#!/usr/bin/env python
# cardinal_pythonlib/sizeformatter.py
"""
===============================================================================
Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).
This file is part of cardinal_pythonlib.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===============================================================================
"""
from typing import Union
[docs]def sizeof_fmt(num: float, suffix: str = "B") -> str:
"""
Formats a number of bytes in a human-readable binary format (e.g. ``2048``
becomes ``'2 KiB'``); from https://stackoverflow.com/questions/1094841.
"""
for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"):
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, "Yi", suffix)
# see: https://en.wikipedia.org/wiki/Binary_prefix
SYMBOLS = {
"customary": ("B", "K", "M", "G", "T", "P", "E", "Z", "Y"),
"customary_ext": (
"byte",
"kilo",
"mega",
"giga",
"tera",
"peta",
"exa",
"zetta",
"iotta",
),
"iec": ("Bi", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"),
"iec_ext": (
"byte",
"kibi",
"mebi",
"gibi",
"tebi",
"pebi",
"exbi",
"zebi",
"yobi",
),
}
[docs]def bytes2human(
n: Union[int, float],
format: str = "%(value).1f %(symbol)s",
symbols: str = "customary",
) -> str:
"""
Converts a number of bytes into a human-readable format.
From https://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/.
Args:
n: number of bytes
format: a format specification string
symbols: can be one of ``"customary"``, ``"customary_ext"``, ``"iec"``
or ``"iec_ext"``; see https://goo.gl/kTQMs
Returns:
the formatted number
Examples:
>>> bytes2human(0)
'0.0 B'
>>> bytes2human(0.9)
'0.0 B'
>>> bytes2human(1)
'1.0 B'
>>> bytes2human(1.9)
'1.0 B'
>>> bytes2human(1024)
'1.0 K'
>>> bytes2human(1048576)
'1.0 M'
>>> bytes2human(1099511627776127398123789121)
'909.5 Y'
>>> bytes2human(9856, symbols="customary")
'9.6 K'
>>> bytes2human(9856, symbols="customary_ext")
'9.6 kilo'
>>> bytes2human(9856, symbols="iec")
'9.6 Ki'
>>> bytes2human(9856, symbols="iec_ext")
'9.6 kibi'
>>> bytes2human(10000, "%(value).1f %(symbol)s/sec")
'9.8 K/sec'
>>> # precision can be adjusted by playing with %f operator
>>> bytes2human(10000, format="%(value).5f %(symbol)s")
'9.76562 K'
""" # noqa: E501
n = int(n)
if n < 0:
raise ValueError("n < 0")
symbols = SYMBOLS[symbols]
prefix = {}
for i, s in enumerate(symbols[1:]):
prefix[s] = 1 << (i + 1) * 10
for symbol in reversed(symbols[1:]):
if n >= prefix[symbol]:
value = float(n) / prefix[symbol]
return format % locals()
return format % dict(symbol=symbols[0], value=n)
[docs]def human2bytes(s: str) -> int:
"""
Modified from
https://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/.
Attempts to guess the string format based on default symbols
set and return the corresponding bytes as an integer.
When unable to recognize the format, :exc:`ValueError` is raised.
>>> human2bytes('0 B')
0
>>> human2bytes('1 K')
1024
>>> human2bytes('1 M')
1048576
>>> human2bytes('1 Gi')
1073741824
>>> human2bytes('1 tera')
1099511627776
>>> human2bytes('0.5kilo')
512
>>> human2bytes('0.1 byte')
0
>>> human2bytes('1 k') # k is an alias for K
1024
>>> human2bytes('12 foo')
Traceback (most recent call last):
...
ValueError: can't interpret '12 foo'
"""
if not s:
raise ValueError(f"Can't interpret {s!r} as integer")
try:
return int(s)
except ValueError:
pass
init = s
num = ""
while s and s[0:1].isdigit() or s[0:1] == ".":
num += s[0]
s = s[1:]
num = float(num)
letter = s.strip()
for name, sset in SYMBOLS.items():
if letter in sset:
break
else:
if letter == "k":
# treat 'k' as an alias for 'K' as per
# https://en.wikipedia.org/wiki/Binary_prefix
sset = SYMBOLS["customary"]
letter = letter.upper()
else:
raise ValueError("can't interpret %r" % init)
prefix = {sset[0]: 1}
for i, s in enumerate(sset[1:]):
prefix[s] = 1 << (i + 1) * 10
return int(num * prefix[letter])