# python """Lazy MongoDB client factory and database-name helpers. The client is intentionally **not** created at module import. Use ``get_mongo_client()`` to obtain a cached singleton. For Gunicorn multi-worker deployments call ``init_mongo_client()`` from a ``post_fork`` hook so each worker process owns its own connection pool rather than inheriting the parent process's client. """ import os import threading from urllib.parse import urlparse from pymongo import MongoClient from pymongo.uri_parser import parse_uri _mongo_client_lock = threading.Lock() _mongo_client = None def _create_mongo_client(): """Build a fail-fast MongoClient from environment variables.""" uri = os.environ.get('MONGO_URI') #log the MONGO_URI for debugging purposes, I will change it later so it doesn't log the password print(f"Using MONGO_URI: {uri}") if not uri: raise RuntimeError( 'MONGO_URI environment variable is required when USE_MONGODB=true.' ) # mongomock is used for unit/integration tests without a real server. if uri.lower().startswith('mongomock') or uri.lower() == 'mongomock': try: import mongomock except ImportError as exc: # pragma: no cover - test dependency raise RuntimeError( 'mongomock is required for test MongoDB mode. ' 'Install it with: pip install mongomock' ) from exc return mongomock.MongoClient() return MongoClient( uri, serverSelectionTimeoutMS=5000, connectTimeoutMS=5000, maxPoolSize=20, ) def init_mongo_client(): """Create a fresh MongoClient and store it as the process singleton. Call this from a Gunicorn ``post_fork`` hook so each worker process gets its own client after forking. It can also be called in tests to reset the shared client to a known state. """ global _mongo_client with _mongo_client_lock: _mongo_client = _create_mongo_client() return _mongo_client def get_mongo_client(): """Return the cached process-level MongoClient, creating it lazily once.""" global _mongo_client if _mongo_client is None: with _mongo_client_lock: if _mongo_client is None: _mongo_client = _create_mongo_client() return _mongo_client def _db_name_from_uri(uri: str) -> str | None: """Extract the database name from a MongoDB connection URI, if present.""" if not uri or uri.lower().startswith('mongomock'): return None try: parsed = parse_uri(uri) return parsed.get('database') or None except Exception: # Fallback to a simple path-based parse for non-standard URIs. try: path = urlparse(uri).path return path.lstrip('/') or None except Exception: return None def get_mongo_db_name() -> str: """Resolve the MongoDB database name from env vars or the connection URI. Precedence: 1. ``MONGO_DB_NAME`` environment variable. 2. Database name parsed from ``MONGO_URI``. 3. Default based on ``DATA_ENV`` / ``DB_ENV``: * prod -> ``chore_db`` * e2e -> ``chore_db_e2e`` * test -> ``chore_db_test`` """ env_name = os.environ.get('MONGO_DB_NAME') if env_name: return env_name uri = os.environ.get('MONGO_URI', '') db_name = _db_name_from_uri(uri) if db_name: return db_name env = (os.environ.get('DATA_ENV') or os.environ.get('DB_ENV', 'prod')).lower() if env == 'prod': return 'chore_db' if env == 'e2e': return 'chore_db_e2e' return 'chore_db_test'