Source code for pycinga.range

"""
Contains a class to represent a range that adheres to the range
format defined by Icinga.
"""


[docs]class RangeValueError(ValueError): """ This exception is raised when an invalid value is passed to :py:class:`Range`. The message of this exception will contain a human readable explanation of the error. """ pass
[docs]class Range(object): """ Encapsulates an Icinga range value. This is the value which would be passed to command line arguments involving ranges. Examples of ranges: 10 - < 0 OR > 10 10:20 - < 10 OR > 20 @10:20 - >= 10 AND <= 20 ~:10 - > 10 (~ = -inf) 10:~ - < 10 """ def __init__(self, value): """ Initializes an Icinga range with the given value. The value should be in the Icinga range format, which is the following: :: [@]start:end Notes: - ``start`` must be less than or equal to ``end`` - Ranges by default are exclusive. A range of ``10:20`` will match values that are ``< 10 OR >20``. - ``@`` means the range is `inclusive`. So ``@10:20`` is valid in the case that the value is ``>= 10 AND <= 20``. - If ``start`` or ``end`` is ``~``, this value is negative or positive inifinity, respectively. A range of ``~:20`` will match values that are ``> 20`` only. - If ``start`` is not given, then it is assumed to be 0. - If ``end`` is not given, but a ``:`` exists, then ``end`` is assumed to be infinity. Example: ``5:`` would match ``< 5``. """ # Clean up the value by clearing whitespace. Also note that an empty # value is invalid. value = value.strip() if len(value) == 0: raise RangeValueError("Range must not be empty") # Test for the inclusivity marked by the "@" symbol at the beginning if value.startswith("@"): self.inclusive = True value = value[1:] else: self.inclusive = False # Split by the ":" character to get the start/end parts. parts = value.split(":") if len(parts) > 2: raise RangeValueError("Range cannot have more than two parts.") if len(parts) == 1: parts.insert(0, "0") # Parse the start value. If no ":" is included in value (e.g. "10") # then the start is assumed to be 0. Otherwise, it is an integer # value which can possibly be infinity. try: if parts[0] == "~": start = float("-inf") else: start = float(parts[0]) except ValueError: raise RangeValueError("invalid start value: %s" % parts[0]) # Parse the end value, which can be positive infinity. try: if parts[1] == "" or parts[1] == "~": end = float("inf") else: end = float(parts[1]) except ValueError: raise RangeValueError("invalid end value: %s" % parts[1]) # The start must be less than the end if start > end: raise RangeValueError("start must be less than or equal to end") # Set the instance variables self.start = start self.end = end
[docs] def in_range(self, value): """ Tests whether ``value`` is in this range. """ if self.inclusive: return value >= self.start and value <= self.end else: return value < self.start or value > self.end
[docs] def __str__(self): """ Turns this range object back into a valid range string which can be passed to another plugin or used for debug output. The string returned from here should generally be equivalent to the value given to the constructor, but sometimes it can be slightly different. However, it will always be functionally equivalent. Examples: :: >> str(Range("@10:20")) == "@10:20" >> str(Range("10")) == "10" >> str(Range("10:")) == "10:~" """ result = "@" if self.inclusive else "" # Setup some proxy variables since typing `self` all the time is tiring start = self.start end = self.end # Only put start in the result if it is not equal to 0, since # it can otherwise be omitted if start == float("-inf"): result += "~:" elif start != 0.0: value = int(start) if start.is_integer() else start result += str(value) + ":" # Append the end value next if end == float("inf"): result += "~" else: value = int(end) if end.is_integer() else end result += str(value) return result