Source code for gpf.common.validate

# coding: utf-8
#
# Copyright 2019 Geocom Informatik AG / VertiGIS

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# noinspection PyUnresolvedReferences
"""
The **validate** module can be used to verify if certain values are "truthy".
Most of the functions in this module return a **boolean** (True/False).

Exceptions to this rule are the :func:`pass_if` and :func:`raise_if` functions, which can be used for data assertions
and therefore serve as an alternative to the ``assert`` statement (which should not be used in production environments).

    >>> def test(text):
    >>>     pass_if(isinstance(text, str), TypeError, 'test() requires text input')
    >>>     print(text)
    >>>
    >>> test('Hello World')
    'Hello World'
    >>>
    >>> test(123)
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    TypeError: test() requires text input
"""

import inspect as _inspect
import typing as _tp
from numbers import Number as _Number

import gpf.common.const as _const
import gpf.common.guids as _guids


[docs]def is_text(value: _tp.Any, allow_empty: bool = True) -> bool: """ Returns ``True`` if **value** is a ``str`` instance. :param value: The value to check. :param allow_empty: If ``True`` (default), empty string values are allowed. If ``False``, empty strings will evaluate as "falsy". """ if isinstance(value, str): return allow_empty or value != _const.CHAR_EMPTY return False
[docs]def is_number(value: _tp.Any, allow_bool: bool = False) -> bool: """ Returns ``True`` if *value* is a built-in numeric object (e.g. ``int``, ``float``, ``long``, ``Decimal``, etc.). Note that :func:`is_number` will return ``False`` for booleans by default, which is non-standard behaviour, because ``bool`` is a subclass of ``int``: >>> isinstance(True, int) True >>> is_number(True) False :param value: The value to check. :param allow_bool: If the standard Python boolean evaluation behavior is desired, set this to ``True``. """ if isinstance(value, bool): return allow_bool return isinstance(value, _Number)
[docs]def is_iterable(value: _tp.Any) -> bool: # noinspection PyUnresolvedReferences """ Returns ``True`` if *value* is an iterable container (e.g. ``list`` or ``tuple`` but not a **generator**). Note that :func:`is_iterable` will return ``False`` for string-like objects as well, even though they are iterable. The same applies to **generators** and **sets**: >>> my_list = [1, 2, 3] >>> is_iterable(my_list) True >>> is_iterable(set(my_list)) False >>> is_iterable(v for v in my_list) False >>> is_iterable(str(my_list)) False :param value: The value to check. """ return not isinstance(value, str) and hasattr(value, '__iter__') and hasattr(value, '__getitem__')
[docs]def is_guid(value: _tp.Any) -> bool: """ Returns ``True`` when the given *value* is a GUID-like object and ``False`` otherwise. The function effectively tries to parse the value as a :class:`gpf.tools.guids.Guid` object. :param value: A string or a GUID-like object. """ try: _guids.Guid(value) except (_guids.Guid.BadGuidError, _guids.Guid.MissingGuidError): return False return True
[docs]def has_value(obj: _tp.Any, strip: bool = False) -> bool: """ Returns ``True`` when *obj* is "truthy". Note that :func:`has_value` will return ``True`` for the ``False`` boolean and ``0`` integer values. Python normally evaluates these values as "falsy", but for databases for example, these values are perfectly valid. This is why :func:`has_value` considers them to be "truthy": >>> my_int = 0 >>> my_bool = False >>> if not (my_int and my_bool): >>> print('Both values are "falsy".') 'Both values are "falsy".' >>> has_value(my_int) True >>> has_value(my_bool) True Other usage examples: >>> has_value(None) False >>> has_value({}) False >>> has_value('test') True >>> has_value(' ', strip=True) False :param obj: The object for which to evaluate its value. :param strip: If ``True`` and *obj* is a ``str``, *obj* will be stripped before evaluation. Defaults to ``False``. """ if not obj: return obj == 0 if is_text(obj): return (obj.strip() if strip else obj) != _const.CHAR_EMPTY return True
[docs]def signature_matches(func: _tp.Callable, template_func: _tp.Callable) -> bool: """ Checks if the given *func* (`function` or `instancemethod`) has a signature equal to *template_func*. If the function is not callable or the signature does not match, ``False`` is returned. :param func: A callable function or (instance) method. :param template_func: A template function to which the callable function argument count should be compared. This should *not* be a an instance method. :rtype: bool """ def _get_params(f): """ Returns the Parameter objects for a function (skipping 'self', if present). """ try: sig = _inspect.signature(f) except (ValueError, TypeError): params = _inspect.OrderedDict() else: params = sig.parameters or _inspect.OrderedDict() for i, (name, p) in enumerate(params.items()): if i == 0 and name == 'self': # For instance methods/functions, we'll remove the first parameter ("self" by convention) continue yield p if not callable(func) or not callable(template_func): return False # Compare the parameter count, names and types return tuple(_get_params(func)) == tuple(_get_params(template_func))
[docs]def pass_if(expression: _tp.Any, exc_type: type(Exception), exc_val: _tp.Any = _const.CHAR_EMPTY) -> bool: """ Raises an error of type *err_type* when *expression* is **falsy** and silently passes otherwise. Opposite of :func:`raise_if`. :param expression: An expression or value to evaluate. :param exc_type: The error to raise when *expression* evaluates to ``False``, e.g. ``AttributeError``. :param exc_val: An optional message or exception value to include with the error (recommended). :Examples: >>> my_value = 0 >>> pass_if(my_value) Traceback (most recent call last): File "<input>", line 1, in <module> AssertionError >>> pass_if(my_value == 0) >>> pass_if(my_value == 1, ValueError, 'my_value should be 1') Traceback (most recent call last): File "<input>", line 1, in <module> ValueError: my_value should be 1 """ if not expression: raise exc_type(exc_val) return True
[docs]def raise_if(expression: _tp.Any, exc_type: type(Exception), exc_val: _tp.Any = _const.CHAR_EMPTY) -> bool: """ Raises an error of type *err_type* when *expression* is **truthy** and silently passes otherwise. Opposite of :func:`pass_if`. :param expression: An expression or value to evaluate. :param exc_type: The error to raise when *expression* evaluates to ``True``, e.g. ``AttributeError``. :param exc_val: An optional message or exception value to include with the error (recommended). :Examples: >>> my_value = 0 >>> raise_if(my_value) >>> raise_if(my_value == 1) >>> raise_if(my_value == 0, ValueError, 'my_value should not be 0') Traceback (most recent call last): File "<input>", line 1, in <module> ValueError: my_value should not be 0 """ if expression: raise exc_type(exc_val) return True