[master 3/3] Make sure we use the Size class properly
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
It's no longer a Decimal instance.
---
blivet/devices/partition.py | 2 +-
blivet/formats/fs.py | 2 +-
tests/formats_test/misc_test.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/blivet/devices/partition.py b/blivet/devices/partition.py
index 121e2b0..3306ff3 100644
--- a/blivet/devices/partition.py
+++ b/blivet/devices/partition.py
@@ -624,7 +624,7 @@ def _compute_resize(self, partition, newsize=None):
# compute new size for partition
current_geom = partition.geometry
current_dev = current_geom.device
- new_len = int(newsize // Size(current_dev.sectorSize))
+ new_len = int(Size(newsize) // Size(current_dev.sectorSize))
new_geometry = parted.Geometry(device=current_dev,
start=current_geom.start,
length=new_len)
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
index 4d5bd66..67517d6 100644
--- a/blivet/formats/fs.py
+++ b/blivet/formats/fs.py
@@ -196,7 +196,7 @@ def get_required_size(cls, free_space):
# usable_size = device_size * _metadata_size_factor
# we can change this to get device size with required usable_size
# device_size = usable_size / _metadata_size_factor
- return Size(Decimal(free_space) / Decimal(cls._metadata_size_factor))
+ return Size(Decimal(int(free_space)) / Decimal(cls._metadata_size_factor))
@classmethod
def biggest_overhead_FS(cls, fs_list=None):
diff --git a/tests/formats_test/misc_test.py b/tests/formats_test/misc_test.py
index 162d17e..a24ff0c 100644
--- a/tests/formats_test/misc_test.py
+++ b/tests/formats_test/misc_test.py
@@ -10,7 +10,7 @@ class FSOverheadTestCase(unittest.TestCase):
def test_required_size_FS(self):
# FS is abstract parent which doesn't have metadata
self.assertEqual(FS.get_required_size(Size("100 MiB")), Size("100 MiB"))
- self.assertEqual(Ext2FS.get_required_size(Size("100 MiB")), Size(Decimal(Size("100 MiB")) / Decimal(0.93)))
+ self.assertEqual(Ext2FS.get_required_size(Size("100 MiB")), Size(Decimal(int(Size("100 MiB"))) / Decimal(0.93)))
def test_biggest_overhead_FS(self):
self.assertTrue(FS.biggest_overhead_FS() is BTRFS)
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/9cceb1c886e866286f1d6ed90c9a...
8 years, 3 months
[master 2/3] Don't store size as int in SameSizeSet
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
This attribute is directly assigned to things like PartitionDevice.req_max_size
which should be an instance of Size. Also a general rule of thumb is to store
sizes as instances of Size so we should do it here too.
I don't really know why we ever converted the value to int() in that place, but
tests seem to pass just fine without the conversion so I guess everything's just
fine.
---
blivet/partitioning.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/blivet/partitioning.py b/blivet/partitioning.py
index d99dc7d..37a19e1 100644
--- a/blivet/partitioning.py
+++ b/blivet/partitioning.py
@@ -1617,7 +1617,7 @@ def __init__(self, devices, size, grow=False, max_size=None):
self.devices.append(partition)
- self.size = int(size / len(devices))
+ self.size = size / len(devices)
self.grow = grow
self.max_size = max_size
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/ea1eabdfde97c8ae434e25802ee9...
8 years, 3 months
[master 1/3] Use libbytesize's Size class as a base for our Size
class
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
libbytesize provides an implementation of what we need for manipulations and
calculations with sizes in bytes. It's a little bit faster and more importantly
it can be shared with other projects because it's written in C. Thus we should
use it.
At some point it would be nice if we could drop our own Size class and the
blivet/size.py module altogether because they don't bring any extra value, but
for now we need this thin wrapper to keep the API (almost*) stable. The only
real changes are:
1) Strings like "100 kibibytes" are no longer accepted, I doubt anybody has ever
used this functionality. If the opposite turns out to be true such support will
be added to libbytesize and no change will be needed in Blivet.
2) human_readable() now ignores the 'skip' and 'min_value' attributes. The
former one is always replaced by 'True' (i.e. trailing zeroes are always
stripped), the latter one is ignored because it made things really cryptic and
weird. There's the new human_readable() method which should be used, at least by
the newly-written code.
* see the commit, mainly the part modifying/removing the tests for more details
---
blivet/size.py | 486 ++++++++---------------------------------------------
python-blivet.spec | 2 +
tests/size_test.py | 221 ++++++------------------
3 files changed, 122 insertions(+), 587 deletions(-)
diff --git a/blivet/size.py b/blivet/size.py
index cca9d85..212d4e9 100644
--- a/blivet/size.py
+++ b/blivet/size.py
@@ -19,162 +19,18 @@
#
# Red Hat Author(s): David Cantrell <dcantrell(a)redhat.com>
-import re
-import string # pylint: disable=deprecated-module
-import locale
-import sys
-from collections import namedtuple
-
-from decimal import Decimal
-from decimal import InvalidOperation
-from decimal import ROUND_DOWN, ROUND_UP, ROUND_HALF_UP
import six
-from .errors import SizePlacesError
-from .i18n import _, N_
-from .util import stringize, unicodeize
-
-ROUND_DEFAULT = ROUND_HALF_UP
-
-# Container for size unit prefix information
-_Prefix = namedtuple("Prefix", ["factor", "prefix", "abbr"])
-
-_DECIMAL_FACTOR = 10 ** 3
-_BINARY_FACTOR = 2 ** 10
-
-# TRANSLATORS: 'B' for "Bytes"
-_BYTES_SYMBOL = N_("B")
-_BYTES_WORDS = (N_("bytes"), N_("byte"))
-
-# Symbolic constants for units
-B = _Prefix(1, "", "")
-
-KB = _Prefix(_DECIMAL_FACTOR ** 1,
- N_("kilo"),
- # TRANSLATORS: "k" for "kilo-" (x 10^3)
- N_("k"))
-
-MB = _Prefix(_DECIMAL_FACTOR ** 2,
- N_("mega"),
- # TRANSLATORS: "M" for "mega-" (x 10^6)
- N_("M"))
-
-GB = _Prefix(_DECIMAL_FACTOR ** 3,
- N_("giga"),
- # TRANSLATORS: "G" for "giga-" (x 10^9)
- N_("G"))
-
-TB = _Prefix(_DECIMAL_FACTOR ** 4,
- N_("tera"),
- # TRANSLATORS: "T" for "tera-" (x 10^12)
- N_("T"))
-
-PB = _Prefix(_DECIMAL_FACTOR ** 5,
- N_("peta"),
- # TRANSLATORS: "P" for "peta-" (x 10^15)
- N_("P"))
-
-EB = _Prefix(_DECIMAL_FACTOR ** 6,
- N_("exa"),
- # TRANSLATORS: "E" for "exa-" (x 10^18)
- N_("E"))
-
-ZB = _Prefix(_DECIMAL_FACTOR ** 7,
- N_("zetta"),
- # TRANSLATORS: "Z" for "zetta-" (x 10^21)
- N_("Z"))
-
-YB = _Prefix(_DECIMAL_FACTOR ** 8,
- N_("yotta"),
- # TRANSLATORS: "Y" for "yotta-" (x 10^24)
- N_("Y"))
-
-KiB = _Prefix(_BINARY_FACTOR ** 1,
- N_("kibi"),
- # TRANSLATORS: "Ki" for "kibi-" (x 2^10)
- N_("Ki"))
-
-MiB = _Prefix(_BINARY_FACTOR ** 2,
- N_("mebi"),
- # TRANSLATORS: "Mi" for "mebi-" (x 2^20)
- N_("Mi"))
-
-GiB = _Prefix(_BINARY_FACTOR ** 3,
- N_("gibi"),
- # TRANSLATORS: "Gi" for "gibi-" (x 2^30)
- N_("Gi"))
-
-TiB = _Prefix(_BINARY_FACTOR ** 4,
- N_("tebi"),
- # TRANSLATORS: "Ti" for "tebi-" (x 2^40)
- N_("Ti"))
-
-PiB = _Prefix(_BINARY_FACTOR ** 5,
- N_("pebi"),
- # TRANSLATORS: "Pi" for "pebi-" (x 2^50)
- N_("Pi"))
-
-EiB = _Prefix(_BINARY_FACTOR ** 6,
- N_("exbi"),
- # TRANSLATORS: "Ei" for "exbi-" (x 2^60)
- N_("Ei"))
-
-ZiB = _Prefix(_BINARY_FACTOR ** 7,
- N_("zebi"),
- # TRANSLATORS: "Zi" for "zebi-" (x 2^70)
- N_("Zi"))
-
-YiB = _Prefix(_BINARY_FACTOR ** 8,
- N_("yobi"),
- # TRANSLATORS: "Yi" for "yobi-" (x 2^80)
- N_("Yi"))
-
-# Categories of symbolic constants
-_DECIMAL_PREFIXES = [KB, MB, GB, TB, PB, EB, ZB, YB]
-_BINARY_PREFIXES = [KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB]
-_EMPTY_PREFIX = B
-
-if six.PY2:
- _ASCIIlower_table = string.maketrans(string.ascii_uppercase, string.ascii_lowercase) # pylint: disable=no-member
-else:
- _ASCIIlower_table = str.maketrans(string.ascii_uppercase, string.ascii_lowercase) # pylint: disable=no-member
-
-
-def _lower_ascii(s):
- """Convert a string to lowercase using only ASCII character definitions.
-
- :param s: string instance to convert
- :type s: str or bytes
- :returns: lower-cased string
- :rtype: str
- """
-
- # XXX: Python 3 has str.maketrans() and bytes.maketrans() so we should
- # ideally use one or the other depending on the type of 's'. But it turns
- # out we expect this function to always return string even if given bytes.
- if not six.PY2 and isinstance(s, bytes):
- s = s.decode(sys.getdefaultencoding())
- return s.translate(_ASCIIlower_table) # pylint: disable=no-member
-
+from bytesize import bytesize
-def _make_spec(prefix, suffix, xlate, lowercase=True):
- """ Synthesizes a whole word from prefix and suffix.
+# we just need to make these objects available here
+# pylint: disable=unused-import
+from bytesize.bytesize import B, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB, KB, MB, GB, TB, PB, EB, ZB, YB
+from bytesize.bytesize import ROUND_UP, ROUND_DOWN
- :param str prefix: a prefix
- :param str suffixes: a suffix
- :param bool xlate: if True, treat as locale specific
- :param bool lowercase: if True, make all lowercase
-
- :returns: whole word
- :rtype: str
- """
- if xlate:
- word = (_(prefix) + _(suffix))
- return word.lower() if lowercase else word
- else:
- word = prefix + suffix
- return _lower_ascii(word) if lowercase else word
+from .errors import SizePlacesError
+ROUND_DEFAULT = ROUND_UP
def unit_str(unit, xlate=False):
""" Return a string representation of unit.
@@ -184,134 +40,9 @@ def unit_str(unit, xlate=False):
:rtype: some kind of string type
:returns: string representation of unit
"""
- return _make_spec(unit.abbr, _BYTES_SYMBOL, xlate, lowercase=False)
-
-
-def parse_units(spec, xlate):
- """ Parse a unit specification and return corresponding factor.
-
- :param spec: a units specifier
- :type spec: any type of string like object
- :param bool xlate: if True, assume locale specific
-
- :returns: a named constant corresponding to spec, if found
- :rtype: _Prefix or NoneType
-
- Looks first for exact matches for a specifier, but, failing that,
- searches for partial matches for abbreviations.
-
- Normalizes units to lowercase, e.g., MiB and mib are treated the same.
- """
- if spec == "":
- return B
-
- if xlate:
- spec = spec.lower()
- else:
- spec = _lower_ascii(spec)
-
- # Search for complete matches
- for unit in [_EMPTY_PREFIX] + _BINARY_PREFIXES + _DECIMAL_PREFIXES:
- if spec == _make_spec(unit.abbr, _BYTES_SYMBOL, xlate) or \
- spec in (_make_spec(unit.prefix, s, xlate) for s in _BYTES_WORDS):
- return unit
-
- # Search for unambiguous partial match among binary abbreviations
- matches = [p for p in _BINARY_PREFIXES if _make_spec(p.abbr, "", xlate).startswith(spec)]
- if len(matches) == 1:
- return matches[0]
-
- return None
-
-
-def parse_spec(spec):
- """ Parse string representation of size.
-
- :param spec: the specification of a size with, optionally, units
- :type spec: any type of string like object
- :returns: numeric value of the specification in bytes
- :rtype: Decimal
-
- :raises ValueError: if spec is unparseable
-
- Tries to parse the spec first as English, if that fails, as
- a locale specific string.
- """
-
- if not spec:
- raise ValueError("invalid size specification", spec)
-
- # Replace the localized radix character with a .
- radix = locale.nl_langinfo(locale.RADIXCHAR)
- if radix != '.':
- spec = spec.replace(radix, '.')
-
- # The purpose of this regular expression is to distinguish
- # between the numeric part and the part that specifies the units.
- # The regular expression that matches the numeric part of the spec
- # should recognize all valid numbers and should not include any part
- # of the unit specifier in the string that it recognizes. It may
- # recognize strings that are not valid numbers as well, if it does
- # some other part of the implementation is expected to recognize that
- # the number is invalid and raise an exception.
- # Specifically, the string "0.9.9 KiB" will be matched, and the numeric
- # part will match "0.9.9". This is not a valid number, but that will
- # be detected when an exception is raised during conversion of the numeric
- # part to a numeric value.
- spec_re = re.compile(
- r"""(?P<numeric> # the numeric part consists of three parts, below
- (-|\+)? # optional sign character
- (?P<base>([0-9\.]+)) # the base
- (?P<exp>(e|E)(-|\+)[0-9]+)?) # optional exponent
- \s* # whitespace
- (?P<rest>[^\s]*$) # the units specification
- """,
- re.VERBOSE
- )
- m = re.match(spec_re, spec.strip())
- if not m:
- raise ValueError("invalid size specification", spec)
-
- try:
- size = Decimal(m.group('numeric'))
- except InvalidOperation:
- raise ValueError("invalid size specification", spec)
-
- specifier = m.group('rest')
-
- # First try to parse as English.
- try:
- if six.PY2:
- spec_ascii = str(specifier.decode("ascii"))
- else:
- spec_ascii = bytes(specifier, 'ascii')
- except (UnicodeDecodeError, UnicodeEncodeError):
- # String contains non-ascii characters, so can not be English.
- pass
- else:
- unit = parse_units(spec_ascii, False)
- if unit is not None:
- return size * unit.factor
-
- # No English match found, try localized size specs.
- if six.PY2:
- # pylint: disable=undefined-variable
- if isinstance(specifier, unicode):
- spec_local = specifier
- else:
- spec_local = specifier.decode("utf-8")
- else:
- spec_local = specifier
-
- unit = parse_units(spec_local, True)
- if unit is not None:
- return size * unit.factor
-
- raise ValueError("invalid size specification", spec)
-
-
-class Size(Decimal):
+ return bytesize.unit_str(unit, xlate)
+class Size(bytesize.Size):
""" Common class to represent storage device and filesystem sizes.
Can handle parsing strings such as 45MB or 6.7GB to initialize
itself, or can be initialized with a numerical size in bytes.
@@ -319,98 +50,52 @@ class Size(Decimal):
decimal places.
"""
- def __new__(cls, value=0, context=None):
- """ Initialize a new Size object. Must pass a bytes or a spec value
- for size. The bytes value is a numerical value for the size
- this object represents, in bytes. The spec value is a string
- specification of the size using any of the size specifiers in the
- _DECIMAL_PREFIXES or _BINARY_PREFIXES lists combined with a 'b' or
- 'B'. For example, to specify 640 kilobytes, you could pass any of
- these spec parameters:
-
- "640kb"
- "640 kb"
- "640KB"
- "640 KB"
- "640 kilobytes"
-
- If you want to use a spec value to represent a bytes value,
- you can use the letter 'b' or 'B' or omit the size specifier.
- """
- if isinstance(value, (six.string_types, bytes)):
- size = parse_spec(value)
- elif isinstance(value, (six.integer_types, float, Decimal)):
- size = Decimal(value)
- elif isinstance(value, Size):
- size = Decimal(value.convert_to())
- else:
- raise ValueError("invalid value %s for size" % value)
-
- # drop any partial byte
- size = size.to_integral_value(rounding=ROUND_DOWN)
- self = Decimal.__new__(cls, value=size, context=context)
- return self
-
- # Force str and unicode types since the translated sizespec may be unicode
- def _to_string(self):
- return self.human_readable()
-
- def __str__(self, eng=False, context=None):
- return stringize(self._to_string())
-
- def __unicode__(self):
- return unicodeize(self._to_string())
-
- def __repr__(self):
- return "Size('%s')" % self
-
- def __deepcopy__(self, memo):
- return Size(self.convert_to())
-
- # pickling support for Size
- # see https://docs.python.org/3/library/pickle.html#object.__reduce__
- def __reduce__(self):
- return (self.__class__, (self.convert_to(),))
+ def __abs__(self):
+ return Size(bytesize.Size.__abs__(self))
def __add__(self, other, context=None):
- # because float is not automatically converted to Decimal type
- if isinstance(other, float):
- other = Decimal(str(other))
- return Size(Decimal.__add__(self, other))
+ return Size(bytesize.Size.__add__(self, other))
# needed to make sum() work with Size arguments
def __radd__(self, other, context=None):
- if isinstance(other, float):
- other = Decimal(str(other))
- return Size(Decimal.__radd__(self, other))
+ return Size(bytesize.Size.__radd__(self, other))
def __sub__(self, other, context=None):
- if isinstance(other, float):
- other = Decimal(str(other))
- return Size(Decimal.__sub__(self, other))
+ return Size(bytesize.Size.__sub__(self, other))
+
+ def __rsub__(self, other, context=None):
+ return Size(bytesize.Size.__rsub__(self, other))
def __mul__(self, other, context=None):
- if isinstance(other, float):
- other = Decimal(str(other))
- return Size(Decimal.__mul__(self, other))
+ return Size(bytesize.Size.__mul__(self, other))
__rmul__ = __mul__
- def __div__(self, other):
- if six.PY2:
- # This still needs to be ignored by pylint, because it will get
- # through the above guard.
- return Size(Decimal.__div__(self, other)) # pylint: disable=no-member
- else:
- raise AttributeError
+ def __div__(self, other, context=None):
+ ret = bytesize.Size.__div__(self, other) # pylint: disable=no-member
+ if isinstance(ret, bytesize.Size):
+ ret = Size(ret)
+
+ return ret
def __truediv__(self, other, context=None):
- return Size(Decimal.__truediv__(self, other))
+ ret = bytesize.Size.__truediv__(self, other)
+ if isinstance(ret, bytesize.Size):
+ ret = Size(ret)
+
+ return ret
def __floordiv__(self, other, context=None):
- return Size(Decimal.__floordiv__(self, other))
+ ret = bytesize.Size.__floordiv__(self, other)
+ if isinstance(ret, bytesize.Size):
+ ret = Size(ret)
+
+ return ret
def __mod__(self, other, context=None):
- return Size(Decimal.__mod__(self, other))
+ return Size(bytesize.Size.__mod__(self, other))
+
+ def __deepcopy__(self, memo_dict):
+ return Size(bytesize.Size.__deepcopy__(self, memo_dict))
def convert_to(self, spec=None):
""" Return the size in the units indicated by the specifier.
@@ -424,13 +109,12 @@ def convert_to(self, spec=None):
.. versionadded:: 1.6
spec parameter may be Size as well as units specifier.
"""
+ if isinstance(spec, Size):
+ if spec == Size(0):
+ raise ValueError("cannot convert to 0 size")
+ return bytesize.Size.__truediv__(self, spec)
spec = B if spec is None else spec
- factor = Decimal(getattr(spec, "factor", spec))
-
- if factor <= 0:
- raise ValueError("invalid conversion unit: %s" % factor)
-
- return Decimal(self) / factor
+ return bytesize.Size.convert_to(self, spec)
def human_readable(self, max_places=2, strip=True, min_value=1, xlate=True):
""" Return a string representation of this size with appropriate
@@ -442,8 +126,8 @@ def human_readable(self, max_places=2, strip=True, min_value=1, xlate=True):
:param max_places: number of decimal places to use, default is 2
:type max_places: an integer type or NoneType
- :param bool strip: True if trailing zeros are to be stripped.
- :param min_value: Lower bound for value, default is 1.
+ :param bool strip: True if trailing zeros are to be stripped (see warning below).
+ :param min_value: Lower bound for value, default is 1 (see warning below).
:type min_value: A precise numeric type: int, long, or Decimal
:param bool xlate: If True, translate for current locale
:returns: a representation of the size
@@ -452,78 +136,42 @@ def human_readable(self, max_places=2, strip=True, min_value=1, xlate=True):
If max_places is set to None, all non-zero digits will be shown.
Otherwise, max_places digits will be shown.
- If strip is True and there is a fractional quantity, trailing
- zeros are removed up to the decimal point.
-
- min_value sets the smallest value allowed.
- If min_value is 10, then single digits on the lhs of
- the decimal will be avoided if possible. In that case,
- 9216 KiB is preferred to 9 MiB. However, 1 B has no alternative.
- If min_value is 1, however, 9 MiB is preferred.
- If min_value is 0.1, then 0.75 GiB is preferred to 768 MiB,
- but 0.05 GiB is still displayed as 51.2 MiB.
-
- human_readable() is a function that evaluates to a number which
- represents a range of values. For a constant choice of max_places,
- all ranges are of equal size, and are bisected by the result. So,
- if n.human_readable() == x U and b is the number of bytes in 1 U,
- and e = 1/2 * 1/(10^max_places) * b, then x - e < n < x + e.
+ .. warning::
+ Since the :class:`Size` class now (since the version 1.15) uses
+ a different implementation, some of the parameters are
+ ignored. :param:`strip` is always ``True`` with trailing zeroes
+ being always stripped and :param:`min_value` is ignored
+ altogether. If you want a better control use the new
+ :method:`human_readable` method.
"""
- if max_places is not None and (max_places < 0 or not isinstance(max_places, six.integer_types)):
- raise SizePlacesError("max_places must be None or an non-negative integer value")
-
- if min_value < 0 or not isinstance(min_value, (six.integer_types, Decimal)):
- raise ValueError("min_value must be a precise positive numeric value.")
-
- # Find the smallest prefix which will allow a number less than
- # _BINARY_FACTOR * min_value to the left of the decimal point.
- # If the number is so large that no prefix will satisfy this
- # requirement use the largest prefix.
- limit = _BINARY_FACTOR * min_value
- for unit in [_EMPTY_PREFIX] + _BINARY_PREFIXES:
- newcheck = self.convert_to(unit)
- if abs(newcheck) < limit:
- break
-
- if max_places is not None:
- newcheck = newcheck.quantize(Decimal(10) ** -max_places)
-
- retval_str = str(newcheck)
-
- if '.' in retval_str and strip:
- retval_str = retval_str.rstrip("0").rstrip(".")
+ if max_places is not None and (max_places < -1 or not isinstance(max_places, six.integer_types)):
+ raise SizePlacesError("max_places must be None or an non-negative integer value")
- if xlate:
- radix = locale.nl_langinfo(locale.RADIXCHAR)
- if radix != '.':
- retval_str = retval_str.replace('.', radix)
+ if max_places is None:
+ max_places = -1
- # pylint: disable=undefined-loop-variable
- return retval_str + " " + _make_spec(unit.abbr, _BYTES_SYMBOL, xlate, lowercase=False)
+ return bytesize.Size.human_readable(self, max_places=max_places, xlate=xlate)
- def round_to_nearest(self, unit, rounding=ROUND_DEFAULT):
+ def round_to_nearest(self, size, rounding=ROUND_DEFAULT):
""" Rounds to nearest unit specified as a named constant or a Size.
- :param unit: a unit specifier
- :type unit: a named constant like KiB, or any non-negative Size
+ :param size: a size specifier
+ :type size: a named constant like KiB, or any non-negative Size
:keyword rounding: which direction to round
:type rounding: one of ROUND_UP, ROUND_DOWN, or ROUND_DEFAULT
:returns: Size rounded to nearest whole specified unit
:rtype: :class:`Size`
- If unit is Size(0), returns Size(0).
+ If size is Size(0), returns Size(0).
"""
if rounding not in (ROUND_UP, ROUND_DOWN, ROUND_DEFAULT):
raise ValueError("invalid rounding specifier")
- factor = Decimal(getattr(unit, "factor", unit))
-
- if factor == 0:
- return Size(0)
-
- if factor < 0:
- raise ValueError("invalid rounding unit: %s" % factor)
+ if isinstance(size, Size):
+ if size.get_bytes() == 0:
+ return Size(0)
+ elif size < Size(0):
+ raise ValueError("invalid rounding size: %s" % size)
- rounded = (Decimal(self) / factor).to_integral_value(rounding=rounding)
- return Size(rounded * factor)
+ return Size(bytesize.Size.round_to_nearest(self, size, rounding))
diff --git a/python-blivet.spec b/python-blivet.spec
index feb210d..7385a21 100644
--- a/python-blivet.spec
+++ b/python-blivet.spec
@@ -22,6 +22,7 @@ Source0: http://github.com/rhinstaller/blivet/archive/%{realname}-%{realversion}
%define e2fsver 1.41.0
%define utillinuxver 2.15.1
%define libblockdevver 1.4
+%define libbytesizever 0.3
BuildArch: noarch
BuildRequires: gettext
@@ -37,6 +38,7 @@ Requires: python3-pyparted >= %{pypartedver}
Requires: libselinux-python3
Requires: python3-blockdev >= %{libblockdevver}
Requires: libblockdev-plugins-all >= %{libblockdevver}
+Requires: python3-bytesize > = 0.3
Requires: util-linux >= %{utillinuxver}
Requires: dosfstools
Requires: e2fsprogs >= %{e2fsver}
diff --git a/tests/size_test.py b/tests/size_test.py
index 089f8ee..2873e4a 100644
--- a/tests/size_test.py
+++ b/tests/size_test.py
@@ -35,10 +35,9 @@
from blivet.i18n import _
from blivet.errors import SizePlacesError
from blivet import size
-from blivet.size import Size, _EMPTY_PREFIX, _BINARY_PREFIXES, _DECIMAL_PREFIXES
+from blivet.size import Size
from blivet.size import B, KiB, MiB, GiB, TiB
-
class SizeTestCase(unittest.TestCase):
def test_exceptions(self):
@@ -47,37 +46,10 @@ def test_exceptions(self):
s = Size(500)
with self.assertRaises(SizePlacesError):
- s.human_readable(max_places=-1)
+ s.human_readable(max_places=-2)
self.assertEqual(s.human_readable(max_places=0), "500 B")
- def _prefix_test_helper(self, numunits, unit):
- """ Test that units and prefix or abbreviation agree.
-
- :param int numunits: this value times factor yields number of bytes
- :param unit: a unit specifier
- """
- c = numunits * unit.factor
-
- s = Size(c)
- self.assertEqual(s, Size(c))
-
- u = size._make_spec(unit.prefix, size._BYTES_WORDS[0], False)
- s = Size("%ld %s" % (numunits, u))
- self.assertEqual(s, c)
- self.assertEqual(s.convert_to(unit), numunits)
-
- u = size._make_spec(unit.abbr, size._BYTES_SYMBOL, False)
- s = Size("%ld %s" % (numunits, u))
- self.assertEqual(s, c)
- self.assertEqual(s.convert_to(unit), numunits)
-
- def test_prefixes(self):
- numbytes = 47
-
- for unit in [_EMPTY_PREFIX] + _BINARY_PREFIXES + _DECIMAL_PREFIXES:
- self._prefix_test_helper(numbytes, unit)
-
def test_human_readable(self):
s = Size(58929971)
self.assertEqual(s.human_readable(), "56.2 MiB")
@@ -94,15 +66,6 @@ def test_human_readable(self):
s = Size("300 MiB")
self.assertEqual(s.human_readable(max_places=2), "300 MiB")
- # when min_value is 10 and single digit on left of decimal, display
- # with smaller unit.
- s = Size("9.68 TiB")
- self.assertEqual(s.human_readable(max_places=2, min_value=10), "9912.32 GiB")
- s = Size("4.29 MiB")
- self.assertEqual(s.human_readable(max_places=2, min_value=10), "4392.96 KiB")
- s = Size("7.18 KiB")
- self.assertEqual(s.human_readable(max_places=2, min_value=10), "7352 B")
-
# rounding should work with max_places limitted
s = Size("12.687 TiB")
self.assertEqual(s.human_readable(max_places=2), "12.69 TiB")
@@ -114,8 +77,6 @@ def test_human_readable(self):
# byte values close to multiples of 2 are shown without trailing zeros
s = Size(0xff)
self.assertEqual(s.human_readable(max_places=2), "255 B")
- s = Size(8193)
- self.assertEqual(s.human_readable(max_places=2, min_value=10), "8193 B")
# a fractional quantity is shown if the value deviates
# from the whole number of units by more than 1%
@@ -124,7 +85,7 @@ def test_human_readable(self):
# if max_places is set to None, all digits are displayed
s = Size(0xfffffffffffff)
- self.assertEqual(s.human_readable(max_places=None), "3.9999999999999991118215803 PiB")
+ self.assertEqual(s.human_readable(max_places=None), "3.99999999999999911182158029987476766109466552734375 PiB")
s = Size(0x10000)
self.assertEqual(s.human_readable(max_places=None), "64 KiB")
s = Size(0x10001)
@@ -135,16 +96,12 @@ def test_human_readable(self):
self.assertEqual(s.human_readable(max_places=2), "1024 YiB")
s = Size(1024 ** 9 - 1)
self.assertEqual(s.human_readable(max_places=2), "1024 YiB")
- s = Size(1024 ** 9 + 1)
- self.assertEqual(s.human_readable(max_places=2, strip=False), "1024.00 YiB")
s = Size(1024 ** 10)
self.assertEqual(s.human_readable(max_places=2), "1048576 YiB")
def test_human_readable_fractional_quantities(self):
s = Size(0xfffffffffffff)
self.assertEqual(s.human_readable(max_places=2), "4 PiB")
- s = Size(0xfffff)
- self.assertEqual(s.human_readable(max_places=2, strip=False), "1024.00 KiB")
s = Size(0xffff)
# value is not exactly 64 KiB, but w/ 2 places, value is 64.00 KiB
# so the trailing 0s are stripped.
@@ -153,24 +110,15 @@ def test_human_readable_fractional_quantities(self):
self.assertEqual(s.human_readable(max_places=None), "63.9990234375 KiB")
# deviation is less than 1/2 of 1% of 1024
- s = Size(16384 - (1024 / 100 // 2))
+ s = Size(16384 - (1024/100//2))
self.assertEqual(s.human_readable(max_places=2), "16 KiB")
# deviation is greater than 1/2 of 1% of 1024
- s = Size(16384 - ((1024 / 100 // 2) + 1))
+ s = Size(16384 - ((1024/100//2) + 1))
self.assertEqual(s.human_readable(max_places=2), "15.99 KiB")
s = Size(0x10000000000000)
self.assertEqual(s.human_readable(max_places=2), "4 PiB")
- def test_min_value(self):
- s = Size("9 MiB")
- self.assertEqual(s.human_readable(), "9 MiB")
- self.assertEqual(s.human_readable(min_value=10), "9216 KiB")
-
- s = Size("0.5 GiB")
- self.assertEqual(s.human_readable(max_places=2, min_value=1), "512 MiB")
- self.assertEqual(s.human_readable(max_places=2, min_value=Decimal("0.1")), "0.5 GiB")
- self.assertEqual(s.human_readable(max_places=2, min_value=Decimal(1)), "512 MiB")
def test_convert_to_precision(self):
s = Size(1835008)
@@ -189,94 +137,95 @@ def test_convert_to_with_size(self):
with self.assertRaises(ValueError):
s.convert_to(Size(0))
- def test_negative(self):
+ def test_segative(self):
s = Size("-500MiB")
self.assertEqual(s.human_readable(), "-500 MiB")
self.assertEqual(s.convert_to(B), -524288000)
def test_partial_bytes(self):
self.assertEqual(Size("1024.6"), Size(1024))
- self.assertEqual(Size("%s KiB" % (1 / 1025.0,)), Size(0))
- self.assertEqual(Size("%s KiB" % (1 / 1023.0,)), Size(1))
+ self.assertEqual(Size("%s KiB" % (1/1025.0,)), Size(0))
+ self.assertEqual(Size("%s KiB" % (1/1023.0,)), Size(1))
def test_no_units_in_string(self):
self.assertEqual(Size("1024"), Size("1 KiB"))
def test_scientific_notation(self):
- self.assertEqual(size.parse_spec("1e+0 KiB"), Decimal(1024))
- self.assertEqual(size.parse_spec("1e-0 KiB"), Decimal(1024))
- self.assertEqual(size.parse_spec("1e-1 KB"), Decimal(100))
- self.assertEqual(size.parse_spec("1E-4KB"), Decimal("0.1"))
+ self.assertEqual(Size("1e+0 KiB"), Decimal(1024))
+ self.assertEqual(Size("1e-0 KiB"), Decimal(1024))
+ self.assertEqual(Size("1e-1 KB"), Decimal(100))
+ self.assertEqual(Size("1E-4KB"), Decimal("0.1"))
self.assertEqual(Size("1E-10KB"), Size(0))
with self.assertRaises(ValueError):
# this is an exponent w/out a base
- size.parse_spec("e+0")
+ Size("e+0")
def test_floating_point_str(self):
- self.assertEqual(size.parse_spec("1.5e+0 KiB"), Decimal(1536))
- self.assertEqual(size.parse_spec("0.0"), Decimal(0))
- self.assertEqual(size.parse_spec("0.9 KiB"), Decimal("921.6"))
- self.assertEqual(size.parse_spec("1.5 KiB"), Decimal(1536))
- self.assertEqual(size.parse_spec("0.5 KiB"), Decimal(512))
- self.assertEqual(size.parse_spec(".5 KiB"), Decimal(512))
- self.assertEqual(size.parse_spec("1. KiB"), Decimal(1024))
- self.assertEqual(size.parse_spec("-1. KiB"), Decimal(-1024))
- self.assertEqual(size.parse_spec("+1. KiB"), Decimal(1024))
- self.assertEqual(size.parse_spec("+1.0000000e+0 KiB"), Decimal(1024))
- self.assertEqual(size.parse_spec("+.5 KiB"), Decimal(512))
+ self.assertEqual(Size("1.5e+0 KiB"), Decimal(1536))
+ self.assertEqual(Size("0.0"), Decimal(0))
+ self.assertEqual(Size("0.9 KiB"), Decimal("921.6"))
+ self.assertEqual(Size("1.5 KiB"), Decimal(1536))
+ self.assertEqual(Size("0.5 KiB"), Decimal(512))
+ self.assertEqual(Size(".5 KiB"), Decimal(512))
+ self.assertEqual(Size("1. KiB"), Decimal(1024))
+ self.assertEqual(Size("-1. KiB"), Decimal(-1024))
+ self.assertEqual(Size("+1. KiB"), Decimal(1024))
+ self.assertEqual(Size("+1.0000000e+0 KiB"), Decimal(1024))
+ self.assertEqual(Size("+.5 KiB"), Decimal(512))
with self.assertRaises(ValueError):
# this is a fragment of an arithmetic expression
- size.parse_spec("+ 1 KiB")
+ Size("+ 1 KiB")
with self.assertRaises(ValueError):
# this is a fragment of an arithmetic expression
- size.parse_spec("- 1 KiB")
+ Size("- 1 KiB")
with self.assertRaises(ValueError):
# this is a lonely .
- size.parse_spec(". KiB")
+ Size(". KiB")
with self.assertRaises(ValueError):
# this has a fragmentary exponent
- size.parse_spec("1.0e+ KiB")
+ Size("1.0e+ KiB")
with self.assertRaises(ValueError):
# this is a version string, not a number
- size.parse_spec("1.0.0")
+ Size("1.0.0")
def test_white_space(self):
- self.assertEqual(size.parse_spec("1 KiB "), Decimal(1024))
- self.assertEqual(size.parse_spec(" 1 KiB"), Decimal(1024))
- self.assertEqual(size.parse_spec(" 1KiB"), Decimal(1024))
- self.assertEqual(size.parse_spec(" 1e+0KiB"), Decimal(1024))
+ self.assertEqual(Size("1 KiB "), Decimal(1024))
+ self.assertEqual(Size(" 1 KiB"), Decimal(1024))
+ self.assertEqual(Size(" 1KiB"), Decimal(1024))
+ self.assertEqual(Size(" 1e+0KiB"), Decimal(1024))
with self.assertRaises(ValueError):
- size.parse_spec("1 KiB just a lot of stray characters")
+ Size("1 KiB just a lot of stray characters")
with self.assertRaises(ValueError):
- size.parse_spec("just 1 KiB")
+ Size("just 1 KiB")
def test_leading_zero(self):
- self.assertEqual(size.parse_spec("001 KiB"), Decimal(1024))
- self.assertEqual(size.parse_spec("1e+01"), Decimal(10))
+ self.assertEqual(Size("001 KiB"), Decimal(1024))
+ self.assertEqual(Size("1e+01"), Decimal(10))
def test_pickling(self):
s = Size("10 MiB")
self.assertEqual(s, cPickle.loads(cPickle.dumps(s)))
+# es_ES uses latin-characters but a comma as the radix separator
+# kk_KZ uses non-latin characters and is case-sensitive
+# ml_IN uses a lot of non-letter modifier characters
+# fa_IR uses non-ascii digits, or would if python supported that, but
+# you know, just in case
+TEST_LANGS = ["es_ES.UTF-8", "kk_KZ.UTF-8", "ml_IN.UTF-8", "fa_IR.UTF-8"]
+LANGS_AVAILABLE = all(os.path.exists("/usr/share/locale/%s/LC_MESSAGES/libbytesize.mo" % lang.split("_")[0]) for lang in TEST_LANGS)
+(a)unittest.skipUnless(LANGS_AVAILABLE, "libbytesize's translations are not available, cannot test now")
class TranslationTestCase(unittest.TestCase):
- def __init__(self, methodName='run_test'):
+ def __init__(self, methodName='runTest'):
super(TranslationTestCase, self).__init__(methodName=methodName)
- # es_ES uses latin-characters but a comma as the radix separator
- # kk_KZ uses non-latin characters and is case-sensitive
- # ml_IN uses a lot of non-letter modifier characters
- # fa_IR uses non-ascii digits, or would if python supported that, but
- # you know, just in case
- self.TEST_LANGS = ["es_ES.UTF-8", "kk_KZ.UTF-8", "ml_IN.UTF-8", "fa_IR.UTF-8"]
-
def setUp(self):
self.saved_lang = os.environ.get('LANG', None)
@@ -284,61 +233,9 @@ def tearDown(self):
os.environ['LANG'] = self.saved_lang
locale.setlocale(locale.LC_ALL, '')
- def test_make_spec(self):
- """ Tests for _make_specs(). """
- for lang in self.TEST_LANGS:
- os.environ['LANG'] = lang
- locale.setlocale(locale.LC_ALL, '')
-
- # untranslated specs
- self.assertEqual(size._make_spec("", "BYTES", False), "bytes")
- self.assertEqual(size._make_spec("Mi", "b", False), "mib")
-
- # un-lower-cased specs
- self.assertEqual(size._make_spec("", "BYTES", False, False), "BYTES")
- self.assertEqual(size._make_spec("Mi", "b", False, False), "Mib")
- self.assertEqual(size._make_spec("Mi", "B", False, False), "MiB")
-
- # translated specs
- res = size._make_spec("", "bytes", True)
-
- # Note that exp != _("bytes").lower() as one might expect
- exp = (_("") + _("bytes")).lower()
- self.assertEqual(res, exp)
-
- def test_parse_spec(self):
- """ Tests for parse_spec(). """
- for lang in self.TEST_LANGS:
- os.environ['LANG'] = lang
- locale.setlocale(locale.LC_ALL, '')
-
- # Test parsing English spec in foreign locales
- self.assertEqual(size.parse_spec("1 kibibytes"), Decimal(1024))
- self.assertEqual(size.parse_spec("2 kibibyte"), Decimal(2048))
- self.assertEqual(size.parse_spec("2 kilobyte"), Decimal(2000))
- self.assertEqual(size.parse_spec("2 kilobytes"), Decimal(2000))
- self.assertEqual(size.parse_spec("2 KB"), Decimal(2000))
- self.assertEqual(size.parse_spec("2 K"), Decimal(2048))
- self.assertEqual(size.parse_spec("2 k"), Decimal(2048))
- self.assertEqual(size.parse_spec("2 Ki"), Decimal(2048))
- self.assertEqual(size.parse_spec("2 g"), Decimal(2 * 1024 ** 3))
- self.assertEqual(size.parse_spec("2 G"), Decimal(2 * 1024 ** 3))
-
- # Test parsing foreign spec
- self.assertEqual(size.parse_spec("1 %s%s" % (_("kibi"), _("bytes"))), Decimal(1024))
-
- # Can't parse a valueless number
- with self.assertRaises(ValueError):
- size.parse_spec("Ki")
-
- self.assertEqual(size.parse_spec("2 %s" % _("K")), Decimal(2048))
- self.assertEqual(size.parse_spec("2 %s" % _("Ki")), Decimal(2048))
- self.assertEqual(size.parse_spec("2 %s" % _("g")), Decimal(2 * 1024 ** 3))
- self.assertEqual(size.parse_spec("2 %s" % _("G")), Decimal(2 * 1024 ** 3))
-
def test_translated(self):
s = Size("56.19 MiB")
- for lang in self.TEST_LANGS:
+ for lang in TEST_LANGS:
os.environ['LANG'] = lang
locale.setlocale(locale.LC_ALL, '')
@@ -366,7 +263,7 @@ def test_translated(self):
def test_human_readable_translation(self):
s = Size("56.19 MiB")
size_str = s.human_readable()
- for lang in self.TEST_LANGS:
+ for lang in TEST_LANGS:
os.environ['LANG'] = lang
locale.setlocale(locale.LC_ALL, '')
@@ -374,7 +271,7 @@ def test_human_readable_translation(self):
self.assertEqual(s.human_readable(xlate=False), size_str)
def test_round_to_nearest(self):
- self.assertEqual(size.ROUND_DEFAULT, size.ROUND_HALF_UP)
+ self.assertEqual(size.ROUND_DEFAULT, size.ROUND_UP)
s = Size("10.3 GiB")
self.assertEqual(s.round_to_nearest(GiB), Size("10 GiB"))
@@ -424,38 +321,26 @@ def test_round_to_nearest(self):
with self.assertRaises(ValueError):
s.round_to_nearest(Size("-1 B"))
-
class UtilityMethodsTestCase(unittest.TestCase):
- def test_lower_ascii(self):
- """ Tests for _lower_ascii. """
- self.assertEqual(size._lower_ascii(""), "")
- self.assertEqual(size._lower_ascii("B"), "b")
-
def test_arithmetic(self):
s = Size("2GiB")
# Make sure arithmatic operations with Size always result in the expected type
self.assertIsInstance(s + s, Size)
self.assertIsInstance(s - s, Size)
- self.assertIsInstance(s * s, Size)
- self.assertIsInstance(s / s, Size)
- self.assertIsInstance(s ** Size(2), Decimal)
+ self.assertIsInstance(s / s, Decimal)
self.assertIsInstance(s % Size(7), Size)
+
# Make sure operations with non-Size on the right result in the expected type
self.assertIsInstance(s + 2, Size)
self.assertIsInstance(s - 2, Size)
self.assertIsInstance(s * 2, Size)
self.assertIsInstance(s / 2, Size)
self.assertIsInstance(s // 2, Size)
- self.assertIsInstance(s ** 2, Decimal)
- self.assertIsInstance(s % 127, Size)
+ self.assertIsInstance(s % Size(127), Size)
# Make sure operations with non-Size on the left result in the expected type
self.assertIsInstance(2 + s, Size)
- self.assertIsInstance(2 - s, Decimal)
- self.assertIsInstance(2 * s, Size)
- self.assertIsInstance(2 / s, Decimal)
- self.assertIsInstance(2 ** Size(2), Decimal)
- self.assertIsInstance(1024 % Size(127), Decimal)
+ self.assertIsInstance(2 - s, Size)
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/fd150fa8ff3e93d0d0f6593a2ff3...
8 years, 3 months
Updated: [rhinstaller/blivet/pulls/251 master] Use libbytesize's Size
class as a base for our Size class
by dashea
libbytesize provides an implementation of what we need for manipulations and
calculations with sizes in bytes. It's a little bit faster and more importantly
it can be shared with other projects because it's written in C. Thus we should
use it.
At some point it would be nice if we could drop our own Size class and the
blivet/size.py module altogether because they don't bring any extra value, but
for now we need this thin wrapper to keep the API (almost*) stable. The only
real changes are:
1) Strings like "100 kibibytes" are no longer accepted, I doubt anybody has ever
used this functionality. If the opposite turns out to be true such support will
be added to libbytesize and no change will be needed in Blivet.
2) humanReadable() now ignores the 'skip' and 'min_value' attributes. The former
one is always replaced by 'True' (i.e. trailing zeroes are always stripped), the
latter one is ignored because it made things really cryptic and weird.
* see the commit, mainly the part modifying/removing the tests for more details
Note: tests are expected to be failing at this phase -- libbytesize is not available as a package yet. I am of course not going to merge this before that changes.
--
To view this pull request on github, visit https://github.com/rhinstaller/blivet/pull/251
8 years, 3 months
[master 3/3] Make sure we use the Size class properly
by dashea
From: Vratislav Podzimek <vpodzime(a)redhat.com>
It's no longer a Decimal instance.
---
blivet/devices/partition.py | 2 +-
blivet/formats/fs.py | 2 +-
tests/formats_test/misc_test.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/blivet/devices/partition.py b/blivet/devices/partition.py
index 121e2b0..3306ff3 100644
--- a/blivet/devices/partition.py
+++ b/blivet/devices/partition.py
@@ -624,7 +624,7 @@ def _compute_resize(self, partition, newsize=None):
# compute new size for partition
current_geom = partition.geometry
current_dev = current_geom.device
- new_len = int(newsize // Size(current_dev.sectorSize))
+ new_len = int(Size(newsize) // Size(current_dev.sectorSize))
new_geometry = parted.Geometry(device=current_dev,
start=current_geom.start,
length=new_len)
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
index 4d5bd66..67517d6 100644
--- a/blivet/formats/fs.py
+++ b/blivet/formats/fs.py
@@ -196,7 +196,7 @@ def get_required_size(cls, free_space):
# usable_size = device_size * _metadata_size_factor
# we can change this to get device size with required usable_size
# device_size = usable_size / _metadata_size_factor
- return Size(Decimal(free_space) / Decimal(cls._metadata_size_factor))
+ return Size(Decimal(int(free_space)) / Decimal(cls._metadata_size_factor))
@classmethod
def biggest_overhead_FS(cls, fs_list=None):
diff --git a/tests/formats_test/misc_test.py b/tests/formats_test/misc_test.py
index 162d17e..a24ff0c 100644
--- a/tests/formats_test/misc_test.py
+++ b/tests/formats_test/misc_test.py
@@ -10,7 +10,7 @@ class FSOverheadTestCase(unittest.TestCase):
def test_required_size_FS(self):
# FS is abstract parent which doesn't have metadata
self.assertEqual(FS.get_required_size(Size("100 MiB")), Size("100 MiB"))
- self.assertEqual(Ext2FS.get_required_size(Size("100 MiB")), Size(Decimal(Size("100 MiB")) / Decimal(0.93)))
+ self.assertEqual(Ext2FS.get_required_size(Size("100 MiB")), Size(Decimal(int(Size("100 MiB"))) / Decimal(0.93)))
def test_biggest_overhead_FS(self):
self.assertTrue(FS.biggest_overhead_FS() is BTRFS)
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/c0261091167feb1eacb5643b6642...
8 years, 3 months