Source code for flyforms.fields

# coding=utf-8
"""
This module contains a collection of fields classes
"""

from abc import ABCMeta
from collections import Iterable
from itertools import chain
from warnings import warn

from flyforms.common import is_set, jsonify_types, UNSET
from flyforms.validators import *
from flyforms.validators import Validator
from flyforms.compatibility import string_types, with_metaclass, NoneType

__all__ = (
    "StringField",
    "EmailField",
    "IntField",
    "FloatField",
    "BooleanField",
    "Ip4Field",
    "ListField",
    "ArrayField"
)


[docs]class Field(with_metaclass(ABCMeta, object)): """ This the base class for all Fields. Fields instances reflect and validate data. """ value_types = (type,) # valid types of field value
[docs] def __init__(self, required=True, choices=(), validators=(), **kwargs): """ :param required: boolean flag is this field required or can be empty :type required: bool :param choices: iterable object contains possible values of this field :type choices: iterable :param validators: the additional validators for field :type validators: list of callable :param default: the default value of the field :type default: instance of value_types :raises TypeError: if passed arguments invalid """ # Define validators container self.base_validators = [] # Add required validation, if necessary if required: self.base_validators.append(RequiredValidator()) self.required = required # Check given default value self.default = kwargs.get("default", UNSET) if not isinstance(self.default, self.value_types) and self.default is not UNSET: raise TypeError( "Bad default value type. \nExpected {}, got {}".format(self.value_types, type(self.default)) ) if required and self.default is not UNSET: warn( "Warning in field %s: default value given for required field. I'll be skipped.", RuntimeWarning ) self.default = UNSET # Add type validation self.base_validators.append(TypedValidator(self.value_types)) # Check is given choices iterable object if not isinstance(choices, Iterable): raise TypeError("Choices should be an iterable object") # Check choices type for choice in choices: if not isinstance(choice, self.value_types): raise TypeError( "Bad value type in choices. \nExpected %s, got %s" % ( self.value_types, type(choice) ) ) # Add choices validation, if necessary if choices: self.base_validators.append(EntryValidator(choices)) # Clean given list of validators, if necessary if not isinstance(validators, Iterable): validators = (validators,) # Check is given validators are callable object and extend validators list for validator in validators: if not isinstance(validator, Validator): raise TypeError("Validator should be a child of Validator superclass") self.custom_validators = validators
[docs] def validate(self, value): """ Validates given value via defined set of :code:`Validators` :param value: the value to validate """ # Check is value set if not self.required and not is_set(value): return # Run all validators for validator in self.validators: validator(value)
[docs] def is_valid(self, value): """ The 'silent' variant of value validation. :param value: the value to validate :return: True if given value is valid, otherwise - False """ # Check is value set if not self.required and not is_set(value): return True # Check value via validators for validator in self.validators: if not validator.is_valid(value): return False return True
@property def validators(self): """ Chain, contains :code:`base_validators` and :code:`custom_validators` """ return chain(self.base_validators, self.custom_validators)
[docs] def bind(self, value): """ Validates and given value via defined set of :code:`Validators` and returns it If value is mutable obj (for example :code:`list`) it'll be converted to immutable (for example :code:`tuple`) :param value: the value to bind :return: given value or :code:`field.default` if value is :py:data:`.UNSET` """ # Check is value set if not self.required and not is_set(value): return self.default # Run all validators for validator in self.validators: validator(value) return value
[docs]class StringField(Field): """ Reflects Python strings """ value_types = string_types # valid types of field value
[docs] def __init__(self, min_length=None, max_length=None, regex="", **kwargs): """ :param required: boolean flag is this field required :type required: bool :param min_length: the minimum length of the string :type min_length: int or None :param max_length: the maximum length of the string :type max_length: int or None :param regex: the regular expression to validate :type regex: str or regexp :param choices: iterable object contains possible values of this field :type choices: iterable :param validators: the additional validators for field :type validators: list of callable :param default: the default value of the field :type default: instance of value_types """ super(StringField, self).__init__(**kwargs) if min_length: self.base_validators.append(MinLengthValidator(min_length, False)) if max_length: self.base_validators.append(MaxLengthValidator(max_length, False)) if regex: self.base_validators.append(RegexValidator(regex))
[docs]class EmailField(Field): """ Reflects Python string corresponding to an email """ value_types = string_types # valid types of field value
[docs] def __init__(self, **kwargs): """ :param required: boolean flag is this field required :type required: bool :param choices: iterable object contains possible values of this field :type choices: iterable :param validators: the additional validators for field :type validators: list of callable :param default: the default value of the field :type default: instance of value_types """ super(EmailField, self).__init__(**kwargs) self.base_validators.append(EmailValidator())
[docs]class IntField(Field): """ Reflects Python integer (:code:`int`) """ value_types = (int, NoneType) # valid types of field value
[docs] def __init__(self, min_value=None, max_value=None, **kwargs): """ :param required: boolean flag is this field required :type required: bool :param min_value: the minimum valid value :param max_value: the maximum valid value :param choices: iterable object contains possible values of this field :type choices: iterable :param validators: the additional validators for field :type validators: list of callable :param default: the default value of the field :type default: instance of value_types """ super(IntField, self).__init__(**kwargs) if min_value is not None: self.base_validators.append(MinValueValidator(min_value, False)) if max_value is not None: self.base_validators.append(MaxValueValidator(max_value, False))
[docs]class FloatField(IntField): """ Reflects Python float (:code:`float`) """ value_types = (float, NoneType) # valid types of field value
[docs]class BooleanField(Field): """ Reflects Python boolean (:code:`bool`) """ value_types = (bool, NoneType) # valid types of field value
[docs] def __init__(self, **kwargs): """ :param required: boolean flag is this field required :type required: bool :param validators: the additional validators for field :type validators: list of callable :param default: the default value of the field :type default: instance of value_types """ super(BooleanField, self).__init__(**kwargs)
[docs]class Ip4Field(Field): """ Reflects Python string corresponding to an IPv4 address """ value_types = string_types # valid types of field value
[docs] def __init__(self, **kwargs): """ :param required: boolean flag is this field required :type required: bool :param choices: iterable object contains possible values of this field :type choices: iterable :param validators: the additional validators for field :type validators: list of callable :param default: the default value of the field :type default: instance of value_types """ super(Ip4Field, self).__init__(**kwargs) self.base_validators.append(Ip4AddressValidator())
[docs]class ListField(Field): """ Reflects iterable Python objects """ value_types = Iterable
[docs] def __init__(self, min_length=None, max_length=None, jsonify=True, **kwargs): """ :param min_length: minimum iterable length :param max_length: maximum iterable length :param jsonify: if passed :code:`item_type` should be one of :py:data:`.jsonify_types` :param kwargs: basic :py:class:`.Field` parameters """ super(ListField, self).__init__(**kwargs) if min_length: self.base_validators.append(MinLengthValidator(min_length, False)) if max_length: self.base_validators.append(MaxLengthValidator(max_length, False)) if jsonify: self.base_validators.append(JsonItemTypedValidator())
def bind(self, value): value = super(ListField, self).bind(value) return tuple(value)
[docs]class ArrayField(ListField): """ Reflects iterable objects where each item same type """
[docs] def __init__(self, item_type, jsonify=True, **kwargs): """ :param item_type: type of each item in the list :param kwargs: basic :py:class:`.ListField` parameters """ if jsonify and item_type not in jsonify_types: raise TypeError("Type %s is not supported for JSON encoding and decoding operations" % item_type) super(ArrayField, self).__init__(jsonify=jsonify, **kwargs) self.base_validators.append(ItemTypedValidator(item_type))