PY2 = sys.version_info[0] == 2 class Compiler_5_0(object):
    pass class DynamicDocument(with_metaclass(DynamicDocumentMeta, Document)):
    def _process_source_key_value(self, key, value):
        key, value = (
            super(DynamicDocument, self)._process_source_key_value(key, value)
        )
        if isinstance(value, dict):
            return key, DynamicDocument(**value)
        return key, value class Type(object):
    python_type = None

    def __init__(self):
        self.sub_type = None
        self.doc_cls = None

    def to_python(self, value):
        if value is None:
            return None
        if self.python_type is not None:
            return self.python_type(value)
        return value

    def to_python_single(self, value):
        return self.to_python(value)

    def from_python(self, value, compiler, validate=False):
        return value class String(Type):
    __visit_name__ = 'string'

    python_type = text_type

    def from_python(self, value, compiler, validate=False):
        return text_type(value) class Byte(_Int):
    __visit_name__ = 'byte'

    MIN_VALUE = -(1 << 7)
    MAX_VALUE = (1 << 7) - 1 class Short(_Int):
    __visit_name__ = 'short'

    MIN_VALUE = -(1 << 15)
    MAX_VALUE = (1 << 15) - 1 class Integer(_Int):
    __visit_name__ = 'integer'

    MIN_VALUE = -(1 << 31)
    MAX_VALUE = (1 << 31) - 1 class Long(_Int):
    __visit_name__ = 'long'

    MIN_VALUE = -(1 << 63)
    MAX_VALUE = (1 << 63) - 1 class Float(_Float):
    __visit_name__ = 'float' class Double(_Float):
    __visit_name__ = 'double' class Date(Type):
    __visit_name__ = 'date'

    python_type = datetime.datetime

    # def __init__(self, format=None):
    #     self.format = format

    def to_python(self, value):
        if value is None:
            return None
        return dateutil.parser.parse(value)

    def from_python(self, value, compiler, validate=True):
        if validate:
            if not isinstance(value, datetime.datetime):
                raise ValidationError('Value must be datetime.datetime object')
        return value class Boolean(Type):
    __visit_name__ = 'boolean'

    python_type = bool

    def to_python(self, value):
        if value is None:
            return None
        if value is False or value == 0 or value in ('', 'false', 'F'):
            return False
        return True

    def from_python(self, value, compiler, validate=True):
        return bool(value) class Binary(Type):
    __visit_name__ = 'binary'

    python_type = binary_type

    def to_python(self, value):
        if value is None:
            return None
        return base64.b64decode(value)

    def from_python(self, value, compiler, validate=False):
        try:
            return base64.b64encode(value).decode()
        except (ValueError, TypeError):
            if validate:
                raise ValidationError(
                    'Cannot decode value from base64: {!r}'.format(value)
                )
            else:
                raise class Ip(Type):
    __visit_name__ = 'ip'

    IPV4_REGEXP = re.compile(
        r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
        r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
    )

    python_type = text_type

    def from_python(self, value, compiler, validate=False):
        if validate:
            try:
                if not self.IPV4_REGEXP.match(value):
                    raise ValidationError(
                        'Not valid IPv4 address: {}'.format(value))
            except (TypeError, ValueError) as e:
                raise ValidationError(*e.args)
        return value class Object(Type):
    __visit_name__ = 'object'

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

    @property
    def python_type(self):
        return self.doc_cls

    def to_python(self, value):
        if value is None:
            return None
        if isinstance(value, self.doc_cls):
            return value
        return self.doc_cls(_hit={'_source': value})

    def from_python(self, value, compiler, validate=False):
        if isinstance(value, self.doc_cls):
            return value.to_source(compiler, validate=validate)
        return value class List(Type):
    python_type = list

    def __init__(self, sub_type):
        self.sub_type = instantiate(sub_type)

    @property
    def __visit_name__(self):
        return self.sub_type.__visit_name__

    @property
    def doc_cls(self):
        return self.sub_type.doc_cls

    def to_python(self, value):
        if value is None:
            return None
        if not isinstance(value, self.python_type):
            value = [value]
        return [self.sub_type.to_python(v) for v in value]

    def to_python_single(self, value):
        v = self.to_python(value)
        if v:
            return v[0]

    def from_python(self, value, compiler, validate=False):
        if not isinstance(value, self.python_type):
            value = [value]
        return [
            self.sub_type.from_python(v, compiler, validate=validate)
            for v in value
        ] class GeoPoint(Type):
    __visit_name__ = 'geo_point'

    LAT_LON_SEPARATOR = ','

    python_type = dict

    def to_python(self, value):
        if value is None:
            return None
        if isinstance(value, (list, tuple)):
            value = list(reversed(value))
        if isinstance(value, string_types):
            if self.LAT_LON_SEPARATOR in value:
                value = list(value.split(self.LAT_LON_SEPARATOR))
            elif GEOHASH_IMPORTED:
                value = list(geohash.decode(value))
        elif isinstance(value, dict):
            value = [value.get('lat'), value.get('lon')]
        return {'lat': float(value[0]), 'lon': float(value[1])}

    def from_python(self, value, compiler, validate=False):
        if validate:
            if not isinstance(value, dict):
                raise ValidationError(
                    'Value must be dictionary: {!r}'.format(value))
            if len(value) != 2 or 'lat' not in value or 'lon' not in value:
                raise ValidationError(
                    "Only 'lat' and 'lon' keys must present in the dictionary"
                )
            try:
                value = {'lat': float(value['lat']),
                         'lon': float(value['lon'])}
            except (ValueError, TypeError):
                raise ValidationError('Lat/lon must be floats: {!r}', value)
        return value class Completion(Type):
    __visit_name__ = 'completion'

    python_type = dict

    def to_python(self, value):
        return value

    def from_python(self, value, compiler, validate=False):
        if validate:
            if isinstance(value, str):
                return value

            if not isinstance(value, dict):
                raise ValidationError(
                    'Value must be dictionary: {!r}'.format(value))
            if not ('input' in value and value['input']):
                raise ValidationError(
                    "'input' key must be present: {!r}".format(value))
            if not isinstance(value['input'], (str, list, tuple)):
                raise ValidationError(
                    "'input' must be either str or list/tuple: {!r}"
                    .format(value))
            if 'output' in value and not isinstance(value['output'], str):
                raise ValidationError(
                    "'output' must be str: {!r}".format(value))
            if 'payload' in value and not isinstance(value['payload'], dict):
                raise ValidationError(
                    "'payload' must be dict or absent: {!r}".format(value))
            if 'weight' in value:
                if isinstance(value['weight'], int):
                    if value['weight'] < 0:
                        raise ValidationError(
                            "'weight' must be greater equal 0: {!r}"
                            .format(value['weight']))
                elif isinstance(value['weight'], str):
                    if not value['weight'].isdigit():
                        raise ValidationError(
                            "'weight' must represent positive integer: {!r}"
                            .format(value['weight']))
                else:
                    raise ValidationError(
                        "'weight' must positive integer, or string"
                        " representing positive integer or absent: {!r}"
                        .format(value))
            # we copy value to be sure it won't be chaged in future.
            value = copy.deepcopy(value)
        return value class ValidationError(ValueError):
    pass
import binascii
import datetime
import unittest
import pytest
from elasticmagic.compat import PY2
from elasticmagic.compiler import Compiler_5_0
from elasticmagic.document import DynamicDocument
from elasticmagic.types import (
    Type, String, Byte, Short, Integer, Long, Float, Double, Date, Boolean,
    Binary, Ip, Object, List, GeoPoint, Completion, ValidationError,
)
    assert t.to_python(123) == 123
    assert t.to_python('123') == 123
    assert t.from_python('test', Compiler_5_0) == 'test'
    with pytest.raises(ValidationError):
        t.from_python('test', Compiler_5_0, validate=True)
    with pytest.raises(ValidationError):
        t.from_python(1 << 31, Compiler_5_0, validate=True)


def test_float():
    t = Float()
    assert t.to_python(None) is None
    with pytest.raises(ValueError):
        t.to_python('test')
    assert t.to_python(123) == 123
    assert t.to_python('123') == 123
    assert t.from_python('test', Compiler_5_0) == 'test'
    with pytest.raises(ValidationError):
        t.from_python('test', Compiler_5_0, validate=True)
    assert t.from_python('128', Compiler_5_0, validate=True) == \
        pytest.approx(128.0)
    assert t.from_python(128, Compiler_5_0, validate=True) == \
        pytest.approx(128.0)


def test_double():
    t = Double()
    assert t.to_python(None) is None
    with pytest.raises(ValueError):
        t.to_python('test')
    assert t.to_python(123) == 123
    assert t.to_python('123') == 123
    assert t.from_python('test', Compiler_5_0) == 'test'
    with pytest.raises(ValidationError):
        t.from_python('test', Compiler_5_0, validate=True)
    assert t.from_python('128', Compiler_5_0, validate=True) == \
        pytest.approx(128.0)
    assert t.from_python(128, Compiler_5_0, validate=True) == \
        pytest.approx(128.0)


def test_date():
    t = Date()
    assert t.to_python(None) is None
    assert t.to_python('2009-11-15T14:12:12') == \
        datetime.datetime(2009, 11, 15, 14, 12, 12)
    with pytest.raises(ValueError):
        t.to_python('test')
    with pytest.raises(ValueError):
        t.from_python('test', Compiler_5_0)
    with pytest.raises(ValidationError):
        t.from_python('test', Compiler_5_0, validate=True)
    assert \
        t.from_python(
            datetime.datetime(2009, 11, 15, 14, 12, 12),
            Compiler_5_0
        ) == \
        datetime.datetime(2009, 11, 15, 14, 12, 12)


def test_boolean():
    t = Boolean()
    assert t.to_python(None) is None
    assert t.to_python(False) is False
    assert t.to_python(True) is True
    assert t.to_python(0) is False
    assert t.to_python(1) is True
    assert t.to_python('false') is False
    assert t.to_python('F') is False
    assert t.to_python('') is False
    assert t.to_python('true') is True
    assert t.to_python('T') is True
    assert t.from_python(False, Compiler_5_0) is False
    assert t.from_python(True, Compiler_5_0) is True
    assert t.from_python(0, Compiler_5_0) is False
    assert t.from_python(1, Compiler_5_0) is True
    assert t.from_python('true', Compiler_5_0) is True
    assert t.from_python('false', Compiler_5_0) is True


def test_binary():
    t = Binary()
    assert t.to_python(None) is None
    assert t.to_python('dGVzdA==') == b'test'
    if PY2:
        with pytest.raises(TypeError):
            t.to_python('dGVzdA=')
    else:
        with pytest.raises(binascii.Error):
            t.to_python('dGVzdA=')
    assert t.from_python(b'test', Compiler_5_0) == 'dGVzdA=='
    with pytest.raises(TypeError):
        t.from_python(True, Compiler_5_0)
    with pytest.raises(ValidationError):
        t.from_python(True, Compiler_5_0, validate=True)


def test_ip():
    t = Ip()
    assert t.to_python(None) is None
    assert t.to_python('8.8.8.8') == '8.8.8.8'
    assert t.from_python('8.8.8.8', Compiler_5_0) == '8.8.8.8'
    assert t.from_python('8.8.8.', Compiler_5_0) == '8.8.8.'
    assert t.from_python(8888, Compiler_5_0) == 8888
    assert t.from_python('8.8.8.8', Compiler_5_0, validate=True) == '8.8.8.8'
    with pytest.raises(ValidationError):
        t.from_python('8.8.8.', Compiler_5_0, validate=True)
    with pytest.raises(ValidationError):
        t.from_python(8888, Compiler_5_0, validate=True)


def test_object():
    t = Object(DynamicDocument)
    assert t.to_python(None) is None
    doc = t.to_python({'name': 'Test', 'status': 1})
    assert doc.name == 'Test'
    assert doc.status == 1


def test_list():

    t = List(Integer)