from datetime import datetime from typing import Any, Dict, List, Optional, Sequence, Tuple from pymongo import MongoClient from ..views import Filter, SortBy, Trace from .tracing_database_driver import TracingDatabaseDriver operator_mapping = { "=": "$eq", "!=": "$ne", "<": "$lt", "<=": "$lte", ">": "$gt", ">=": "$gte", "contains": "$regex", } class MongodbDriver(TracingDatabaseDriver): is_production_ready = True mongo_connection_string: str mongo_database: str def __init__(self) -> None: super().__init__() if self.mongo_connection_string is None or self.mongo_database is None: raise ValueError( "Please configure the MongoDB access options by calling MongodbDriver.configure_credentials" ) @classmethod def configure_credentials( # type: ignore cls, *, mongo_connection_string: str, mongo_database: str, **_: Any, ) -> None: cls.mongo_connection_string = mongo_connection_string cls.mongo_database = mongo_database super().configure_credentials() def save(self, trace: Trace) -> str: serialized = trace.to_flat_dict() serialized["_id"] = trace.trace_id with MongoClient[Any](self.mongo_connection_string) as client: return client[self.mongo_database].traces.insert_one(serialized).inserted_id def save_batch(self, documents: List[Trace]) -> List[str]: serialized = [d.to_flat_dict() for d in documents] for s in serialized: s["_id"] = s["trace_id"] with MongoClient[Any](self.mongo_connection_string) as client: return ( client[self.mongo_database] .traces.insert_many(serialized, ordered=False) .inserted_ids ) def get(self, id: str) -> Optional[Trace]: with MongoClient[Any](self.mongo_connection_string) as client: value = client[self.mongo_database].traces.find_one(id) if value: value = Trace.parse_obj(value) return value def _get_operator(self, filter: Filter) -> str: if filter.operator == "contains" and not isinstance(filter.value, str): return operator_mapping["="] return operator_mapping[filter.operator] def query( self, *, skip: int = 0, take: Optional[int] = None, conjunctive_filters: Sequence[Filter] = [], conjunctive_tags: Sequence[str] = [], since: Optional[datetime] = None, until: Optional[datetime] = None, has_feedback: Optional[bool] = None, sort_by: Sequence[SortBy] = [], ) -> Tuple[List[Trace], int]: query: Dict[str, Any] = { "filter": {}, } and_query: List[Dict[str, Any]] = [{}] and_query.extend({"tags": tag} for tag in conjunctive_tags) and_query.extend( {f.property: {self._get_operator(f): f.value}} for f in conjunctive_filters ) if since: and_query.append({"created": {"$gte": since}}) if until: and_query.append({"created": {"$lte": until}}) if has_feedback is not None: and_query.append( {"feedback": {"$ne": None}} if has_feedback else {"feedback": None} ) query["filter"]["$and"] = and_query with MongoClient[Any](self.mongo_connection_string) as client: count = client[self.mongo_database].traces.count_documents(**query) if skip: query["skip"] = skip if take: query["limit"] = take query["sort"] = [ (col.column_id, 1 if col.direction == "asc" else -1) for col in sort_by ] with client[self.mongo_database].traces.find(**query) as cursor: documents = [Trace[Any].parse_obj(t) for t in cursor] return documents, count def update(self, id: str, new_version: Trace) -> None: serialized = new_version.to_flat_dict() serialized["_id"] = new_version.trace_id with MongoClient[Any](self.mongo_connection_string) as client: client[self.mongo_database].traces.update_one({"_id": id}, serialized) def delete(self, id: str) -> None: with MongoClient[Any](self.mongo_connection_string) as client: client[self.mongo_database].traces.delete_one({"_id": id}) def delete_batch(self, ids: List[str]) -> None: delete_filter = {"_id": {"$in": ids}} with MongoClient[Any](self.mongo_connection_string) as client: client[self.mongo_database].traces.delete_many(delete_filter)