| import logging |
| from typing import Dict, List, Union, Any |
| from bson import ObjectId |
| import ast |
| import json |
| import re |
| from pymongo import MongoClient |
| from threading import Lock |
| from urllib.parse import urlparse |
| from pymongo.uri_parser import parse_uri |
|
|
| logging.basicConfig( |
| format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", |
| level=logging.INFO |
| ) |
| logger = logging.getLogger("mongo_service") |
|
|
| |
| |
| |
| class MongoConnectionManager: |
| def __init__(self): |
| self._clients: Dict[str, MongoClient] = {} |
| self._lock = Lock() |
|
|
| def get_client(self, uri: str) -> MongoClient: |
| if uri not in self._clients: |
| with self._lock: |
| if uri not in self._clients: |
| logger.info(f"Initialize new MongoDB Client for URI: {uri[:20]}...") |
| self._clients[uri] = MongoClient( |
| uri, |
| serverSelectionTimeoutMS=5000, |
| connectTimeoutMS=5000, |
| maxPoolSize=50 |
| ) |
| return self._clients[uri] |
|
|
| mongo_manager = MongoConnectionManager() |
|
|
| |
| |
| |
| def convert_oid(obj): |
| """Recursively convert $oid or ObjectId to strings.""" |
| if isinstance(obj, ObjectId): return str(obj) |
| if isinstance(obj, dict): |
| if set(obj.keys()) == {"$oid"}: return str(obj["$oid"]) |
| return {k: convert_oid(v) for k, v in obj.items()} |
| elif isinstance(obj, list): |
| return [convert_oid(item) for item in obj] |
| return obj |
|
|
| def sanitize_json_input(query_input: Union[str, Dict, List]) -> Union[Dict, List]: |
| """Parses input string/dict/list into Python objects, handling ObjectIds.""" |
| if isinstance(query_input, (dict, list)): return query_input |
|
|
| query_str = str(query_input).strip() |
| match = re.search(r"```(?:json|javascript)?\s*(.*?)\s*```", query_str, re.DOTALL) |
| if match: query_str = match.group(1).strip() |
|
|
| |
| query_str = re.sub(r"ObjectId\(['\"]([a-fA-F0-9]+)['\"]\)", r"'\1'", query_str) |
| query_str = re.sub(r"ISODate\(['\"](.*?)['\"]\)", r"'\1'", query_str) |
| |
| try: |
| return json.loads(query_str) |
| except: |
| try: |
| return ast.literal_eval(query_str) |
| except Exception as e: |
| logger.error(f"Failed to parse query: {e}") |
| raise ValueError(f"Parsing error: {str(e)}") |
|
|
| |
| |
| |
| def execute_mongo_operation( |
| mongo_uri: str, |
| db_name: str, |
| collection_name: str, |
| query: Union[Dict, List], |
| limited: bool = False, |
| limit_rows: int = 20 |
| ): |
| client = mongo_manager.get_client(mongo_uri) |
| |
| try: |
| db = client[db_name] |
| collection = db[collection_name] |
| results = [] |
| |
| |
| if isinstance(query, list): |
| pipeline = query |
| if limited: |
| if not (pipeline and "$limit" in pipeline[-1]): |
| pipeline.append({"$limit": limit_rows}) |
| cursor = collection.aggregate(pipeline, allowDiskUse=True) |
| results = list(cursor) |
| |
| |
| elif isinstance(query, dict): |
| cursor = collection.find(query) |
| if limited: cursor = cursor.limit(limit_rows) |
| results = list(cursor) |
| |
| else: |
| raise ValueError("Query must be a Pipeline (List) or Filter (Dict)") |
|
|
| return results |
| except Exception as e: |
| logger.error(f"DB Execution Error: {e}") |
| raise e |
| |
| def extract_database_name(uri: str) -> str: |
| """ |
| Robustly extracts database name from a MongoDB URI. |
| 1. Tries PyMongo's strict parser. |
| 2. Falls back to standard URL path extraction (like your TS code). |
| """ |
| if not uri: |
| return None |
|
|
| |
| try: |
| parsed = parse_uri(uri) |
| if parsed.get('database'): |
| return parsed['database'] |
| except Exception: |
| pass |
|
|
| |
| try: |
| |
| result = urlparse(uri) |
| |
| |
| path = result.path |
| |
| |
| if path.startswith('/'): |
| path = path[1:] |
| |
| |
| return path if path else None |
| except Exception: |
| return None |