File size: 4,799 Bytes
9d76e23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca2bd00
9d76e23
 
 
 
 
 
 
 
 
 
 
ca2bd00
9d76e23
 
ca2bd00
9d76e23
ca2bd00
9d76e23
 
ca2bd00
9d76e23
 
ca2bd00
9d76e23
 
ca2bd00
9d76e23
ca2bd00
9d76e23
 
ca2bd00
 
 
 
 
 
9d76e23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# Import required MongoDB libraries and utilities
from pymongo import MongoClient
from pymongo.database import Database
from pymongo.collection import Collection
from pymongo.errors import ConnectionFailure, ConfigurationError, OperationFailure
import logging
from typing import Optional
from src.config.settings import (
    MONGO_URI, MONGO_DB_NAME, MONGO_NEWS_COLLECTION_NAME,
    MONGO_SESSIONS_COLLECTION_NAME, MONGO_TRACKING_COLLECTION_NAME
)

# Set up logger for this module
logger = logging.getLogger(__name__)

class MongoDB:
    """
    MongoDB connection handler implementing the Singleton pattern.
    This class manages the connection to MongoDB Atlas and provides access to collections.
    """
    # Class variables to store singleton instance and connection details
    _instance: Optional['MongoDB'] = None
    _client: Optional[MongoClient] = None
    _db: Optional[Database] = None

    def __new__(cls):
        """
        Implements the Singleton pattern by ensuring only one instance of MongoDB class exists.
        Returns the existing instance if it exists, otherwise creates a new one.
        """
        if cls._instance is None:
            cls._instance = super(MongoDB, cls).__new__(cls)
        return cls._instance

    def __init__(self):
        """
        Initialize the MongoDB connection if it hasn't been established yet.
        Only creates a new connection if _client is None.
        """
        if self._client is None:
            self._connect()

    def _connect(self):
        """
        Establishes connection to MongoDB Atlas.
        Handles connection errors and sets up the database instance.
        Does not raise exceptions if connection fails - allows graceful degradation.
        """
        try:
            logger.info(f"Connecting to MongoDB Atlas: DB='{MONGO_DB_NAME}'")
            # Create MongoDB client with 5 second timeout for server selection
            self._client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
            # Verify connection by running a simple command
            self._client.admin.command('ismaster')
            # Set up database instance
            self._db = self._client[MONGO_DB_NAME]
            logger.info("Successfully connected to MongoDB.")
        except (ConnectionFailure, ConfigurationError) as e:
            logger.warning(f"MongoDB connection failed: {e}")
            self._client = None
            self._db = None
            # Don't raise the exception - allow the application to continue without MongoDB
        except Exception as e:
            logger.warning(f"An unexpected error occurred during MongoDB connection: {e}")
            self._client = None
            self._db = None
            # Don't raise the exception - allow the application to continue without MongoDB

    @property
    def db(self) -> Optional[Database]:
        """
        Property to get the database instance.
        Returns None if the connection is not available.
        Returns:
            Database: MongoDB database instance or None if not connected
        """
        if self._db is None:
            # Try to connect once, but don't keep retrying
            try:
                self._connect()
            except Exception as e:
                logger.warning(f"Failed to establish MongoDB connection: {e}")
                return None
        return self._db

    @property
    def news_collection(self) -> Collection:
        """
        Property to get the news collection.
        Returns:
            Collection: MongoDB collection for news data
        """
        return self.db[MONGO_NEWS_COLLECTION_NAME]

    @property
    def sessions_collection(self) -> Collection:
        """
        Property to get the sessions collection.
        Returns:
            Collection: MongoDB collection for session data
        """
        return self.db[MONGO_SESSIONS_COLLECTION_NAME]

    @property
    def tracking_collection(self) -> Collection:
        """
        Property to get the tracking collection.
        Returns:
            Collection: MongoDB collection for user feedback/tracking data
        """
        return self.db[MONGO_TRACKING_COLLECTION_NAME]

    def close(self):
        """
        Closes the MongoDB connection and cleans up resources.
        Handles any errors during connection closure.
        """
        if self._client:
            try:
                self._client.close()
                logger.info("MongoDB connection closed.")
            except Exception as e:
                logger.error(f"Error closing MongoDB connection: {e}", exc_info=True)
            finally:
                self._client = None
                self._db = None

# Create a singleton instance of MongoDB for use throughout the application
mongodb = MongoDB()