Source code for ripozo.resources.fields.common

"""
Contains common field types that
may be used.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import abc
from datetime import datetime

from ripozo.exceptions import TranslationException
from ripozo.resources.fields.base import BaseField
from ripozo.resources.fields.field import IField, Field
from ripozo.resources.fields.validations import validate_regex, validate_size, \
    basic_validation, translate_iterable_to_single, validate_required

import six


[docs]class StringField(IField): """ Used for casting and validating string fields. """ field_type = six.text_type
[docs] def __init__(self, name, regex=None, minimum=None, maximum=None, **kwargs): """ A field class for validating string inputs. :param unicode name: The name of the field :param _sre.SRE_Pattern regex: A compiled regular expression that must match at least once. :param dict kwargs: The additional arguments to pass to the super call. """ super(StringField, self).__init__(name, **kwargs) self.minimum = minimum self.maximum = maximum self.regex = regex
def _translate(self, obj, skip_required=False): """ Attempts to convert the object to a string type :param object obj: :return: The object in its string representation :rtype: unicode :raises: TranslationsException """ obj = translate_iterable_to_single(obj) return six.text_type(obj) def _validate(self, obj, skip_required=False): """ Validates the object. It makes a call to super checking if the input can be None :param unicode obj: :return: :rtype: unicode :raises: ValidationException """ obj = basic_validation(self, obj, skip_required=skip_required) if obj is None: return obj obj = validate_size(self, obj, len(obj), minimum=self.minimum, maximum=self.maximum, msg=self.error_message) obj = validate_regex(self, obj, regex=self.regex) return obj
@six.add_metaclass(abc.ABCMeta) class _INumberField(IField): @abc.abstractproperty def field_type(self): raise NotImplementedError def __init__(self, name, regex=None, minimum=None, maximum=None, **kwargs): super(_INumberField, self).__init__(name, **kwargs) self.minimum = minimum self.maximum = maximum self.regex = regex def _translate(self, obj, skip_required=False): obj = translate_iterable_to_single(obj) try: return self.field_type(obj) except ValueError: msg = self.error_message or 'Not a valid integer type: {0}'.format(obj) raise TranslationException(msg) def _validate(self, obj, skip_required=False): obj = basic_validation(self, obj, skip_required=skip_required) if obj is None: return obj obj = validate_size(self, obj, obj, minimum=self.minimum, maximum=self.maximum, msg=self.error_message) return obj
[docs]class IntegerField(_INumberField): """ A field used for translating and validating an integer input. While translating it will attempt to cast the object provided as an integer. """ field_type = int
[docs]class FloatField(_INumberField): """ A field used for translating and validating a float input. Pretty much the same as the IntegerField except that it will be cast as an IntegerField. """ field_type = float
[docs]class BooleanField(IField): """ A field used for translating and validating a boolean input It can take either a boolean or a string. If it's a string it checks if it matches 'false' or 'true' (case insensitive). """ field_type = bool def _translate(self, obj, skip_required=False): obj = translate_iterable_to_single(obj) if isinstance(obj, bool): return obj if isinstance(obj, six.string_types): if obj.lower() == 'false': return False elif obj.lower() == 'true': return True msg = self.error_message or ('{0} is not a valid boolean.' ' Either "true" or "false" is ' 'required (case insensitive)'.format(obj)) raise TranslationException(msg) def _validate(self, obj, skip_required=False): return validate_required(self, obj, skip_required=skip_required)
[docs]class DateTimeField(IField): """ A field for validating and translating a datetime input. By default it accepts the following formats: %Y-%m-%dT%H:%M:%S.%fZ If you need other formats simply pass a list of valid formats into the valid_formats parameter on initialization """ field_type = datetime valid_formats = ['%Y-%m-%dT%H:%M:%S.%fZ']
[docs] def __init__(self, name, valid_formats=None, minimum=None, maximum=None, **kwargs): """ :param unicode name: The name of the field :param list valid_formats: A list of datetime formats that are valid for translation. By default it accepts %Y-%m-%dT%H:%M:%S.%fZ """ super(DateTimeField, self).__init__(name, **kwargs) self.valid_formats = valid_formats or self.valid_formats self.minimum = minimum self.maximum = maximum
def _translate(self, obj, skip_required=False): """ First checks if the obj is None or already a datetime object Returns that if true. Otherwise assumes that it is a string and attempts to parse it out using the formats in self.valid_formats and the datetime.strptime method Additionally it strips out any whitespace from the beginning and end of the input before attempting to parse out a datetime string :param unicode obj: The input that is being translated :return: The parse datetime object :rtype: datetime """ if isinstance(obj, datetime): return obj obj = obj.strip() for date_format in self.valid_formats: try: return datetime.strptime(obj, date_format) except ValueError: continue raise TranslationException(self.error_message or 'The object ({0}) could not be parsed as a datetime ' 'string using the formats {1}'.format(obj, self.valid_formats)) def _validate(self, obj, skip_required=False): """ Just makes a size check on top of instance type check :param datetime obj: :return: The object unchanged :rtype: datetime :raises: ValidationException """ obj = basic_validation(self, obj, skip_required=skip_required) return validate_size(self, obj, obj, minimum=self.minimum, maximum=self.maximum, msg=self.error_message)
[docs]class ListField(IField): """ A field for a list of objects. A field for the individual results can also be provided. This would be run against every individual item in the list that is provided. """ field_type = list
[docs] def __init__(self, name, indv_field=None, minimum=None, maximum=None, **kwargs): """ :param unicode name: The name of the field. :param BaseField indv_field: The field to use when translating and validating individual items in the list. """ self.indv_field = indv_field or Field('list') super(ListField, self).__init__(name, **kwargs) self.minimum = minimum self.maximum = maximum
[docs] def translate(self, obj, **kwargs): """ Translates the object into a list. :param object obj: The object to translate. :return: The translated/validated object. :rtype: list """ obj = super(ListField, self).translate(obj, **kwargs) if obj is None: return obj translated_list = [] for field in obj: translated_field = self.indv_field.translate(field, **kwargs) translated_list.append(translated_field) return translated_list
def _translate(self, obj, skip_required=False): try: return list(obj) except TypeError: raise TranslationException(self.error_message or '{0} requires an iterable. The object' ' {1} is not'.format(self.name, obj)) def _validate(self, obj, skip_required=False): obj = basic_validation(self, obj, skip_required=skip_required) obj = obj or [] return validate_size(self, obj, len(obj), minimum=self.minimum, maximum=self.maximum, msg=self.error_message)
[docs]class DictField(IField): """ A field for a dictionary of objects. Each named sub-field can be mapped to individual fields (Or even nested DictField's). """ field_type = dict
[docs] def __init__(self, name, field_list=None, minimum=None, maximum=None, **kwargs): """ Calls super and sets the field_dict on the object. :param unicode name: The name of this field. :param list field_list: A list of fields the names of the fields are what is used as the key in the dictionary. :param dict kwargs: The standard arguments for BaseField.__init__ """ self.field_list = field_list or [] super(DictField, self).__init__(name, **kwargs) self.minimum = minimum self.maximum = maximum
[docs] def translate(self, obj, **kwargs): """ Translates and Validates the dictionary field and each of it's contained fields. It will iterate over this field_dict and translate and validate each of the individual key, value pairs. :param dict obj: The object to translate and validate (if ``validate=True``) :return: The translated (and possibly validated) object. :rtype: dict """ obj = super(DictField, self).translate(obj, **kwargs) if obj is None: return obj translated_dict = obj.copy() for field in self.field_list: key = field.name value = obj.get(key) translated_dict[key] = field.translate(value, **kwargs) return translated_dict
def _translate(self, obj, skip_required=False): if not hasattr(obj, 'get'): raise TranslationException(self.error_message or 'A dictionary field must have a get method ' 'that allows for retrieving an item with a default. ' 'For example a dictionary.') return obj def _validate(self, obj, skip_required=False): obj = basic_validation(self, obj, skip_required=skip_required) obj = obj or {} return validate_size(self, obj, len(obj), minimum=self.minimum, maximum=self.maximum, msg=self.error_message)