I wrote a class for some MongoDB operations. So far I'm self-taught and mostly I follow the style from already existing code at my workplace (a startup).
We don't have a code-review culture, so I'm pretty new to the process, but I'm open fortips for everything from code optimization, better logging, better documentation, etc.
import logging
from logging.handlers import TimedRotatingFileHandler
from pymongo import MongoClient
from pymongo import errors
from warnings import warn
HOST = 'MONGODB_HOST'
DEFAULT_HOST = 'localhost'
PORT = 'MONGODB_PORT'
DEFAULT_PORT = '27017'
TIMEOUT = 'MONGODB_CONN_TIMEOUT'
DATABASE = 'MONGODB_DB'
COLLECTION = 'MONGODB_DB_CLIENT'
USERNAME = 'MONGODB_USERNAME'
PASSWORD = 'MONGODB_PASSWORD'
LOG_FILE = 'LOG_FILE'
class MongoDB:
"""
A class used to manage connections to MongoDB
...
Attributes
----------
conf : dict
a dictionary that has the configuration for class instance.
client : MongoClient
the MongoClient from pymongo
database : pymongo.database.Database
the database instance from MongoClient
collection : pymongo.collection.Collection
the collection instance from database
verbose: int
the verbosity of the class (default 5)
logger: logging.logger
an logger to log all database operations
Methods
-------
__init_logger__()
Initializes the logger.
connect_to_client( host=None, port=None, timeout=3000, username=None, password=None)
Connects to the Mongodb instance using MongoClient from PyMongo.
connect_to_database(database_name=None)
Connects to the collection in the mongodb instance.
connect_to_collection(collection_name=None)
Connects to the collection in the mongodb database.
perform_bulk_operations(list_operations_to_perform_in_bulk=None)
Executes the operations against MongoDB in bulk.
"""
def __init__(self, conf, verbose=5):
self.conf = conf
self.client = None
self.database = None
self.collection = None
self.verbose = verbose
self.logger = self.__init_logger__()
def __init_logger__(self) -> logging.getLogger():
"""This function initializes the logger."""
logging_file = self.conf.get(LOG_FILE, 'db.log')
logger = logging.getLogger(__name__)
log_formatter = logging.Formatter('%(asctime)s|%(name)-12s|%(levelname)-8s|%(message)s')
log_handler = TimedRotatingFileHandler(filename=f'{logging_file}', when='s', interval=10)
log_handler.setFormatter(log_formatter)
logger.addHandler(log_handler)
logger.setLevel(logging.DEBUG)
return logger
def connect_to_client(self, host=None, port=None, timeout=3000, username=None, password=None):
"""This function connects to the Mongodb instance using MongoClient from PyMongo.
1. If the parameters are not passed they are set to their default value.
2. Connects to the database. The parameters determine the connection profile.
Raises OperationFailure if authentication fails.
3. Testing connection.
Raises ServerSelectionTimeoutError, if cannot connect to the database in a timely manner.
Parameters
----------
host: str, optional
The ip address of the mongodb address. The default 'localhost'.
port: str, optional
The port of the mongodb instance. Default is '27017'.
timeout: int, optional
The number of seconds to try connecting to the MongoDB instance before timing out. Default is 3 seconds.
username: str, optional
The username for authentication. Default is None.
password: str, optional
The password for authentication. Default is None.
Raises
------
errors.ServerSelectionTimeoutError
If attempt to connect to the server times out.
errors.OperationFailure
If authentication with the server fails.
"""
# 1. If the parameters are not passed they are set to their default value.
host = self.conf.get(HOST, host)
port = self.conf.get(PORT, port)
timeout = self.conf.get(TIMEOUT, timeout)
username = self.conf.get(USERNAME, username)
password = self.conf.get(PASSWORD, password)
if host is None:
self.logger.warning(f"No \'{host}\' defined in configuration. Connecting to {DEFAULT_HOST}.")
host = DEFAULT_HOST
if port is None:
self.logger.warning(f"No \'{port}\' defined in configuration. Connecting to {DEFAULT_PORT}.")
port = DEFAULT_PORT
connection_host_and_port = f'{host}:{port}'
try:
# 2. Connects to the database. The parameters determine the connection profile.
# Raises OperationFailure if authentication fails.
if username is not None and password is not None:
self.logger.warning(f"Username and password are defined in configuration. "
f"Connecting with login credentials.")
self.client = MongoClient(
host=connection_host_and_port, # <-- IP and port go here
serverSelectionTimeoutMS=timeout, # 3 se+cond timeout
username=f"{username}",
password=f"{password}",
authSource='admin',
)
else:
self.logger.warning(f"No username or password defined in configuration. "
f"Connecting without login credentials.")
self.client = MongoClient(
host=connection_host_and_port, # <-- IP and port go here
serverSelectionTimeoutMS=timeout, # 3 se+cond timeout
)
# 3. Testing connection.
# Raises ServerSelectionTimeoutError, if cannot connect to the database in a timely manner.
self.client.server_info()
except errors.ServerSelectionTimeoutError as err:
self.logger.error(f'Connection to \'{connection_host_and_port}\' timed out.')
raise err
except errors.OperationFailure as err:
self.logger.error(f'Authentication to \'{connection_host_and_port}\' failed.')
print(err)
raise err
else:
self.logger.debug(f'Created connection to {connection_host_and_port}')
def connect_to_database(self, database_name=None):
"""This function connects to the database in the mongodb instance.
Parameters
----------
database_name: str, optional
The name of the database. The default 'None'.
Raises
------
ValueError
If database name is None.
"""
database_name = self.conf.get(DATABASE, database_name)
if database_name is not None:
if self.conf[DATABASE] not in self.client.database_names():
msg = f'Database \'{database_name}\' does not exist. Creating database.'
self.logger.warning(msg)
if self.verbose >= 1:
warn(msg)
self.database = self.client[database_name]
self.logger.debug(f'Connected to database: \'{database_name}\'')
else:
msg = 'No Database specified.'
self.logger.error(msg)
if self.verbose >= 1:
warn(msg)
raise ValueError(msg)
def connect_to_collection(self, collection_name=None):
"""This function connects to the collection in the mongodb database.
Parameters
----------
collection_name: str, optional
The name of the collection. The default 'None'.
Raises
------
ValueError
If collection name is None.
"""
collection_name = self.conf.get(COLLECTION, collection_name)
if collection_name is not None:
if collection_name not in self.database.collection_names():
msg = f'Collection \'{collection_name}\' does not exist. Creating collection.'
self.logger.warning(msg)
if self.verbose >= 1:
warn(msg)
self.collection = self.database[collection_name]
self.logger.debug(f'Connected to Collection: \'{collection_name}\'')
else:
msg = 'No Collection specified.'
self.logger.error(msg)
if self.verbose >= 1:
warn(msg)
raise ValueError(msg)
def perform_bulk_operations(self, list_operations_to_perform_in_bulk=None):
"""This function executes the operations against MongoDB in bulk.
Parameters
----------
list_operations_to_perform_in_bulk: list, optional
The list of operations to perform. The default 'None'.
Raises
------
ValueError
If requests is empty.
"""
if list_operations_to_perform_in_bulk is None:
list_operations_to_perform_in_bulk = []
if len(list_operations_to_perform_in_bulk) > 0:
try:
res = self.collection.bulk_write(list_operations_to_perform_in_bulk, ordered=False)
except errors.BulkWriteError as bwe:
self.logger.error(bwe.details['writeErrors'])
if self.verbose >= 1:
warn(bwe.details)
raise bwe
else:
self.logger.info(res.bulk_api_result)
return res
else:
msg = 'No operations to perform.'
self.logger.error(msg)
raise ValueError(msg)