11# -*- coding: utf-8 -*-
22import logging
3- from typing import Optional , Type
3+ from typing import Optional , Type , Dict , Any , Union
4+ from urllib .parse import quote_plus
45
5- from .error import NotSupportedError
6+ from pymongo import MongoClient
7+ from pymongo .database import Database
8+ from pymongo .collection import Collection
9+ from pymongo .errors import PyMongoError , ConnectionFailure
10+
11+ from .error import NotSupportedError , DatabaseError , OperationalError
612from .common import BaseCursor
13+ from .cursor import Cursor
714
815_logger = logging .getLogger (__name__ )
916
1017
1118class Connection :
19+ """MongoDB connection wrapper that provides SQL-like interface"""
1220
1321 def __init__ (
1422 self ,
23+ host : str ,
24+ port : int ,
25+ database : str = None ,
26+ username : str = None ,
27+ password : str = None ,
28+ auth_source : str = None ,
29+ ssl : bool = None ,
30+ ssl_cert_reqs : str = None ,
31+ connection_timeout : int = None ,
32+ server_selection_timeout : int = None ,
1533 ** kwargs ,
16- ) -> None : ...
34+ ) -> None :
35+ """Initialize MongoDB connection
36+
37+ Args:
38+ host: MongoDB host (required)
39+ port: MongoDB port (required)
40+ database: Default database name (optional)
41+ username: Username for authentication (optional)
42+ password: Password for authentication (optional)
43+ auth_source: Authentication database (optional)
44+ ssl: Enable SSL (optional)
45+ ssl_cert_reqs: SSL certificate requirements (optional)
46+ connection_timeout: Connection timeout in ms (optional)
47+ server_selection_timeout: Server selection timeout in ms (optional)
48+ **kwargs: Additional PyMongo connection parameters
49+ """
50+ self ._host = host
51+ self ._port = port
52+ self ._database_name = database
53+ self ._username = username
54+ self ._password = password
55+ self ._auth_source = auth_source
56+ self ._ssl = ssl
57+ self ._ssl_cert_reqs = ssl_cert_reqs
58+ self ._connection_timeout = connection_timeout
59+ self ._server_selection_timeout = server_selection_timeout
60+
61+ self ._autocommit = True
62+ self ._in_transaction = False
63+ self ._client : Optional [MongoClient ] = None
64+ self ._database : Optional [Database ] = None
65+ self .cursor_pool = []
66+ self .cursor_class = Cursor
67+ self .cursor_kwargs = kwargs
68+
69+ # Establish connection
70+ self ._connect ()
71+
72+ def _connect (self ) -> None :
73+ """Establish connection to MongoDB"""
74+ try :
75+ # Build connection string
76+ if self ._username and self ._password :
77+ auth_string = (
78+ f"{ quote_plus (self ._username )} :{ quote_plus (self ._password )} @"
79+ )
80+ else :
81+ auth_string = ""
82+
83+ connection_string = f"mongodb://{ auth_string } { self ._host } :{ self ._port } /"
84+
85+ # Connection options with defaults only when needed
86+ options = {}
87+
88+ if self ._connection_timeout is not None :
89+ options ["connectTimeoutMS" ] = self ._connection_timeout
90+ if self ._server_selection_timeout is not None :
91+ options ["serverSelectionTimeoutMS" ] = self ._server_selection_timeout
92+ if self ._ssl is not None :
93+ options ["ssl" ] = self ._ssl
94+ if self ._ssl_cert_reqs is not None :
95+ options ["ssl_cert_reqs" ] = self ._ssl_cert_reqs
96+
97+ if self ._username and self ._auth_source :
98+ options ["authSource" ] = self ._auth_source
99+
100+ # Create client
101+ self ._client = MongoClient (connection_string , ** options )
102+
103+ # Test connection
104+ self ._client .admin .command ("ping" )
105+
106+ # Set database if specified
107+ if self ._database_name :
108+ self ._database = self ._client [self ._database_name ]
109+
110+ _logger .info (
111+ f"Successfully connected to MongoDB at { self ._host } :{ self ._port } "
112+ )
113+
114+ except ConnectionFailure as e :
115+ _logger .error (f"Failed to connect to MongoDB: { e } " )
116+ raise OperationalError (f"Could not connect to MongoDB: { e } " )
117+ except Exception as e :
118+ _logger .error (f"Unexpected error during connection: { e } " )
119+ raise DatabaseError (f"Database connection error: { e } " )
120+
121+ @property
122+ def client (self ) -> MongoClient :
123+ """Get the PyMongo client"""
124+ if self ._client is None :
125+ raise OperationalError ("No active connection" )
126+ return self ._client
127+
128+ @property
129+ def database (self ) -> Database :
130+ """Get the current database"""
131+ if self ._database is None :
132+ raise OperationalError ("No database selected" )
133+ return self ._database
134+
135+ def use_database (self , database_name : str ) -> None :
136+ """Switch to a different database"""
137+ if self ._client is None :
138+ raise OperationalError ("No active connection" )
139+ self ._database_name = database_name
140+ self ._database = self ._client [database_name ]
141+ _logger .info (f"Switched to database: { database_name } " )
142+
143+ def get_collection (self , collection_name : str ) -> Collection :
144+ """Get a collection from the current database"""
145+ if self ._database is None :
146+ raise OperationalError ("No database selected" )
147+ return self ._database [collection_name ]
17148
18149 @property
19150 def autocommit (self ) -> bool :
@@ -37,31 +168,107 @@ def in_transaction(self) -> bool:
37168 def in_transaction (self , value : bool ) -> bool :
38169 self ._in_transaction = False
39170
171+ @property
172+ def host (self ) -> str :
173+ """Get the hostname"""
174+ return self ._host
175+
176+ @property
177+ def port (self ) -> int :
178+ """Get the port number"""
179+ return self ._port
180+
181+ @property
182+ def database_name (self ) -> str :
183+ """Get the database name"""
184+ return self ._database_name
185+
186+ @property
187+ def username (self ) -> str :
188+ """Get the username"""
189+ return self ._username
190+
191+ @property
192+ def password (self ) -> str :
193+ """Get the password"""
194+ return self ._password
195+
40196 def __enter__ (self ):
41197 return self
42198
43199 def __exit__ (self , exc_type , exc_val , exc_tb ):
44200 self .close ()
45201
202+ @property
203+ def is_connected (self ) -> bool :
204+ """Check if connected to MongoDB"""
205+ return self ._client is not None
206+
207+ @property
208+ def database_instance (self ):
209+ """Get the database instance"""
210+ return self ._database
211+
212+ def disconnect (self ) -> None :
213+ """Disconnect from MongoDB (alias for close)"""
214+ self .close ()
215+
216+ def __str__ (self ) -> str :
217+ """String representation of the connection"""
218+ status = "connected" if self .is_connected else "disconnected"
219+ return f"Connection(host={ self ._host } , port={ self ._port } , database={ self ._database_name } , status={ status } )"
220+
46221 def cursor (self , cursor : Optional [Type [BaseCursor ]] = None , ** kwargs ) -> BaseCursor :
47222 kwargs .update (self .cursor_kwargs )
48223 if not cursor :
49224 cursor = self .cursor_class
50225
51- return cursor (
226+ new_cursor = cursor (
52227 connection = self ,
53228 ** kwargs ,
54229 )
230+ self .cursor_pool .append (new_cursor )
231+ return new_cursor
55232
56- def close (self ) -> None : ...
233+ def close (self ) -> None :
234+ """Close the MongoDB connection"""
235+ try :
236+ # Close all cursors
237+ for cursor in self .cursor_pool :
238+ cursor .close ()
239+ self .cursor_pool .clear ()
240+
241+ # Close client connection
242+ if self ._client :
243+ self ._client .close ()
244+ self ._client = None
245+ self ._database = None
246+
247+ _logger .info ("MongoDB connection closed" )
248+ except Exception as e :
249+ _logger .error (f"Error closing connection: { e } " )
57250
58251 def begin (self ) -> None :
59252 self ._autocommit = False
60253 self ._in_transaction = True
61254
62- def commit (self ) -> None : ...
255+ def commit (self ) -> None :
256+ """Commit transaction (MongoDB doesn't support traditional transactions in the same way)"""
257+ self ._in_transaction = False
258+ self ._autocommit = True
63259
64260 def rollback (self ) -> None :
65- raise NotSupportedError
261+ raise NotSupportedError (
262+ "MongoDB doesn't support rollback in the traditional SQL sense"
263+ )
66264
67- def test_connection (self ) -> bool : ...
265+ def test_connection (self ) -> bool :
266+ """Test if the connection is alive"""
267+ try :
268+ if self ._client :
269+ self ._client .admin .command ("ping" )
270+ return True
271+ return False
272+ except Exception as e :
273+ _logger .error (f"Connection test failed: { e } " )
274+ return False
0 commit comments