Source code for pylablib.core.gui.limiter

[docs] class LimitError(ArithmeticError): """Error raised when the value is out of limits and can't be coerced""" def __init__(self, value, lower_limit=None, upper_limit=None): ArithmeticError.__init__(self) self.value=value self.lower_limit=lower_limit self.upper_limit=upper_limit def __str__(self): lb=self.lower_limit if lb==None: lb="-Inf" hb=self.upper_limit if hb==None: hb="+Inf" return "value {0} is out of limits ({1}, {2})".format(self.value, lb,hb)
[docs] class NumberLimit: """ Number limiter, which checks validity of user inputs. Callable object with takes a number as an argument and either returns its coerced version (or the number itself, if it is within limits), or raises :exc:`LimitError` if it should be ignored. Args: lower_limit: lower limit (inclusive), or ``None`` if there is no limit. upper_limit: upper limit (inclusive), or ``None`` if there is no limit. action (str): action taken if the number is out of limits; either ``"coerce"`` (return the closest valid value), or ``"ignore"`` (raise :exc:`LimitError`). value_type (str): determines value type coercion; can be ``None`` (do nothing, only check limits), ``"float"`` (cast to float), or ``"int"`` (cast to integer). """ def __init__(self, lower_limit=None, upper_limit=None, action="coerce", value_type=None): if not value_type in [None,"float","int"]: raise ValueError("unrecognized value type: {0}".format(value_type)) self.value_type=value_type lower_limit,upper_limit=self.cast(lower_limit), self.cast(upper_limit) if lower_limit!=None and upper_limit!=None and lower_limit>upper_limit: raise ValueError("impossible value range: ({0}, {1})".format(lower_limit,upper_limit)) self.range=(lower_limit,upper_limit) if not action in ["coerce", "ignore"]: raise ValueError("unrecognized action: {0}".format(action)) self.action=action def __call__(self, value): """ Restrict value to the preset limit and type. Raise :exc:`LimitError` if value is outside bounds and ``self.action=='ignore'``. """ value=self.cast(value) if self.range[0] is not None and value<self.range[0]: if self.action=="coerce": return self.cast(self.range[0]) elif self.action=="ignore": raise LimitError(value,*self.range) elif self.range[1] is not None and value>self.range[1]: if self.action=="coerce": return self.cast(self.range[1]) elif self.action=="ignore": raise LimitError(value,*self.range) else: return value
[docs] def cast(self, value): if value==None: return None if self.value_type=="float": return float(value) elif self.value_type=="int": return int(value) else: return value
[docs] def filter_limiter(pred): """ Turn a predicate into a limiter. Returns a function that raises :exc:`LimitError` if the predicate is false. """ def wrapped(v): if not pred(v): raise LimitError(v) return v return wrapped
[docs] def as_limiter(limiter): """ Turn an object into a limiter. Limiter can be a callable object which takes a single value and either returns a limited value, or raises :exc:`LimitError` if it should be ignored; or it can be a tuple ``(lower, upper, action, value_type)``, where ``lower`` and ``upper`` are the limits (``None`` means no limits), ``action`` defines out-of-limit action (either ``"ignore"`` to ignore entered value, or ``"coerce"`` to truncate to the nearest limit), and ``value_type`` can be ``None`` (keep value as is), ``"float"`` (cast value to float), ``"int"`` (cast value to int). If the tuple is shorter, the missing parts are filled by default values ``(None, None, "ignore", None)``. """ if hasattr(limiter,"__call__"): return limiter if limiter is None: return NumberLimit() if isinstance(limiter,tuple): return NumberLimit(*limiter) raise ValueError("unknown limiter: {}".format(limiter))