<commit_before># -*- coding: utf-8 -*-

import sys
import functools
import inspect
import re
import copy
import itertools
import numbers
import pkg_resources


# Python3 support
py3 = sys.version_info[0] == 3
if py3:
    import urllib.parse as urlparse
    str_types = (str, bytes)
    unicode = str
else:
    try:
        from future_builtins import map
    except ImportError:
        # Support for GAE runner
        from itertools import imap as map
    import urlparse
    str_types = (basestring,)

"""
Trafaret is tiny library for data validation
It provides several primitives to validate complex data structures
Look at doctests for usage examples
"""

__all__ = ("DataError", "Trafaret", "Any", "Int", "String",
           "List", "Dict", "Or", "Null", "Float", "Enum", "Callable",
           "Call", "Forward", "Bool", "Type", "Mapping", "guard", "Key",
           "Tuple", "Atom", "Email", "URL")

ENTRY_POINT = 'trafaret'
_empty = object()

def py3metafix(cls):
    if not py3:
        return cls
    else:
        newcls = cls.__metaclass__(cls.__name__, (cls,), {})
        newcls.__doc__ = cls.__doc__
        return newcls


class DataError(ValueError):

    """
    Error with data preserve
    error can be a message or None if error raised in childs
    data can be anything
    """

    def __init__(self, error=None, name=None):
        self.error = error
        self.name = name

    def __str__(self):
        return str(self.error)

    def __repr__(self):
        return 'DataError(%s)' % str(self)

    def as_dict(self):
        def as_dict(dataerror):
            if not isinstance(dataerror.error, dict):
                return self.error
            return dict((k, v.as_dict() if isinstance(v, DataError) else v)
                        for k, v in dataerror.error.items())
        return as_dict(self)


class TrafaretMeta(type):

    """
    Metaclass for trafarets to make using "|" operator possible not only
    on instances but on classes

    >>> Int | String
    <Or(<Int>, <String>)>
    >>> Int | String | Null
    <Or(<Int>, <String>, <Null>)>
    >>> (Int >> (lambda v: v if v ** 2 > 15 else 0)).check(5)
    5
    """

    def __or__(cls, other):
        return cls() | other

    def __rshift__(cls, other):
        return cls() >> other


@py3metafix
class Trafaret(object):

    """
    Base class for trafarets, provides only one method for
    trafaret validation failure reporting

    Check that converters can be stacked
    >>> (Int() >> (lambda x: x * 2) >> (lambda x: x * 3)).check(1)
    6

    Check order
    >>> (Int() >> float >> str).check(4)
    '4.0'
    """

    __metaclass__ = TrafaretMeta

    def check(self, value):
        """
        Common logic. In subclasses you need to implement check_value or
        check_and_return.
        """
        if hasattr(self, 'check_value'):
            self.check_value(value)
            return self._convert(value)
        if hasattr(self, 'check_and_return'):
            return self._convert(self.check_and_return(value))
        cls = "%s.%s" % (type(self).__module__, type(self).__name__)
        raise NotImplementedError("You must implement check_value or"
                                  " check_and_return methods '%s'" % cls)

    def converter(self, value):
        """
        You can change converter with `>>` operator or append method
        """
        return value

    def _convert(self, value):
        val = value
        for converter in getattr(self, 'converters', [self.converter]):
            val = converter(val)
        return val

    def _failure(self, error=None):
        """
        Shortcut method for raising validation error
        """
        raise DataError(error=error)

    def _trafaret(self, trafaret):
        """
        Helper for complex trafarets, takes trafaret instance or class
        and returns trafaret instance
        """
        if isinstance(trafaret, Trafaret) or inspect.isroutine(trafaret):
            return trafaret
        elif issubclass(trafaret, Trafaret):
            return trafaret()
        elif isinstance(trafaret, type):
            return Type(trafaret)
        else:
            raise RuntimeError("%r should be instance or subclass"
                               " of Trafaret" % trafaret)

    def append(self, converter):
        """
        Appends new converter to list.
        """
        if hasattr(self, 'converters'):
            self.converters.append(converter)
        else:
            self.converters = [converter]
        return self

    def __or__(self, other):
        return Or(self, other)

    def __rshift__(self, other):
        self.append(other)
        return self

    def __call__(self, val):
        return self.check(val)


class TypeMeta(TrafaretMeta):

    def __getitem__(self, type_):
        return self(type_)


@py3metafix
class Type(Trafaret):

    """
    >>> Type(int)
    <Type(int)>
    >>> Type[int]
    <Type(int)>
    >>> c = Type[int]
    >>> c.check(1)
    1
    >>> extract_error(c, "foo")
    'value is not int'
    """
    __metaclass__ = TypeMeta

    def __init__(self, type_):
        self.type_ = type_

    def check_value(self, value):
        if not isinstance(value, self.type_):
            self._failure("value is not %s" % self.type_.__name__)

    def __repr__(self):
        return "<Type(%s)>" % self.type_.__name__


class Any(Trafaret):

    """
    >>> Any()
    <Any>
    >>> (Any() >> ignore).check(object())
    """

    def check_value(self, value):
        pass

    def __repr__(self):
        return "<Any>"


class OrMeta(TrafaretMeta):

    """
    Allows to use "<<" operator on Or class

    >>> Or << Int << String
    <Or(<Int>, <String>)>
    """

    def __lshift__(cls, other):
        return cls() << other


@py3metafix
class Or(Trafaret):

    """
    >>> nullString = Or(String, Null)
    >>> nullString
    <Or(<String>, <Null>)>
    >>> nullString.check(None)
    >>> nullString.check("test")
    'test'
    >>> extract_error(nullString, 1)
    {0: 'value is not a string', 1: 'value should be None'}
    """

    __metaclass__ = OrMeta

    def __init__(self, *trafarets):
        self.trafarets = list(map(self._trafaret, trafarets))

    def check_and_return(self, value):
        errors = []
        for trafaret in self.trafarets:
            try:
                return trafaret.check(value)
            except DataError as e:
                errors.append(e)
        raise DataError(dict(enumerate(errors)))

    def __lshift__(self, trafaret):
        self.trafarets.append(self._trafaret(trafaret))
        return self

    def __or__(self, trafaret):
        self << trafaret
        return self

    def __repr__(self):
        return "<Or(%s)>" % (", ".join(map(repr, self.trafarets)))


class Null(Trafaret):

    """
    >>> Null()
    <Null>
    >>> Null().check(None)
    >>> extract_error(Null(), 1)
    'value should be None'
    """

    def check_value(self, value):
        if value is not None:
            self._failure("value should be None")

    def __repr__(self):
        return "<Null>"


class Bool(Trafaret):

    """
    >>> Bool()
    <Bool>
    >>> Bool().check(True)
    True
    >>> Bool().check(False)
    False
    >>> extract_error(Bool(), 1)
    'value 1 should be True or False'
    """

    def check_value(self, value):
        if not isinstance(value, bool):
            self._failure("value %s should be True or False" % value)

    def __repr__(self):
        return "<Bool>"


class StrBool(Trafaret):

    """
    >>> extract_error(StrBool(), 'aloha')
    "value aloha can't be converted to Bool"
    >>> StrBool().check(1)
    True
    >>> StrBool().check(0)
    False
    >>> StrBool().check('y')
    True
    >>> StrBool().check('n')
    False
    >>> StrBool().check(None)
    False
    >>> StrBool().check('1')
    True
    >>> StrBool().check('0')
    False
    >>> StrBool().check('YeS')
    True
    >>> StrBool().check('No')
    False
    >>> StrBool().check(True)
    True
    >>> StrBool().check(False)
    False
    """

    convertable = ('t', 'true', 'false', 'y', 'n', 'yes', 'no', 'on',
                   '1', '0', 'none')

    def _check(self, value):
        _value = str(value).strip().lower()
        if _value not in self.convertable:
            self._failure("value %s can't be converted to Bool" % value)

    def converter(self, value):
        if value is None:
            return False
        _str = str(value).strip().lower()

        return _str in ('t', 'true', 'y', 'yes', 'on', '1')

    def __repr__(self):
        return "<StrBool>"


class NumberMeta(TrafaretMeta):

    """
    Allows slicing syntax for min and max arguments for
    number trafarets

    >>> Int[1:]
    <Int(gte=1)>
    >>> Int[1:10]
    <Int(gte=1, lte=10)>
    >>> Int[:10]
    <Int(lte=10)>
    >>> Float[1:]
    <Float(gte=1)>
    >>> Int > 3
    <Int(gt=3)>
    >>> 1 < (Float < 10)
    <Float(gt=1, lt=10)>
    >>> (Int > 5).check(10)
    10
    >>> extract_error(Int > 5, 1)
    'value 1 should be greater than 5'
    >>> (Int < 3).check(1)
    1
    >>> extract_error(Int < 3, 3)
    'value 3 should be less than 3'
    """

    def __getitem__(cls, slice_):
        return cls(gte=slice_.start, lte=slice_.stop)

    def __lt__(cls, lt):
        return cls(lt=lt)

    def __gt__(cls, gt):
        return cls(gt=gt)


@py3metafix
class Float(Trafaret):

    """
    >>> Float()
    <Float>
    >>> Float(gte=1)
    <Float(gte=1)>
    >>> Float(lte=10)
    <Float(lte=10)>
    >>> Float(gte=1, lte=10)
    <Float(gte=1, lte=10)>
    >>> Float().check(1.0)
    1.0
    >>> extract_error(Float(), 1 + 3j)
    'value (1+3j) is not float'
    >>> extract_error(Float(), 1)
    1.0
    >>> Float(gte=2).check(3.0)
    3.0
    >>> extract_error(Float(gte=2), 1.0)
    'value 1.0 is less than 2'
    >>> Float(lte=10).check(5.0)
    5.0
    >>> extract_error(Float(lte=3), 5.0)
    'value 5.0 is greater than 3'
    >>> Float().check("5.0")
    5.0
    """

    __metaclass__ = NumberMeta

    convertable = str_types + (numbers.Real,)
    value_type = float

    def __init__(self, gte=None, lte=None, gt=None, lt=None):
        self.gte = gte
        self.lte = lte
        self.gt = gt
        self.lt = lt

    def _converter(self, val):
        if not isinstance(val, self.convertable):
            self._failure('value %s is not %s' % (val, self.value_type.__name__))
        try:
            return self.value_type(val)
        except ValueError:
            self._failure(
                "value %s can't be converted to %s" % (
                    val, self.value_type.__name__
                )
            )

    def check_and_return(self, val):
        if not isinstance(val, self.value_type):
            value = self._converter(val)
        else:
            value = val
        if self.gte is not None and value < self.gte:
            self._failure("value %s is less than %s" % (value, self.gte))
        if self.lte is not None and value > self.lte:
            self._failure("value %s is greater than %s" % (value, self.lte))
        if self.lt is not None and value >= self.lt:
            self._failure("value %s should be less than %s" % (value, self.lt))
        if self.gt is not None and value <= self.gt:
            self._failure("value %s should be greater than %s" % (value, self.gt))
        return value

    def __lt__(self, lt):
        return type(self)(gte=self.gte, lte=self.lte, gt=self.gt, lt=lt)

    def __gt__(self, gt):
        return type(self)(gte=self.gte, lte=self.lte, gt=gt, lt=self.lt)

    def __repr__(self):
        r = "<%s" % type(self).__name__
        options = []
        for param in ("gte", "lte", "gt", "lt"):
            if getattr(self, param) is not None:
                options.append("%s=%s" % (param, getattr(self, param)))
        if options:
            r += "(%s)" % (", ".join(options))
        r += ">"
        return r


class Int(Float):

    """
    >>> Int()
    <Int>
    >>> Int().check(5)
    5
    >>> extract_error(Int(), 1.1)
    'value 1.1 is not int'
    >>> extract_error(Int(), 1 + 1j)
    'value (1+1j) is not int'
    """

    value_type = int

    def _converter(self, val):
        if isinstance(val, float):
            if not val.is_integer():
                self._failure('value %s is not int' % (val))
        return super(Int, self)._converter(val)


class Atom(Trafaret):

    """
    >>> Atom('atom').check('atom')
    'atom'
    >>> extract_error(Atom('atom'), 'molecule')
    "value is not exactly 'atom'"
    """

    def __init__(self, value):
        self.value = value

    def check_value(self, value):
        if self.value != value:
            self._failure("value is not exactly '%s'" % self.value)


class String(Trafaret):

    """
    >>> String()
    <String>
    >>> String(allow_blank=True)
    <String(blank)>
    >>> String().check("foo")
    'foo'
    >>> extract_error(String(), "")
    'blank value is not allowed'
    >>> String(allow_blank=True).check("")
    ''
    >>> extract_error(String(), 1)
    'value is not a string'
    >>> String(regex='\w+').check('wqerwqer')
    'wqerwqer'
    >>> extract_error(String(regex='^\w+$'), 'wqe rwqer')
    "value 'wqe rwqer' does not match pattern: ^\\\w+$"
    >>> String(min_length=2, max_length=3).check('123')
    '123
    >>> extract_error(String(min_length=2, max_length=6), '1')'
    'String is shorter than 2 characters'
    >>> extract_error(String(min_length=2, max_length=6), '1234567')
    'String is longer than 6 characters'
    >>> String(min_length=2, max_length=6, allow_blank=True)
    Traceback (most recent call last):
    ...
    AssertionError: Either allow_blank or min_length should be specified, not both
    >>> String(min_length=0, max_length=6, allow_blank=True).check('123')
    '123'
    """

    def __init__(self, allow_blank=False, regex=None, min_length=None, max_length=None):
        assert not (allow_blank and min_length), \
            "Either allow_blank or min_length should be specified, not both"
        self.allow_blank = allow_blank
        self.regex = re.compile(regex) if isinstance(regex, str_types) else regex
        self.min_length = min_length
        self.max_length = max_length
        self._raw_regex = self.regex.pattern if self.regex else None

    def check_and_return(self, value):
        if not isinstance(value, str_types):
            self._failure("value is not a string")
        if not self.allow_blank and len(value) is 0:
            self._failure("blank value is not allowed")
        if self.min_length is not None and len(value) < self.min_length:
            self._failure('String is shorter than %s characters' % self.min_length)
        if self.max_length is not None and len(value) > self.max_length:
            self._failure('String is longer than %s characters' % self.max_length)
        if self.regex is not None:
            match = self.regex.match(value)
            if not match:
                self._failure("value '%s' does not match pattern: %s" % (
                    value, self._raw_regex)
                )
            return match
        return value

    def converter(self, value):
        if isinstance(value, str_types):
            return value
        return value.group()

    def __repr__(self):
        return "<String(blank)>" if self.allow_blank else "<String>"


class Email(String):

    """
    >>> Email().check('<EMAIL>')
    '<EMAIL>'
    >>> (Email() >> (lambda m: m.groupdict()['domain'])).check('<EMAIL>')
    'example.net'
    >>> extract_error(Email(),'foo')
    'foo value is not an email'
    """

    regex = re.compile(
        r"(?P<name>^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
        r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
        r')@(?P<domain>(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$)'  # domain
        r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE)  # literal form, ipv4 address (SMTP 4.1.3)
    min_length = None
    max_length = None

    def __init__(self, allow_blank=False):
        super(Email, self).__init__(allow_blank=allow_blank, regex=self.regex)

    def check_and_return(self, value):
        try:
            return super(Email, self).check_and_return(value)
        except DataError:
            # Trivial case failed. Try for possible IDN domain-part
            if value and '@' in value:
                parts = value.split('@')
                try:
                    parts[-1] = parts[-1].encode('idna')
                except UnicodeError:
                    pass
                else:
                    return super(Email, self).check_and_return('@'.join(parts))
        self._failure('value is not a valid email address')

    def __repr__(self):<commit_msg>Fixed t.Email validator for Python3 on idna suspected emails.<commit_after># -*- coding: utf-8 -*-

import sys
import functools
import inspect
import re
import copy
import itertools
import numbers
import pkg_resources


# Python3 support
py3 = sys.version_info[0] == 3
if py3:
    import urllib.parse as urlparse
    str_types = (str, bytes)
    unicode = str
else:
    try:
        from future_builtins import map
    except ImportError:
        # Support for GAE runner
        from itertools import imap as map
    import urlparse
    str_types = (basestring,)

"""
Trafaret is tiny library for data validation
It provides several primitives to validate complex data structures
Look at doctests for usage examples
"""

__all__ = ("DataError", "Trafaret", "Any", "Int", "String",
           "List", "Dict", "Or", "Null", "Float", "Enum", "Callable",
           "Call", "Forward", "Bool", "Type", "Mapping", "guard", "Key",
           "Tuple", "Atom", "Email", "URL")

ENTRY_POINT = 'trafaret'
_empty = object()

def py3metafix(cls):
    if not py3:
        return cls
    else:
        newcls = cls.__metaclass__(cls.__name__, (cls,), {})
        newcls.__doc__ = cls.__doc__
        return newcls


class DataError(ValueError):

    """
    Error with data preserve
    error can be a message or None if error raised in childs
    data can be anything
    """

    def __init__(self, error=None, name=None):
        self.error = error
        self.name = name

    def __str__(self):
        return str(self.error)

    def __repr__(self):
        return 'DataError(%s)' % str(self)

    def as_dict(self):
        def as_dict(dataerror):
            if not isinstance(dataerror.error, dict):
                return self.error
            return dict((k, v.as_dict() if isinstance(v, DataError) else v)
                        for k, v in dataerror.error.items())
        return as_dict(self)


class TrafaretMeta(type):

    """
    Metaclass for trafarets to make using "|" operator possible not only
    on instances but on classes

    >>> Int | String
    <Or(<Int>, <String>)>
    >>> Int | String | Null
    <Or(<Int>, <String>, <Null>)>
    >>> (Int >> (lambda v: v if v ** 2 > 15 else 0)).check(5)
    5
    """

    def __or__(cls, other):
        return cls() | other

    def __rshift__(cls, other):
        return cls() >> other


@py3metafix
class Trafaret(object):

    """
    Base class for trafarets, provides only one method for
    trafaret validation failure reporting

    Check that converters can be stacked
    >>> (Int() >> (lambda x: x * 2) >> (lambda x: x * 3)).check(1)
    6

    Check order
    >>> (Int() >> float >> str).check(4)
    '4.0'
    """

    __metaclass__ = TrafaretMeta

    def check(self, value):
        """
        Common logic. In subclasses you need to implement check_value or
        check_and_return.
        """
        if hasattr(self, 'check_value'):
            self.check_value(value)
            return self._convert(value)
        if hasattr(self, 'check_and_return'):
            return self._convert(self.check_and_return(value))
        cls = "%s.%s" % (type(self).__module__, type(self).__name__)
        raise NotImplementedError("You must implement check_value or"
                                  " check_and_return methods '%s'" % cls)

    def converter(self, value):
        """
        You can change converter with `>>` operator or append method
        """
        return value

    def _convert(self, value):
        val = value
        for converter in getattr(self, 'converters', [self.converter]):
            val = converter(val)
        return val

    def _failure(self, error=None):
        """
        Shortcut method for raising validation error
        """
        raise DataError(error=error)

    def _trafaret(self, trafaret):
        """
        Helper for complex trafarets, takes trafaret instance or class
        and returns trafaret instance
        """
        if isinstance(trafaret, Trafaret) or inspect.isroutine(trafaret):
            return trafaret
        elif issubclass(trafaret, Trafaret):
            return trafaret()
        elif isinstance(trafaret, type):
            return Type(trafaret)
        else:
            raise RuntimeError("%r should be instance or subclass"
                               " of Trafaret" % trafaret)

    def append(self, converter):
        """
        Appends new converter to list.
        """
        if hasattr(self, 'converters'):
            self.converters.append(converter)
        else:
            self.converters = [converter]
        return self

    def __or__(self, other):
        return Or(self, other)

    def __rshift__(self, other):
        self.append(other)
        return self

    def __call__(self, val):
        return self.check(val)


class TypeMeta(TrafaretMeta):

    def __getitem__(self, type_):
        return self(type_)


@py3metafix
class Type(Trafaret):

    """
    >>> Type(int)
    <Type(int)>
    >>> Type[int]
    <Type(int)>
    >>> c = Type[int]
    >>> c.check(1)
    1
    >>> extract_error(c, "foo")
    'value is not int'
    """
    __metaclass__ = TypeMeta

    def __init__(self, type_):
        self.type_ = type_

    def check_value(self, value):
        if not isinstance(value, self.type_):
            self._failure("value is not %s" % self.type_.__name__)

    def __repr__(self):
        return "<Type(%s)>" % self.type_.__name__


class Any(Trafaret):

    """
    >>> Any()
    <Any>
    >>> (Any() >> ignore).check(object())
    """

    def check_value(self, value):
        pass

    def __repr__(self):
        return "<Any>"


class OrMeta(TrafaretMeta):

    """
    Allows to use "<<" operator on Or class

    >>> Or << Int << String
    <Or(<Int>, <String>)>
    """

    def __lshift__(cls, other):
        return cls() << other


@py3metafix
class Or(Trafaret):

    """
    >>> nullString = Or(String, Null)
    >>> nullString
    <Or(<String>, <Null>)>
    >>> nullString.check(None)
    >>> nullString.check("test")
    'test'
    >>> extract_error(nullString, 1)
    {0: 'value is not a string', 1: 'value should be None'}
    """

    __metaclass__ = OrMeta

    def __init__(self, *trafarets):
        self.trafarets = list(map(self._trafaret, trafarets))

    def check_and_return(self, value):
        errors = []
        for trafaret in self.trafarets:
            try:
                return trafaret.check(value)
            except DataError as e:
                errors.append(e)
        raise DataError(dict(enumerate(errors)))

    def __lshift__(self, trafaret):
        self.trafarets.append(self._trafaret(trafaret))
        return self

    def __or__(self, trafaret):
        self << trafaret
        return self

    def __repr__(self):
        return "<Or(%s)>" % (", ".join(map(repr, self.trafarets)))


class Null(Trafaret):

    """
    >>> Null()
    <Null>
    >>> Null().check(None)
    >>> extract_error(Null(), 1)
    'value should be None'
    """

    def check_value(self, value):
        if value is not None:
            self._failure("value should be None")

    def __repr__(self):
        return "<Null>"


class Bool(Trafaret):

    """
    >>> Bool()
    <Bool>
    >>> Bool().check(True)
    True
    >>> Bool().check(False)
    False
    >>> extract_error(Bool(), 1)
    'value 1 should be True or False'
    """

    def check_value(self, value):
        if not isinstance(value, bool):
            self._failure("value %s should be True or False" % value)

    def __repr__(self):
        return "<Bool>"


class StrBool(Trafaret):

    """
    >>> extract_error(StrBool(), 'aloha')
    "value aloha can't be converted to Bool"
    >>> StrBool().check(1)
    True
    >>> StrBool().check(0)
    False
    >>> StrBool().check('y')
    True
    >>> StrBool().check('n')
    False
    >>> StrBool().check(None)
    False
    >>> StrBool().check('1')
    True
    >>> StrBool().check('0')
    False
    >>> StrBool().check('YeS')
    True
    >>> StrBool().check('No')
    False
    >>> StrBool().check(True)
    True
    >>> StrBool().check(False)
    False
    """

    convertable = ('t', 'true', 'false', 'y', 'n', 'yes', 'no', 'on',
                   '1', '0', 'none')

    def _check(self, value):
        _value = str(value).strip().lower()
        if _value not in self.convertable:
            self._failure("value %s can't be converted to Bool" % value)

    def converter(self, value):
        if value is None:
            return False
        _str = str(value).strip().lower()

        return _str in ('t', 'true', 'y', 'yes', 'on', '1')

    def __repr__(self):
        return "<StrBool>"


class NumberMeta(TrafaretMeta):

    """
    Allows slicing syntax for min and max arguments for
    number trafarets

    >>> Int[1:]
    <Int(gte=1)>
    >>> Int[1:10]
    <Int(gte=1, lte=10)>
    >>> Int[:10]
    <Int(lte=10)>
    >>> Float[1:]
    <Float(gte=1)>
    >>> Int > 3
    <Int(gt=3)>
    >>> 1 < (Float < 10)
    <Float(gt=1, lt=10)>
    >>> (Int > 5).check(10)
    10
    >>> extract_error(Int > 5, 1)
    'value 1 should be greater than 5'
    >>> (Int < 3).check(1)
    1
    >>> extract_error(Int < 3, 3)
    'value 3 should be less than 3'
    """

    def __getitem__(cls, slice_):
        return cls(gte=slice_.start, lte=slice_.stop)

    def __lt__(cls, lt):
        return cls(lt=lt)

    def __gt__(cls, gt):
        return cls(gt=gt)


@py3metafix
class Float(Trafaret):

    """
    >>> Float()
    <Float>
    >>> Float(gte=1)
    <Float(gte=1)>
    >>> Float(lte=10)
    <Float(lte=10)>
    >>> Float(gte=1, lte=10)
    <Float(gte=1, lte=10)>
    >>> Float().check(1.0)
    1.0
    >>> extract_error(Float(), 1 + 3j)
    'value (1+3j) is not float'
    >>> extract_error(Float(), 1)
    1.0
    >>> Float(gte=2).check(3.0)
    3.0
    >>> extract_error(Float(gte=2), 1.0)
    'value 1.0 is less than 2'
    >>> Float(lte=10).check(5.0)
    5.0
    >>> extract_error(Float(lte=3), 5.0)
    'value 5.0 is greater than 3'
    >>> Float().check("5.0")
    5.0
    """

    __metaclass__ = NumberMeta

    convertable = str_types + (numbers.Real,)
    value_type = float

    def __init__(self, gte=None, lte=None, gt=None, lt=None):
        self.gte = gte
        self.lte = lte
        self.gt = gt
        self.lt = lt

    def _converter(self, val):
        if not isinstance(val, self.convertable):
            self._failure('value %s is not %s' % (val, self.value_type.__name__))
        try:
            return self.value_type(val)
        except ValueError:
            self._failure(
                "value %s can't be converted to %s" % (
                    val, self.value_type.__name__
                )
            )

    def check_and_return(self, val):
        if not isinstance(val, self.value_type):
            value = self._converter(val)
        else:
            value = val
        if self.gte is not None and value < self.gte:
            self._failure("value %s is less than %s" % (value, self.gte))
        if self.lte is not None and value > self.lte:
            self._failure("value %s is greater than %s" % (value, self.lte))
        if self.lt is not None and value >= self.lt:
            self._failure("value %s should be less than %s" % (value, self.lt))
        if self.gt is not None and value <= self.gt:
            self._failure("value %s should be greater than %s" % (value, self.gt))
        return value

    def __lt__(self, lt):
        return type(self)(gte=self.gte, lte=self.lte, gt=self.gt, lt=lt)

    def __gt__(self, gt):
        return type(self)(gte=self.gte, lte=self.lte, gt=gt, lt=self.lt)

    def __repr__(self):
        r = "<%s" % type(self).__name__
        options = []
        for param in ("gte", "lte", "gt", "lt"):
            if getattr(self, param) is not None:
                options.append("%s=%s" % (param, getattr(self, param)))
        if options:
            r += "(%s)" % (", ".join(options))
        r += ">"
        return r


class Int(Float):

    """
    >>> Int()
    <Int>
    >>> Int().check(5)
    5
    >>> extract_error(Int(), 1.1)
    'value 1.1 is not int'
    >>> extract_error(Int(), 1 + 1j)
    'value (1+1j) is not int'
    """

    value_type = int

    def _converter(self, val):
        if isinstance(val, float):
            if not val.is_integer():
                self._failure('value %s is not int' % (val))
        return super(Int, self)._converter(val)


class Atom(Trafaret):

    """
    >>> Atom('atom').check('atom')
    'atom'
    >>> extract_error(Atom('atom'), 'molecule')
    "value is not exactly 'atom'"
    """

    def __init__(self, value):
        self.value = value

    def check_value(self, value):
        if self.value != value:
            self._failure("value is not exactly '%s'" % self.value)


class String(Trafaret):

    """
    >>> String()
    <String>
    >>> String(allow_blank=True)
    <String(blank)>
    >>> String().check("foo")
    'foo'
    >>> extract_error(String(), "")
    'blank value is not allowed'
    >>> String(allow_blank=True).check("")
    ''
    >>> extract_error(String(), 1)
    'value is not a string'
    >>> String(regex='\w+').check('wqerwqer')
    'wqerwqer'
    >>> extract_error(String(regex='^\w+$'), 'wqe rwqer')
    "value 'wqe rwqer' does not match pattern: ^\\\w+$"
    >>> String(min_length=2, max_length=3).check('123')
    '123
    >>> extract_error(String(min_length=2, max_length=6), '1')'
    'String is shorter than 2 characters'
    >>> extract_error(String(min_length=2, max_length=6), '1234567')
    'String is longer than 6 characters'
    >>> String(min_length=2, max_length=6, allow_blank=True)
    Traceback (most recent call last):
    ...
    AssertionError: Either allow_blank or min_length should be specified, not both
    >>> String(min_length=0, max_length=6, allow_blank=True).check('123')
    '123'
    """

    def __init__(self, allow_blank=False, regex=None, min_length=None, max_length=None):
        assert not (allow_blank and min_length), \
            "Either allow_blank or min_length should be specified, not both"
        self.allow_blank = allow_blank
        self.regex = re.compile(regex) if isinstance(regex, str_types) else regex
        self.min_length = min_length
        self.max_length = max_length
        self._raw_regex = self.regex.pattern if self.regex else None

    def check_and_return(self, value):
        if not isinstance(value, str_types):
            self._failure("value is not a string")
        if not self.allow_blank and len(value) is 0:
            self._failure("blank value is not allowed")
        if self.min_length is not None and len(value) < self.min_length:
            self._failure('String is shorter than %s characters' % self.min_length)
        if self.max_length is not None and len(value) > self.max_length:
            self._failure('String is longer than %s characters' % self.max_length)
        if self.regex is not None:
            match = self.regex.match(value)
            if not match:
                self._failure("value '%s' does not match pattern: %s" % (
                    value, self._raw_regex)
                )
            return match
        return value

    def converter(self, value):
        if isinstance(value, str_types):
            return value
        return value.group()

    def __repr__(self):
        return "<String(blank)>" if self.allow_blank else "<String>"


class Email(String):

    """
    >>> Email().check('<EMAIL>')
    '<EMAIL>'
    >>> extract_error(Email(),'<EMAIL>') # try without domain-part
    'value is not a valid email address'
    >>> str(Email().check('<EMAIL>')) # try with `idna` encoding
    'someone@xn--e1afmkfd.xn--p1ai'
    >>> (Email() >> (lambda m: m.groupdict()['domain'])).check('<EMAIL>')
    'example.net'
    >>> extract_error(Email(),'foo')
    'value is not a valid email address'
    """

    regex = re.compile(
        r"(?P<name>^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
        r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
        r')@(?P<domain>(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)$)'  # domain
        r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE)  # literal form, ipv4 address (SMTP 4.1.3)
    min_length = None
    max_length = None

    def __init__(self, allow_blank=False):
        super(Email, self).__init__(allow_blank=allow_blank, regex=self.regex)

    def check_and_return(self, value):
        try:
            return super(Email, self).check_and_return(value)
        except DataError:
            if value and isinstance(value, bytes):
                decoded = value.decode('utf-8')
            else:
                decoded = value
            # Trivial case failed. Try for possible IDN domain-part
            if decoded and '@' in decoded:
                parts = decoded.split('@')
                try:
                    parts[-1] = parts[-1].encode('idna').decode('ascii')
                except UnicodeError:
                    pass
                else:
                    try:
                        return super(Email, self).check_and_return('@'.join(parts))
                    except DataError:
                        # Will fail with main error
                        pass
        self._failure('value is not a valid email address')

    def __repr__(self):
{'fraction_non_alphanumeric': 0.15017462165308498, 'fraction_numerical': 0.014813736903376019, 'mean_word_length': 2.230024440684339, 'pattern_counts': {'":': 0, '<': 129, '<?xml version=': 0, '>': 637, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6234081', 'n_tokens_mistral': 11579, 'n_tokens_neox': 11085, 'n_words': 2840}