# Source code for flyforms.fields

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

    from abc import ABCMeta
    from collections import Iterable
    from itertools import chain
    from datetime import datetime

    from flyforms.common import is_set, jsonify_types, UNSET, FrozenDict
    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",
        "DatetimeField",
        "DictField"
    )

    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

        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:
                warnings.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

        def validate(self, value):
            """
            Validates given value via defined set of :code:`Validators`

            :param value: the value to validate
            """
            warnings.simplefilter('always', DeprecationWarning)
            warnings.warn("This method deprecated and will be removed in v1.0.0")
            warnings.simplefilter('ignore', DeprecationWarning)

            # Check is value set
            if not self.required and not is_set(value):
                return

            # Run all validators
            for validator in self.validators:
                validator(value)

        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
            """

            warnings.simplefilter('always', DeprecationWarning)
            warnings.warn("This method deprecated and will be removed in v1.0.0")
            warnings.simplefilter('ignore', DeprecationWarning)

            # 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)

        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

            :param name: field name (optional)
            :type name: str

            :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

    class StringField(Field):
        """
        Reflects Python strings
        """

        value_types = string_types  # valid types of field value

        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))

    class EmailField(Field):
        """
        Reflects Python string corresponding to an email
        """

        value_types = string_types  # valid types of field value

        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())

    class IntField(Field):
        """
        Reflects Python integer (:code:`int`)
        """

        value_types = (int, NoneType)  # valid types of field value

        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))

    class FloatField(IntField):
        """
        Reflects Python float (:code:`float`)
        """

        value_types = (float, NoneType)  # valid types of field value

    class BooleanField(Field):
        """
        Reflects Python boolean (:code:`bool`)
        """

        value_types = (bool, NoneType)  # valid types of field value

        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)

    class Ip4Field(Field):
        """
        Reflects Python string corresponding to an IPv4 address
        """

        value_types = string_types  # valid types of field value

        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())

    class ListField(Field):
        """
        Reflects iterable Python objects
        """
        value_types = Iterable

        def __init__(self, min_length=None, max_length=None, jsonify=True, **kwargs):
            """
            :param required: boolean flag is this field required or can be empty
            :type required: bool

            :param min_length: minimum iterable length
            :type min_length: int

            :param max_length: maximum iterable length
            :type max_length: int

            :param jsonify: if passed :code:`item_type` should be one of :py:data:`.jsonify_types`
            :type jsonify: 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

            .. versionadded:: 0.2.0
            """

            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)

    class ArrayField(ListField):
        """

        .. versionadded:: 0.2.0

        Reflects iterable objects where each item same type
        """

        def __init__(self, item_type, jsonify=True, **kwargs):
            """
            :param item_type: type of each item in the list

            :param min_length: minimum iterable length
            :type min_length: int

            :param max_length: maximum iterable length
            :type max_length: int

            :param jsonify: if passed :code:`item_type` should be one of :py:data:`.jsonify_types`
            :type jsonify: bool

            :param required: boolean flag is this field required or can be empty
            :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
            """

            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))

    class DatetimeField(StringField):
        """
        .. versionadded:: 0.3.0

        A datetime field.

        Parse string contains datetime via datetime.strptime
        """

        def __init__(self, required=True, fmt="%Y-%m-%d %H:%M:%S", now=False):
            """
            :param fmt: datetime format
            :type fmt: str

            :param now: if passed the default value will be datetime.now()
            :type now: bool

            .. versionadded:: 0.3.0

            """

            super(DatetimeField, self).__init__(required=required)

            self.fmt = fmt

            if now:
                self.default = datetime.now().strftime(self.fmt)

        def bind(self, value):
            """
            Validates given datetime and wraps it into :py:class:`datetime.datetime`
            """
            try:
                return datetime.strptime(super(DatetimeField, self).bind(value), self.fmt)
            except ValueError as error:
                raise ValidationError(str(error))

    class DictField(Field):
        """

        .. versionadded:: 0.3.0

        Reflects Python dict
        """

        value_types = dict

        def __init__(self, schema, **kwargs):
            """
            :param schema: template to dict validation
            :type schema: dict

            :param required: boolean flag is this field required or can be empty
            :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
            """

            if not isinstance(schema, dict):
                raise TypeError("Schema for DictField should be dict instance")

            for key, value in schema.items():
                if not isinstance(value, Field):
                    raise TypeError("Bad field %s in schema. Expected Field subclass instance, got %s" % type(value))

            super(DictField, self).__init__(**kwargs)
            self.schema = schema

        def bind(self, value):
            """
            Validates given dict and wraps it into :py:class:`.FrozenDict`
            """
            d = super(DictField, self).bind(value)
            assert isinstance(d, dict)
            if d is not self.default:
                for key, value in d.items():
                    if key not in self.schema:
                        raise ValidationError("Unknown key }".format(key))
                    d[key] = self.schema[key].bind(value)
            return FrozenDict(**d)