Source code for wheezy.validation.model

""" ``model`` module.
"""

from datetime import date, datetime, time
from decimal import Decimal
from gettext import NullTranslations
from time import strptime

from wheezy.validation.i18n import (
    decimal_separator,
    default_date_input_format,
    default_datetime_input_format,
    default_time_input_format,
    fallback_date_input_formats,
    fallback_datetime_input_formats,
    fallback_time_input_formats,
    thousands_separator,
)
from wheezy.validation.patches import patch_strptime_cache_size

if not patch_strptime_cache_size():  # pragma: nocover
    from warnings import warn

    warn("Failed to patch _strptime._CACHE_MAX_SIZE")
    del warn
del patch_strptime_cache_size


null_translations = NullTranslations()


[docs]def try_update_model(model, values, results, translations=None): """Try update `model` with `values` (a dict of lists or strings), any errors encountered put into `results` and use `translations` for i18n. """ if translations is None: translations = null_translations gettext = translations.gettext if hasattr(model, "__iter__"): attribute_names = model model_type = type(model) getter = model_type.__getitem__ setter = model_type.__setitem__ else: attribute_names = list(model.__dict__) attribute_names.extend( [name for name in model.__class__.__dict__ if name[:1] != "_"] ) getter = getattr setter = setattr succeed = True for name in attribute_names: if name not in values: continue value = values[name] attr = getter(model, name) # Check if we have a deal with list like attribute if hasattr(attr, "__setitem__"): # Guess type of list by checking the first item, # fallback to str provider that leaves value unchanged. if attr: provider_name = type(attr[0]).__name__ if provider_name in value_providers: value_provider = value_providers[provider_name] else: # pragma: nocover continue else: value_provider = value_providers["str"] items = [] try: for item in value: items.append(value_provider(item, gettext)) attr[:] = items except (ArithmeticError, ValueError): results[name] = [ gettext("Multiple input was not in a correct format.") ] succeed = False else: # A simple value attribute provider_name = type(attr).__name__ if provider_name in value_providers: value_provider = value_providers[provider_name] if isinstance(value, list): value = value and value[-1] or "" try: value = value_provider(value, gettext) setter(model, name, value) except (ArithmeticError, ValueError): results[name] = [ gettext("Input was not in a correct format.") ] succeed = False return succeed
# region: internal details # value_provider => lambda value, gettext: parsed_value
[docs]def bytes_value_provider(value, gettext): """Converts ``value`` to ``bytes``.""" if value is None: return None t = type(value) if t is bytes: return value if t is str: return value.encode("UTF-8") return str(value).encode("UTF-8")
[docs]def str_value_provider(value, gettext): """Converts ``value`` to ``str``.""" if value is None: return None t = type(value) if t is str: return value.strip() if t is bytes: return value.strip().decode("UTF-8") return str(value)
[docs]def int_value_provider(value, gettext): """Converts ``value`` to ``int``.""" if value is None or type(value) is int: return value value = str(value).strip() if value: s = thousands_separator(gettext) if s in value: value = value.replace(s, "") return int(value) else: return None
decimal_zero = Decimal(0) decimal_zero_values = ["0", "0.0", "0.00"]
[docs]def decimal_value_provider(value, gettext): """Converts ``value`` to ``Decimal``.""" if value is None: return None value = str(value).strip() if value: s = thousands_separator(gettext) if s in value: value = value.replace(s, "") s = decimal_separator(gettext) if s in value: value = value.replace(s, ".", 1) if value in decimal_zero_values: return decimal_zero return Decimal(value) else: return None
boolean_true_values = ["1", "True"]
[docs]def bool_value_provider(value, gettext): """Converts ``value`` to ``bool``.""" if value is None or type(value) is bool: return value value = str(value).strip() return value in boolean_true_values
[docs]def float_value_provider(value, gettext): """Converts ``value`` to ``float``.""" if value is None or type(value) is float: return value value = str(value).strip() if value: s = thousands_separator(gettext) if s in value: value = value.replace(s, "") s = decimal_separator(gettext) if s in value: value = value.replace(s, ".", 1) return float(value) else: return None
[docs]def date_value_provider(value, gettext): """Converts ``value`` to ``datetime.date``.""" if value is None: return None value = str(value).strip() if value: try: return date( *strptime(value, default_date_input_format(gettext))[:3] ) except ValueError: for fmt in fallback_date_input_formats(gettext).split("|"): try: return date(*strptime(value, fmt)[:3]) except ValueError: continue raise ValueError() else: return None
[docs]def time_value_provider(value, gettext): """Converts ``value`` to ``datetime.time``.""" if value is None: return None value = str(value).strip() if value: try: return time( *strptime(value, default_time_input_format(gettext))[3:6] ) except ValueError: for fmt in fallback_time_input_formats(gettext).split("|"): try: return time(*strptime(value, fmt)[3:6]) except ValueError: continue raise ValueError() else: return None
[docs]def datetime_value_provider(value, gettext): """Converts ``value`` to ``datetime.datetime``.""" if value is None: return None value = str(value).strip() if value: try: return datetime( *strptime(value, default_datetime_input_format(gettext))[:6] ) except ValueError: for fmt in fallback_datetime_input_formats(gettext).split("|"): try: return datetime(*strptime(value, fmt)[:6]) except ValueError: continue value = date_value_provider(value, gettext) return datetime(value.year, value.month, value.day) else: return None
value_providers = { "int": int_value_provider, "Decimal": decimal_value_provider, "bool": bool_value_provider, "float": float_value_provider, "date": date_value_provider, "time": time_value_provider, "datetime": datetime_value_provider, } value_providers["str"] = str_value_provider value_providers["bytes"] = bytes_value_provider