Mini Shell
# size.py
# Python module to represent storage sizes
#
# Copyright (C) 2010 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# Red Hat Author(s): David Cantrell <dcantrell@redhat.com>
from bytesize import bytesize
# 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, ROUND_HALF_UP
def unit_str(unit, xlate=False):
""" Return a string representation of unit.
:param unit: a named unit, e.g., KiB
:param bool xlate: if True, translate to current locale
:rtype: some kind of string type
:returns: string representation of unit
"""
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.
Also generates human readable strings to a specified number of
decimal places.
"""
def __abs__(self):
return Size(bytesize.Size.__abs__(self))
def __add__(self, other):
return Size(bytesize.Size.__add__(self, other))
# needed to make sum() work with Size arguments
def __radd__(self, other):
return Size(bytesize.Size.__radd__(self, other))
def __sub__(self, other):
return Size(bytesize.Size.__sub__(self, other))
def __rsub__(self, other):
return Size(bytesize.Size.__rsub__(self, other))
def __mul__(self, other):
return Size(bytesize.Size.__mul__(self, other))
__rmul__ = __mul__
def __div__(self, other): # pylint: disable=unused-argument
ret = bytesize.Size.__div__(self, other) # pylint: disable=no-member
if isinstance(ret, bytesize.Size):
ret = Size(ret)
return ret
def __truediv__(self, other):
ret = bytesize.Size.__truediv__(self, other)
if isinstance(ret, bytesize.Size):
ret = Size(ret)
return ret
def __floordiv__(self, other):
ret = bytesize.Size.__floordiv__(self, other)
if isinstance(ret, bytesize.Size):
ret = Size(ret)
return ret
def __mod__(self, other):
return Size(bytesize.Size.__mod__(self, other))
def __deepcopy__(self, memo_dict):
return Size(bytesize.Size.__deepcopy__(self, memo_dict))
# pylint: disable=arguments-differ,arguments-renamed
def convert_to(self, spec=None):
""" Return the size in the units indicated by the specifier.
:param spec: a units specifier
:type spec: a units specifier or :class:`Size`
:returns: a numeric value in the units indicated by the specifier
:rtype: Decimal
:raises ValueError: if Size unit specifier is non-positive
.. 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
return bytesize.Size.convert_to(self, spec)
def human_readable(self, min_unit=B, max_places=2, xlate=True):
""" Return a string representation of this size with appropriate
size specifier and in the specified number of decimal places.
Values are always represented using binary not decimal units.
For example, if the number of bytes represented by this size
is 65531, expect the representation to be something like
64.00 KiB, not 65.53 KB.
:param min_unit: the smallest unit the returned representation should use
:type min_unit: one of the B, KiB, MiB,... (binary) units from this module
or str ("B", "KiB",...)
:param max_places: number of decimal places to use
:type max_places: an integer type or NoneType
:param bool xlate: If True, translate for current locale
:returns: a representation of the size
:rtype: str
"""
if max_places is None:
max_places = -1
return bytesize.Size.human_readable(self, min_unit, max_places, xlate)
# pylint: disable=arguments-differ,arguments-renamed
def round_to_nearest(self, size, rounding):
""" Rounds to nearest unit specified as a named constant or a 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_HALF_UP
:returns: Size rounded to nearest whole specified unit
:rtype: :class:`Size`
If size is Size(0), returns Size(0).
"""
if rounding not in (ROUND_UP, ROUND_DOWN, ROUND_HALF_UP):
raise ValueError("invalid rounding specifier")
if isinstance(size, Size):
if size.get_bytes() == 0:
return Size(0)
elif size < Size(0):
raise ValueError("invalid rounding size: %s" % size)
return Size(bytesize.Size.round_to_nearest(self, size, rounding))
def ensure_percent_reserve(self, percent):
"""Get a new size with given space reserve.
:param percent: number of percent to reserve
:returns: a new size with :param:`percent` space reserve
:rtype: :class:`Size`
Best described with an example:
>>> Size("80 GiB").ensure_percent_reserve(20)
Size (100 GiB)
This method returns a new size in which there's a :param:`percent`%
reserve (on top of the original size). In other words::
with_reserve - orig_size == with_reserve * (percent / 100)
except that due to float arithmetics the numbers are unlikely to be
exactly equal.
"""
return Size(self * (1 / (1 - (percent / 100))))