[Kimchi-devel] [PATCH v2 1/2] Add function to convert data sizes
Paulo Ricardo Paz Vital
pvital at gmail.com
Wed Mar 4 08:02:33 UTC 2015
Reviewed-by: Paulo Vital <pvital at gmail.com>
Tested-by: Paulo Vital <pvital at gmail.com>
On Tue, 2015-03-03 at 16:42 -0300, Crístian Viana wrote:
> The new function "kimchi.utils.convert_data_size" can be used to convert
> values from different units, e.g. converting 5 GiB to MiB, 1 GiB to B.
>
> Signed-off-by: Crístian Viana <vianac at linux.vnet.ibm.com>
> ---
> src/kimchi/i18n.py | 2 +
> src/kimchi/utils.py | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> tests/test_utils.py | 69 +++++++++++++++++++++++++
> 3 files changed, 216 insertions(+)
> create mode 100644 tests/test_utils.py
>
> diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
> index 2af0be6..df5422f 100644
> --- a/src/kimchi/i18n.py
> +++ b/src/kimchi/i18n.py
> @@ -267,6 +267,8 @@ messages = {
> "KCHUTILS0001E": _("Invalid URI %(uri)s"),
> "KCHUTILS0002E": _("Timeout while running command '%(cmd)s' after %(seconds)s seconds"),
> "KCHUTILS0003E": _("Unable to choose a virtual machine name"),
> + "KCHUTILS0004E": _("Invalid data value '%(value)s'"),
> + "KCHUTILS0005E": _("Invalid data unit '%(unit)s'"),
>
> "KCHVMSTOR0002E": _("Invalid storage type. Types supported: 'cdrom', 'disk'"),
> "KCHVMSTOR0003E": _("The path '%(value)s' is not a valid local/remote path for the device"),
> diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py
> index fc5245f..f1ef12c 100644
> --- a/src/kimchi/utils.py
> +++ b/src/kimchi/utils.py
> @@ -388,3 +388,148 @@ def get_unique_file_name(all_names, name):
> max_num = max(max_num, int(match.group(re_group_num)))
>
> return u'%s (%d)' % (name, max_num + 1)
> +
> +
> +def convert_data_size(value, from_unit, to_unit='B'):
> + """Convert a data value from one unit to another unit
> + (e.g. 'MiB' -> 'GiB').
> +
> + The data units supported by this function are made up of one prefix and one
> + suffix. The valid prefixes are those defined in the SI (i.e. metric system)
> + and those defined by the IEC, and the valid suffixes indicate if the base
> + unit is bit or byte.
> + Take a look at the tables below for the possible values:
> +
> + Prefixes:
> +
> + ================================== ===================================
> + PREFIX (SI) | DESCRIPTION | VALUE PREFIX (IEC) | DESCRIPTION | VALUE
> + ================================== ===================================
> + k | kilo | 1000 Ki | kibi | 1024
> + ---------------------------------- -----------------------------------
> + M | mega | 1000^2 Mi | mebi | 1024^2
> + ---------------------------------- -----------------------------------
> + G | giga | 1000^3 Gi | gibi | 1024^3
> + ---------------------------------- -----------------------------------
> + T | tera | 1000^4 Ti | tebi | 1024^4
> + ---------------------------------- -----------------------------------
> + P | peta | 1000^5 Pi | pebi | 1024^5
> + ---------------------------------- -----------------------------------
> + E | exa | 1000^6 Ei | exbi | 1024^6
> + ---------------------------------- -----------------------------------
> + Z | zetta | 1000^7 Zi | zebi | 1024^7
> + ---------------------------------- -----------------------------------
> + Y | yotta | 1000^8 Yi | yobi | 1024^8
> + ================================== ===================================
> +
> + Suffixes:
> +
> + =======================
> + SUFFIX | DESCRIPTION
> + =======================
> + b | bit
> + -----------------------
> + B | byte (default)
> + =======================
> +
> + See http://en.wikipedia.org/wiki/Binary_prefix for more details on
> + those units.
> +
> + If a wrong unit is provided, an error will be raised.
> +
> + Examples:
> + convert_data_size(5, 'MiB', 'KiB') -> 5120.0
> + convert_data_size(5, 'MiB', 'M') -> 5.24288
> + convert_data_size(5, 'MiB', 'GiB') -> 0.0048828125
> + convert_data_size(5, 'MiB', 'Tb') -> 4.194304e-05
> + convert_data_size(5, 'MiB') -> 5242880.0
> + convert_data_size(5, 'mib') -> #ERROR# (invalid from_unit)
> +
> + Parameters:
> + value -- the value to be converted, in the unit specified by 'from_unit'.
> + this parameter can be of any type which can be cast to float
> + (e.g. int, float, str).
> + from_unit -- the unit of 'value', as described above.
> + to_unit -- the unit of the return value, as described above.
> +
> + Return:
> + A float number representing 'value' (in 'from_unit') converted
> + to 'to_unit'.
> + """
> + SI_PREFIXES = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
> + # The IEC prefixes are the equivalent SI prefixes + 'i'
> + # but, exceptionally, 'k' becomes 'Ki' instead of 'ki'.
> + IEC_PREFIXES = map(lambda p: 'Ki' if p == 'k' else p + 'i', SI_PREFIXES)
> + PREFIXES_BY_BASE = {1000: SI_PREFIXES,
> + 1024: IEC_PREFIXES}
> +
> + SUFFIXES_WITH_MULT = {'b': 1,
> + 'B': 8}
> + DEFAULT_SUFFIX = 'B'
> +
> + if not from_unit:
> + raise InvalidParameter('KCHUTILS0005E', {'unit': from_unit})
> + if not to_unit:
> + raise InvalidParameter('KCHUTILS0005E', {'unit': to_unit})
> +
> + # set the default suffix
> + if from_unit[-1] not in SUFFIXES_WITH_MULT:
> + from_unit += DEFAULT_SUFFIX
> + if to_unit[-1] not in SUFFIXES_WITH_MULT:
> + to_unit += DEFAULT_SUFFIX
> +
> + # split prefix and suffix for better parsing
> + from_p = from_unit[:-1]
> + from_s = from_unit[-1]
> + to_p = to_unit[:-1]
> + to_s = to_unit[-1]
> +
> + # validate parameters
> + try:
> + value = float(value)
> + except TypeError:
> + raise InvalidParameter('KCHUTILS0004E', {'value': value})
> + if from_p != '' and from_p not in (SI_PREFIXES + IEC_PREFIXES):
> + raise InvalidParameter('KCHUTILS0005E', {'unit': from_unit})
> + if from_s not in SUFFIXES_WITH_MULT:
> + raise InvalidParameter('KCHUTILS0005E', {'unit': from_unit})
> + if to_p != '' and to_p not in (SI_PREFIXES + IEC_PREFIXES):
> + raise InvalidParameter('KCHUTILS0005E', {'unit': to_unit})
> + if to_s not in SUFFIXES_WITH_MULT:
> + raise InvalidParameter('KCHUTILS0005E', {'unit': to_unit})
> +
> + # if the units are the same, return the input value
> + if from_unit == to_unit:
> + return value
> +
> + # convert 'value' to the most basic unit (bits)...
> + bits = value
> +
> + for suffix, mult in SUFFIXES_WITH_MULT.iteritems():
> + if from_s == suffix:
> + bits *= mult
> + break
> +
> + if from_p != '':
> + for base, prefixes in PREFIXES_BY_BASE.iteritems():
> + for i, p in enumerate(prefixes):
> + if from_p == p:
> + bits *= base**(i + 1)
> + break
> +
> + # ...then convert the value in bits to the destination unit
> + ret = bits
> +
> + for suffix, mult in SUFFIXES_WITH_MULT.iteritems():
> + if to_s == suffix:
> + ret /= float(mult)
> + break
> +
> + if to_p != '':
> + for base, prefixes in PREFIXES_BY_BASE.iteritems():
> + for i, p in enumerate(prefixes):
> + if to_p == p:
> + ret /= float(base)**(i + 1)
> + break
> +
> + return ret
> diff --git a/tests/test_utils.py b/tests/test_utils.py
> new file mode 100644
> index 0000000..b8ff621
> --- /dev/null
> +++ b/tests/test_utils.py
> @@ -0,0 +1,69 @@
> +#
> +# Project Kimchi
> +#
> +# Copyright IBM, Corp. 2015
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> +
> +import unittest
> +
> +from kimchi.exception import InvalidParameter
> +from kimchi.utils import convert_data_size
> +
> +
> +class UtilsTests(unittest.TestCase):
> + def test_convert_data_size(self):
> + failure_data = [{'val': None, 'from': 'MiB'},
> + {'val': self, 'from': 'MiB'},
> + {'val': 1, 'from': None},
> + {'val': 1, 'from': ''},
> + {'val': 1, 'from': 'foo'},
> + {'val': 1, 'from': 'kib'},
> + {'val': 1, 'from': 'MiB', 'to': None},
> + {'val': 1, 'from': 'MiB', 'to': ''},
> + {'val': 1, 'from': 'MiB', 'to': 'foo'},
> + {'val': 1, 'from': 'MiB', 'to': 'kib'}]
> +
> + for d in failure_data:
> + if 'to' in d:
> + self.assertRaises(InvalidParameter, convert_data_size,
> + d['val'], d['from'], d['to'])
> + else:
> + self.assertRaises(InvalidParameter, convert_data_size,
> + d['val'], d['from'])
> +
> + success_data = [{'got': convert_data_size(5, 'MiB', 'MiB'),
> + 'want': 5},
> + {'got': convert_data_size(5, 'MiB', 'KiB'),
> + 'want': 5120},
> + {'got': convert_data_size(5, 'MiB', 'M'),
> + 'want': 5.24288},
> + {'got': convert_data_size(5, 'MiB', 'GiB'),
> + 'want': 0.0048828125},
> + {'got': convert_data_size(5, 'MiB', 'Tb'),
> + 'want': 4.194304e-05},
> + {'got': convert_data_size(5, 'KiB', 'MiB'),
> + 'want': 0.0048828125},
> + {'got': convert_data_size(5, 'M', 'MiB'),
> + 'want': 4.76837158203125},
> + {'got': convert_data_size(5, 'GiB', 'MiB'),
> + 'want': 5120},
> + {'got': convert_data_size(5, 'Tb', 'MiB'),
> + 'want': 596046.4477539062},
> + {'got': convert_data_size(5, 'MiB'),
> + 'want': convert_data_size(5, 'MiB', 'B')}]
> +
> + for d in success_data:
> + self.assertEquals(d['got'], d['want'])
More information about the Kimchi-devel
mailing list