from abc import abstractmethod from typing import List, Type, Dict, Any import config import traceback from db import db from utils.elastic_search.elastic_helpers import add_model, remove_model, query_model, add_models from utils.elastic_search.query_strategy import QueryStrategyType from utils.list_utils import range_chunks class SearchableMixin(object): @classmethod def elastic_index(cls: Type[db.Model]) -> str: return cls.__tablename__.lower() @property @abstractmethod def __searchable__(self) -> Dict[str, Any]: ... @property @abstractmethod def id(self): ... @classmethod def es_query(cls: Type[db.Model]): raise NotImplementedError def es_payload(self): payload = {} keys_to_index = self.__searchable__.keys() for key in keys_to_index: result = getattr(self, key) if result is list: for index, result_item in enumerate(result): payload[f"{key}_{index}"] = result_item else: payload[key] = result return payload @classmethod def search( cls: Type[db.Model], expression: str, fields: List[db.Column] = None, page: int = None, per_page: int = None, strategy: QueryStrategyType = QueryStrategyType.MULTIMATCH_FUZZY_QUERY, ) -> List[int]: try: mapped_fields = list(map(lambda x: x.name, fields)) if fields is not None else None search_results = query_model( cls.elastic_index(), expression, mapped_fields, page, per_page, query_strategy=strategy ) if search_results.total == 0: return [] return search_results.ids except Exception as error: from app import app app.logger.error("Elastic Search Error: search ", f"{error}\n{traceback.format_exc()}") return [] @classmethod def index_model(cls, model): if config.runtime_config().TESTING: return try: add_model(model) except Exception as error: from app import app app.logger.error("Elastic Search Error: index ", f"{error}\n{traceback.format_exc()}") @classmethod def index_models(cls, models): if config.runtime_config().TESTING: return try: add_models(cls, models) except Exception as error: from app import app app.logger.error("Elastic Search Error: index ", f"{error}\n{traceback.format_exc()}") @classmethod def delete_index(cls, model): if config.runtime_config().TESTING: return try: remove_model(model) except Exception as error: from app import app app.logger.error("Elastic Search Error: delete index ", f"{error}\n{traceback.format_exc()}") @classmethod def reindex(cls): if config.runtime_config().TESTING: return from app import app objects_count = cls.es_query().count() app.logger.info(f"[ES] Found {objects_count} objects...") chunk_size = 100_000 for r in range_chunks(range(0, objects_count), chunk_size): objects = cls.es_query().order_by(cls.id).slice(r.start, r.stop).all() app.logger.info(f"[ES] Chunk with {len(objects)} objects adding...") try: add_models(cls, objects) except Exception as error: app.logger.error("Elastic Search Error: reindex ", f"{error}\n{traceback.format_exc()}")