[Kimchi-devel] [PATCH v2 1/2] Add function to convert data sizes

Crístian Viana vianac at linux.vnet.ibm.com
Tue Mar 3 19:42:57 UTC 2015


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'])
-- 
2.1.0




More information about the Kimchi-devel mailing list