class AttributedField(Expression, FieldOperators):
    __visit_name__ = 'attributed_field'

    def __init__(self, parent, attr_name, field):
        self._parent = parent
        self._attr_name = attr_name
        self._field = field

        self._dynamic_fields = OrderedAttributes()

        if self._field._type.doc_cls:
            doc_cls = self._field._type.doc_cls
            dynamic_defaults = {}
            for dyn_field_name, dyn_attr_field in \
                    doc_cls.dynamic_fields.items():
                dyn_field = dyn_attr_field._field.clone()
                dyn_field._name = self._make_field_name(dyn_field._name)
                self._dynamic_fields[dyn_field_name] = AttributedField(
                    self._parent, dyn_field_name, dyn_field
                )
                default = _attributed_field_factory(
                    AttributedField, self._parent, dyn_field,
                    self._make_field_name
                )
                dynamic_defaults[dyn_field_name] = default

            self._sub_fields = OrderedAttributes(defaults=dynamic_defaults)
            for attr_name, attr_field in doc_cls.user_fields.items():
                field = attr_field._field.clone()
                field._name = self._make_field_name(field._name)
                self._sub_fields[attr_name] = AttributedField(
                    self._parent, attr_name, field
                )
        elif self._field._fields:
            self._sub_fields = OrderedAttributes()
            for field_attr, field in self._field._fields.items():
                field = field.clone()
                field._name = self._make_field_name(field_attr)
                self._sub_fields[field_attr] = AttributedField(
                    self, field_attr, field
                )
        else:
            self._sub_fields = OrderedAttributes()

    def _make_field_name(self, name):
        return '{}.{}'.format(self._field._name, name)

    @property
    def fields(self):
        return self._sub_fields

    @property
    def dynamic_fields(self):
        return self._dynamic_fields

    def __getattr__(self, name):
        return getattr(self.fields, name)

    def wildcard(self, name):
        return DynamicAttributedField(
            self._parent, name, Field(self._make_field_name(name))
        )

    def get_parent(self):
        return self._parent

    def get_attr_name(self):
        return self._attr_name

    def get_field(self):
        return self._field

    def get_field_name(self):
        return self._field._name

    def get_type(self):
        return self._field.get_type()

    def __get__(self, obj, type=None):
        if obj is None:
            return self

        obj.__dict__[self._attr_name] = None
        return None

    def _collect_doc_classes(self):
        if isinstance(self._parent, AttributedField):
            return self._parent._collect_doc_classes()
        return {self._parent}

    def _to_python(self, value):
        return self._field._to_python(value)

    def _from_python(self, value):
        return self._field._from_python(value) class DynamicAttributedField(AttributedField):
    def __getattr__(self, name):
        return getattr(
            self.fields,
            name,
            DynamicAttributedField(
                self._parent, name, Field(self._make_field_name(name))
            )
        ) BOOL_OPERATOR_NAMES = {
    operator.and_: 'and',
    operator.or_: 'or',
}
BOOL_OPERATORS_MAP = {
    operator.and_: Bool.must,
    operator.or_: Bool.should,
}
DEFAULT_DOC_TYPE = '_doc'
    META_FIELDS = ('_id', '_type', '_routing', '_parent', '_version')
    META_SPECIAL_PARAMS = {'_id', '_index', '_type'}
    META_FIELD_NAMES = (
        '_id',
        '_index',
        '_type',
        'routing',
        'parent',
        'timestamp',
        'ttl',
        'version',
    )
class CompilationError(Exception):
class MultiSearchError(ElasticsearchException):
class Compiled(object):
class CompiledEndpoint(Compiled):
class CompiledExpression(Compiled):
class CompiledSearchQuery(CompiledExpression, CompiledEndpoint):
class CompiledExplain(CompiledSearchQuery):
class CompiledScroll(CompiledEndpoint):
class CompiledScalarQuery(CompiledSearchQuery):
class CompiledCountQuery(CompiledScalarQuery):
class CompiledExistsQuery(CompiledScalarQuery):
class CompiledDeleteByQuery(CompiledScalarQuery):
class CompiledMultiSearch(CompiledEndpoint):
    class _MultiQueries(object):
class CompiledPutMapping(CompiledEndpoint):
    class _MultipleMappings(object):
class CompiledCreateIndex(CompiledEndpoint):
    class _CreateIndex(object):
class CompiledGet(CompiledEndpoint):
class CompiledMultiGet(CompiledEndpoint):
    class _DocsOrIds(object):
class CompiledDelete(CompiledGet):
class CompiledBulk(CompiledEndpoint):
    class _Actions(object):
class CompiledMeta(Compiled):
class CompiledSource(CompiledExpression):
        class _CompiledExpression(CompiledExpression):
        class _CompiledSearchQuery(CompiledSearchQuery):
        class _CompiledScroll(CompiledScroll):
        class _CompiledCountQuery(CompiledCountQuery):
        class _CompiledExistsQuery(CompiledExistsQuery):
        class _CompiledExplain(CompiledExplain):
        class _CompiledDeleteByQuery(CompiledDeleteByQuery):
        class _CompiledMultiSearch(CompiledMultiSearch):
        class _CompiledGet(CompiledGet):
        class _CompiledMultiGet(CompiledMultiGet):
        class _CompiledDelete(CompiledDelete):
        class _CompiledMeta(CompiledMeta):
        class _CompiledSource(CompiledSource):
        class _CompiledBulk(CompiledBulk):
        class _CompiledPutMapping(CompiledPutMapping):
        class _CompiledCreateIndex(CompiledCreateIndex):
class Compiler_1_0(object):
class Compiler_2_0(object):
class Compiler_5_0(object):
class Compiler_5_6(object):
class Compiler_6_0(object):
class Compiler_7_0(object):
def _is_emulate_doc_types_mode(features, doc_cls):
def _add_doc_type_fields_into_stored_fields(
        stored_fields, add_source
):
def _patch_stored_fields_in_params(features, params, add_source_field):
def _has_custom_source(params):
def _mk_doc_type(doc_types):
def _mk_doc_cls_map(doc_classes, supports_doc_type):
    def __init__(self, expression, params=None):
    def prepare_params(self, params):
    def visit(self, expr, **kwargs):
    def visit_params(self, params):
    def visit_dict(self, dct):
    def visit_list(self, lst):
    def visit_script(self, script):
    def process_result(self, raw_result):
    def __init__(self, expr, params=None, doc_classes=None):
    def visit_literal(self, expr):
    def visit_field(self, field):
    def visit_mapping_field(self, field):
    def visit_attributed_field(self, field):
    def visit_boost_expression(self, expr):
    def visit_query_expression(self, expr):
    def visit_field_query(self, expr, **kwargs):
    def visit_match(self, expr):
    def visit_range(self, expr):
    def _get_field_doc_cls(field):
    def visit_term(self, term):
    def visit_terms(self, expr):
    def visit_missing(self, expr):
    def visit_multi_match(self, expr):
    def visit_match_all(self, expr):
    def visit_query(self, expr):
    def visit_boolean_expression(self, expr):
    def visit_not(self, expr):
    def visit_sort(self, expr):
    def visit_sort_script(self, sort_script):
    def visit_agg(self, agg):
    def visit_bucket_agg(self, agg):
    def visit_filter_agg(self, agg):
    def visit_top_hits_agg(self, agg):
    def visit_source(self, expr):
    def visit_query_rescorer(self, rescorer):
    def visit_rescore(self, rescore):
    def visit_highlighted_field(self, hf):
    def visit_highlight(self, highlight):
    def visit_ids(self, expr):
    def visit_parent_id(self, expr):
    def visit_has_parent(self, expr):
    def visit_has_child(self, expr):
    def visit_function(self, func):
    def visit_weight_function(self, func):
    def visit_decay_function(self, func):
    def _patch_stored_fields(self, params, doc_classes):
    def __init__(self, query, params=None):
    def api_method(self, client):
    def prepare_params(self, params):
    def process_result(self, raw_result):
    def get_query(cls, query_context, wrap_function_score=True):
    def get_filtered_query(
            cls, query_context, wrap_function_score=True, doc_classes=None
    ):
    def get_post_filter(cls, query_context):
    def visit_search_query_context(self, query_ctx):
    def _patch_doc_type(self, search_params):
    def __init__(self, query, doc_or_id, params=None, doc_cls=None):
    def api_method(self, client):
    def prepare_params(self, params):
    def process_result(self, raw_result):
    def visit_search_query_context(self, query_ctx):
    def __init__(self, params, doc_cls=None, instance_mapper=None):
    def api_method(self, client):
    def process_result(self, raw_result):
    def visit_search_query_context(self, query_ctx):
    def api_method(self, client):
    def process_result(self, raw_result):
    def __init__(self, query, params=None):
    def api_method(self, client):
    def process_result(self, raw_result):
    def api_method(self, client):
    def process_result(self, raw_result):
        def __init__(self, queries):
        def __iter__(self):
    def __init__(self, queries, params=None, raise_on_error=False):
    def api_method(self, client):
    def visit_multi_queries(self, expr):
    def process_result(self, raw_result):
        def __init__(self, mappings):
    def __init__(self, doc_cls_or_mapping, params=None, ordered=False):
    def api_method(self, client):
    def prepare_params(self, params):
    def process_result(self, raw_result):
    def _visit_dynamic_field(self, field):
    def visit_field(self, field):
    def visit_mapping_field(self, field):
    def visit_attributed_field(self, field):
    def visit_ordered_attributes(self, attrs):
    def _get_parent_doc_type(doc_cls):
    def _merge_properties(mappings, properties):
    def visit_multiple_mappings(self, multiple_mappings):
    def visit_document(self, doc_cls):
        def __init__(self, settings, mappings):
    def __init__(self, settings=None, mappings=None, params=None):
    def api_method(self, client):
    def process_result(self, raw_result):
    def visit_create_index(self, create_index):
    def __init__(self, doc_or_id, params=None, doc_cls=None):
    def api_method(self, client):
    def prepare_params(self, params):
    def _patch_source_include_exclude(self, get_params):
    def process_result(self, raw_result):
        def __init__(self, docs_or_ids):
        def __iter__(self):
    def __init__(self, docs_or_ids, params=None, doc_cls=None):
    def api_method(self, client):
    def visit_docs_or_ids(self, docs_or_ids):
    def process_result(self, raw_result):
    def api_method(self, client):
    def process_result(self, raw_result):
        def __init__(self, actions):
        def __iter__(self):
    def __init__(self, actions, params=None):
    def api_method(self, client):
    def visit_actions(self, actions):
    def process_result(self, raw_result):
    def __init__(self, doc_or_action):
    def visit_action(self, action):
    def visit_document(self, doc):
    def _populate_meta_from_document(self, doc, meta):
    def _populate_meta_from_dict(self, doc, meta):
    def __init__(self, doc_or_action, validate=False):
    def visit_action(self, action):
    def visit_document(self, doc):
def _featured_compiler(elasticsearch_features):
    def inject_features(cls):
def get_compiler_by_es_version(es_version): class Document(with_metaclass(DocumentMeta)):
    __visit_name__ = 'document'

    _uid = MappingField(String)
    _id = MappingField(String)
    _type = MappingField(String)
    _source = MappingField(String)
    _all = MappingField(String)
    _analyzer = MappingField(String)
    _boost = MappingField(String)
    _parent = MappingField(String)
    _field_names = MappingField(String)
    _routing = MappingField(String)
    _index = MappingField(String)
    _size = MappingField(Integer)
    _timestamp = MappingField(Date)
    _ttl = MappingField(String)
    _version = MappingField(Integer)
    _score = MappingField(Float)

    __dynamic_fields__ = []

    __mapping_options__ = {}

    def __init__(self, _hit=None, _result=None, **kwargs):
        self.__hit_fields = None
        self.__highlight = None
        self.__matched_queries = None
        self.__explanation = None
        self._index = self._type = self._id = self._score = None
        if _hit:
            self._score = _hit.get('_score')
            source = _hit.get('_source')
            fields = _hit.get('fields')
            custom_doc_type = fields.get(DOC_TYPE_NAME_FIELD) \
                if fields else None

            for attr_field in self._mapping_fields:
                setattr(
                    self,
                    attr_field._attr_name,
                    _hit.get(attr_field._field._name)
                )

            if custom_doc_type:
                doc_type = custom_doc_type[0]
                _, _, self._id = _hit['_id'].rpartition(DOC_TYPE_ID_DELIMITER)
                self._type = doc_type

                custom_parent_id = fields.get(DOC_TYPE_PARENT_FIELD)
                if custom_parent_id:
                    parent_id = custom_parent_id[0]
                    _, _, self._parent = parent_id.rpartition(
                        DOC_TYPE_ID_DELIMITER
                    )

            if source:
                for hit_key, hit_value in source.items():
                    setattr(
                        self,
                        *self._process_source_key_value(hit_key, hit_value)
                    )

            if fields:
                # we cannot construct document from fields
                # in next example we cannot decide
                # which tag has name and which has not:
                # {"tags.id": [1, 2], "tags.name": ["Test"]}
                self.__hit_fields = self._process_fields(fields)

            if _hit.get('highlight'):
                self.__highlight = _hit['highlight']

            if _hit.get('matched_queries'):
                self.__matched_queries = _hit['matched_queries']

            if _hit.get('_explanation'):
                self.__explanation = _hit['_explanation']

        for fkey, fvalue in kwargs.items():
            setattr(self, fkey, fvalue)

        self.__result = _result

    def _process_source_key_value(self, key, value):
        if key in self._field_name_map:
            attr_field = self._field_name_map[key]
            return (
                attr_field._attr_name,
                attr_field.get_type().to_python(value)
            )
        return key, value

    def _process_fields(self, hit_fields):
        processed_fields = {}
        for field_name, field_values in hit_fields.items():
            field_path = field_name.split('.')
            doc_cls = self.__class__
            field_type = None
            for fname in field_path:
                attr_field = doc_cls._field_name_map.get(fname)
                if not attr_field:
                    break
                field_type = attr_field.get_field().get_type()
                doc_cls = field_type.doc_cls
            if field_type:
                processed_values = list(
                    map(field_type.to_python, field_values)
                )
            else:
                processed_values = field_values
            processed_fields[field_name] = processed_values
        return processed_fields

    @classmethod
    def get_doc_type(cls):
        return getattr(cls, '__doc_type__', None)

    @classmethod
    def has_parent_doc_cls(cls):
        return hasattr(cls, '__parent__')

    @classmethod
    def get_parent_doc_cls(cls):
        return getattr(cls, '__parent__', None)

    def to_meta(self, compiler):
        meta_compiler = compiler.compiled_bulk.compiled_meta
        return meta_compiler(self).body

    def to_source(self, compiler, validate=False):
        source_compiler = compiler.compiled_bulk.compiled_source
        return source_compiler(self, validate=validate).body

    def get_highlight(self):
        return self.__highlight or {}

    def get_matched_queries(self):
        return self.__matched_queries or []

    def get_fields(self):
        return self.__hit_fields or {}

    def get_hit_fields(self):
        return self.get_fields()

    def get_explanation(self):
        return self.__explanation or {}

    @classmethod
    def to_mapping(cls, compiler, ordered=False):
        return compiler.compiled_put_mapping(cls, ordered=ordered).body

    @cached_property
    def instance(self):
        if self.__result:
            self.__result._populate_instances(self.__class__)
            return self.__dict__['instance'] 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 Field(Expression, FieldOperators):
    __visit_name__ = 'field'

    _counter = count()

    def __init__(self, *args, **kwargs):
        self._name = None
        self._type = None
        if len(args) == 1:
            if isinstance(args[0], string_types):
                self._name = args[0]
            elif (
                    isinstance(args[0], Type) or (
                        inspect.isclass(args[0]) and issubclass(args[0], Type)
                    )
            ):
                self._type = args[0]
            else:
                raise TypeError(
                    'Argument must be string or field type: {} found'.format(
                        args[0].__class__.__name__))
        elif len(args) == 2:
            self._name, self._type = args
        elif len(args) >= 3:
            raise TypeError('Too many positional arguments: %s' % len(args))

        if self._type is None:
            self._type = Type()
        self._type = instantiate(self._type)

        self._fields = kwargs.pop('fields', {})
        self._count = kwargs.pop('_counter', next(self._counter))
        self._mapping_options = kwargs

    def clone(self, cls=None):
        cls = cls or self.__class__
        assert issubclass(cls, Field)
        return cls(
            self._name, self._type, fields=self._fields,
            **self._mapping_options
        )

    def get_name(self):
        return self._name

    def get_type(self):
        return self._type

    def get_mapping_options(self):
        return self._mapping_options

    def to_mapping(self, compiler):
        return compiler.compiled_put_mapping(self).body class MultiMatch(QueryExpression):
    __visit_name__ = 'multi_match'

    def __init__(
            self, query, fields,
            type=None, analyzer=None, boost=None, operator=None,
            minimum_should_match=None, fuzziness=None, prefix_length=None,
            max_expansions=None, rewrite=None, zero_terms_query=None,
            cutoff_frequency=None, tie_breaker=None, **kwargs
    ):
        super(MultiMatch, self).__init__(
            type=type, analyzer=analyzer, boost=boost, operator=operator,
            minimum_should_match=minimum_should_match, fuzziness=fuzziness,
            prefix_length=prefix_length, max_expansions=max_expansions,
            rewrite=rewrite, zero_terms_query=zero_terms_query,
            cutoff_frequency=cutoff_frequency, tie_breaker=tie_breaker,
            **kwargs
        )
        self.query = query
        self.fields = fields

    def _collect_doc_classes(self):
        return set().union(
            super(MultiMatch, self)._collect_doc_classes(),
            collect_doc_classes(self.fields),
        ) def collect_doc_classes(expr):
    if hasattr(expr, '_collect_doc_classes'):
        return expr._collect_doc_classes()

    if isinstance(expr, dict):
        return set().union(
            *[collect_doc_classes(e)
              for e in chain(expr.keys(), expr.values())]
        )

    if isinstance(expr, (list, tuple)):
        return set().union(*[collect_doc_classes(e) for e in expr])

    return set() 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 Integer(_Int):
    __visit_name__ = 'integer'

    MIN_VALUE = -(1 << 31)
    MAX_VALUE = (1 << 31) - 1 class Float(_Float):
    __visit_name__ = 'float' 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 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 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 Percolator(Type):
    __visit_name__ = 'percolator'

    python_type = dict

    def from_python(self, value, compiler, validate=False):
        if hasattr(value, 'to_elastic'):
            value = value.to_elastic(compiler)
        if validate:
            if not isinstance(value, dict):
                raise ValidationError(
                    'Value must be dictionary or expression: {!r}'
                    .format(value)
                )
        return value class ValidationError(ValueError):
    pass def assert_expression(expr, expected, compiler):  # noqa: F811
    assert expr.to_dict(compiler=compiler) == expected def assert_same_elements(seq1, seq2):
    it1 = iter(seq1)
    it2 = iter(seq2)
    i = 0
    while True:
        try:
            e1 = next(it1)
        except StopIteration:
            try:
                e2 = next(it2)
            except StopIteration:
                break
            else:
                raise AssertionError(
                    'seq2 has more elements than seq1: {!r}'.format(e2)
                )
        try:
            e2 = next(it2)
        except StopIteration:
            raise AssertionError(
                'seq1 has more elements than seq2: {!r}'.format(e1)
            )
        assert e1 is e2, '{} element is not the same'.format(i)
        i += 1
import datetime
import dateutil
import pytest
from elasticmagic.attribute import AttributedField, DynamicAttributedField
from elasticmagic.compiler import all_compilers
from elasticmagic.document import Document, DynamicDocument
from elasticmagic.expression import Field, MultiMatch
from elasticmagic.util import collect_doc_classes
from elasticmagic.types import (
    Type, String, Integer, Float, Boolean,
    Date, Object, List, GeoPoint, Completion, Percolator,
)
from elasticmagic.types import ValidationError
from .conftest import assert_expression
from .conftest import assert_same_elements
        __dynamic_fields__ = [
            Field('group_id_level_*', Integer)
        ]

        __mapping_options__ = {
            'dynamic': True,
        }

    class ProductDocument(Document):
        __doc_type__ = 'product'

        __mapping_options__ = {
            'dynamic': False,
            'date_detection': False,
        }

        _routing = Field(required=True, path='company_id')
        _all = Field(enabled=False)

        name = Field(
            String,
            fields={
                'autocomplete': Field(
                    String,
                    analyzer='ngrams',
                    search_analyzer='text_delimit',
                    norms={'enabled': False}
                )
            }
        )
        company_id = Field(Integer)
        group = Field(Object(ProductGroupSubDocument))
        popularity = Field(Float, doc_values=True)

        __dynamic_fields__ = [
            Field('attr_*', Integer)
        ]

    # add field on the fly
    ProductDocument.tags = Field(List(String))

    expected_mapping = {
        "dynamic": False,
        "date_detection": False,
        "dynamic_templates": [
            {
                "group.group_id_level_*": {
                    "path_match": "group.group_id_level_*",
                    "mapping": {
                        "type": "integer"
                    }
                }
            },
            {
                "attr_*": {
                    "path_match": "attr_*",
                    "mapping": {
                        "type": "integer"
                    }
                }
            }
        ],
        "_routing": {
            "required": True,
            "path": "company_id"
        },
        "_all": {
            "enabled": False
        },
        "properties": {
            "name": {
                "type": "string",
                "fields": {
                    "autocomplete": {
                        "type": "string",
                        "analyzer": "ngrams",
                        "search_analyzer": "text_delimit",
                        "norms": {"enabled": False}
                    }
                }
            },
            "company_id": {
                "type": "integer"
            },
            "group": {
                "dynamic": True,
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string",
                        "norms": {
                            "enabled": False
                        }
                    }
                }
            },
            "popularity": {
                "type": "float",
                "doc_values": True
            },
            "tags": {
                "type": "string"
            }
        }
    }
    if compiler.features.supports_mapping_types:
        assert ProductDocument.to_mapping(compiler) == {
            "product": expected_mapping
        }
    else:
        assert ProductDocument.to_mapping(compiler) == expected_mapping


def test_document_class_with_geopoint_field__to_mapping(compiler):
    class GeoPointDoc(Document):
        __doc_type__ = 'geo_data'


        pin = Field(GeoPoint)