[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