Executor / mongo_service.py
Soumik Bose
update mongo exec
369b363
Raw
History Blame Contribute Delete
5.06 kB
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")
# ==============================================================================
# MONGO CONNECTION MANAGER
# ==============================================================================
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()
# ==============================================================================
# HELPER FUNCTIONS
# ==============================================================================
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()
# Sanitize JS specific wrappers
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)}")
# ==============================================================================
# MAIN EXECUTION LOGIC
# ==============================================================================
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 = []
# --- Pipeline (List) ---
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)
# --- Find (Dict) ---
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
# Method 1: PyMongo Strict Parser
try:
parsed = parse_uri(uri)
if parsed.get('database'):
return parsed['database']
except Exception:
pass # Fallback if PyMongo complains about the URI format
# Method 2: Generic URL Parsing (Mimics your TypeScript logic)
try:
# Standard URL parsing
result = urlparse(uri)
# The database name is usually the 'path' part (e.g., /api_platform)
path = result.path
# Remove leading slash if present
if path.startswith('/'):
path = path[1:]
# If empty, return None
return path if path else None
except Exception:
return None