Fixes #125 - Added Connection Singleton class

This commit is contained in:
Sangat Das 2022-07-28 02:28:56 -07:00
parent 109f50bec9
commit 60ae991d70
14 changed files with 174 additions and 80 deletions

View File

@ -42,7 +42,7 @@ This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior directed at yourself or another community member may be
reported by contacting a project maintainer at [contact@planetmint.com](mailto:contact@planetmint.com). All
reported by contacting a project maintainer at [mail@planetmint.io](mailto:contact@planetmint.io). All
complaints will be reviewed and investigated and will result in a response that
is appropriate to the circumstances. Maintainers are
obligated to maintain confidentiality with regard to the reporter of an

View File

@ -13,4 +13,4 @@ configuration or the ``PLANETMINT_DATABASE_BACKEND`` environment variable.
# Include the backend interfaces
from planetmint.backend import schema, query, convert # noqa
from planetmint.backend.connection import connect, Connection
from planetmint.backend.connection import Connection

View File

@ -94,9 +94,8 @@ class Connection(metaclass=DBSingleton):
from and implements this class.
"""
def __init__(self, host=None, port=None, dbname=None,
connection_timeout=None, max_tries=None,
**kwargs):
def __init__(self, host: str =None, port: int = None, login: str = None, password: str = None, backend: str = None,
connection_timeout: int = None, max_tries: int = None, **kwargs):
"""Create a new :class:`~.Connection` instance.
Args:
host (str): the host to connect to.
@ -110,23 +109,102 @@ class Connection(metaclass=DBSingleton):
**kwargs: arbitrary keyword arguments provided by the
configuration's ``database`` settings
"""
dbconf = Config().get()['database']
self.host = host or dbconf['host']
self.port = port or dbconf['port']
self.dbname = dbname or dbconf['name']
self.connection_timeout = connection_timeout if connection_timeout is not None \
else dbconf['connection_timeout']
self.connection_timeout = connection_timeout if connection_timeout is not None else Config().get()["database"]
self.max_tries = max_tries if max_tries is not None else dbconf['max_tries']
self.max_tries_counter = range(self.max_tries) if self.max_tries != 0 else repeat(0)
self._conn = None
self.conn = None
@property
def conn(self):
if self._conn is None:
self.connect()
return self._conn
try:
backend = backend
if not backend and kwargs and kwargs.get("backend"):
backend = kwargs["backend"]
if backend and backend != Config().get()["database"]["backend"]:
Config().init_config(backend)
else:
backend = Config().get()["database"]["backend"]
except KeyError:
logger.info("Backend {} not supported".format(backend))
raise ConfigurationError
if(self.conn is None):
try:
self.connect(host=host, port=port, login=login, password=password, backend=backend, kwargs=kwargs)
except tarantool.error.NetworkError as network_err:
print(f"Host {host}:{port} can't be reached.\n{network_err}")
raise network_err
def connect(self, host: str = None, port: int = None, login: str = None, password: str = None, backend: str = None, **kwargs):
"""Try to connect to the database.
Raises:
:exc:`~ConnectionError`: If the connection to the database
fails.
"""
for attempt in self.max_tries_counter:
if (self.conn is None):
try:
modulepath, _, class_name = BACKENDS[backend].rpartition('.')
Class = getattr(import_module(modulepath), class_name)
self.conn = Class(host=host, port=port, login=login, password=password, kwargs=kwargs)
break
except ConnectionError as exc:
logger.warning('Attempt %s/%s. Connection to %s:%s failed after %sms.',
attempt, self.max_tries if self.max_tries != 0 else '',
host, port, self.connection_timeout)
if attempt == self.max_tries:
logger.critical('Cannot connect to the Database. Giving up.')
raise ConnectionError() from exc
else:
break
return self.conn
def close(self):
"""Try to close connection to database.
Raises:
:exc:`~ConnectionError`: If the closing connection to the database
fails.
"""
for attempt in self.max_tries_counter:
if (self.conn is not None):
try:
self.conn.close()
self.conn = None
break
except ConnectionError as exc:
logger.warning('Attempt %s/%s. Close Connection to %s:%s failed after %sms.',
attempt, self.max_tries if self.max_tries != 0 else '',
self.conn.host, self.conn.port, self.connection_timeout)
if attempt == self.max_tries:
logger.critical('Cannot close connection to the Database. Giving up.')
raise ConnectionError() from exc
else:
break
class DBConnection():
def __init__(self, host: str = None, port: int = None, login: str = None, password: str = None, **kwargs):
"""Create a new :class:`~.DBConnection` instance.
Args:
host (str): the host to connect to.
port (int): the port to connect to.
dbname (str): the name of the database to use.
connection_timeout (int, optional): the milliseconds to wait
until timing out the database connection attempt.
Defaults to 5000ms.
max_tries (int, optional): how many tries before giving up,
if 0 then try forever. Defaults to 3.
**kwargs: arbitrary keyword arguments provided by the
configuration's ``database`` settings
"""
dbconf = Config().get()['database']
self.host = host or dbconf["host"] if not kwargs.get("host") else kwargs["host"]
self.port = port or dbconf['port'] if not kwargs.get("port") else kwargs["port"]
self.login = login or dbconf['login'] if not kwargs.get("login") else kwargs["login"]
self.password = password or dbconf['password'] if not kwargs.get("password") else kwargs["password"]
def run(self, query):
"""Run a query.
@ -149,18 +227,12 @@ class Connection(metaclass=DBSingleton):
:exc:`~ConnectionError`: If the connection to the database
fails.
"""
raise NotImplementedError()
attempt = 0
for i in self.max_tries_counter:
attempt += 1
try:
self._conn = self._connect()
except ConnectionError as exc:
logger.warning('Attempt %s/%s. Connection to %s:%s failed after %sms.',
attempt, self.max_tries if self.max_tries != 0 else '',
self.host, self.port, self.connection_timeout)
if attempt == self.max_tries:
logger.critical('Cannot connect to the Database. Giving up.')
raise ConnectionError() from exc
else:
break
def close(self):
"""Try to close connection to the database.
Raises:
:exc:`~ConnectionError`: If the connection to the database
fails.
"""
raise NotImplementedError()

View File

@ -13,15 +13,13 @@ from planetmint.backend.exceptions import (DuplicateKeyError,
ConnectionError)
from planetmint.transactions.common.exceptions import ConfigurationError
from planetmint.utils import Lazy
from planetmint.backend.connection import Connection
from planetmint.backend.connection import DBConnection, _kwargs_parser
logger = logging.getLogger(__name__)
class LocalMongoDBConnection(Connection):
class LocalMongoDBConnection(DBConnection):
def __init__(self, replicaset=None, ssl=None, login=None, password=None,
ca_cert=None, certfile=None, keyfile=None,
keyfile_passphrase=None, crlfile=None, **kwargs):
def __init__(self, host: str =None, port: int = None, login: str = None, password: str = None, **kwargs):
"""Create a new Connection instance.
Args:
@ -31,16 +29,23 @@ class LocalMongoDBConnection(Connection):
configuration's ``database`` settings
"""
super().__init__(**kwargs)
self.replicaset = replicaset or Config().get()['database']['replicaset']
self.ssl = ssl if ssl is not None else Config().get()['database']['ssl']
self.login = login or Config().get()['database']['login']
self.password = password or Config().get()['database']['password']
self.ca_cert = ca_cert or Config().get()['database']['ca_cert']
self.certfile = certfile or Config().get()['database']['certfile']
self.keyfile = keyfile or Config().get()['database']['keyfile']
self.keyfile_passphrase = keyfile_passphrase or Config().get()['database']['keyfile_passphrase']
self.crlfile = crlfile or Config().get()['database']['crlfile']
super().__init__(host=host, port=port, login=login, password=password, **kwargs)
dbconf = Config().get()['database']
self.dbname = _kwargs_parser(key="name", kwargs=kwargs) or dbconf['name']
self.replicaset = _kwargs_parser(key="replicaset", kwargs=kwargs) or dbconf['replicaset']
self.ssl = _kwargs_parser(key="ssl", kwargs=kwargs) or dbconf['ssl']
self.ca_cert = _kwargs_parser(key="ca_cert", kwargs=kwargs) or dbconf['ca_cert']
self.certfile = _kwargs_parser(key="certfile", kwargs=kwargs) or dbconf['certfile']
self.keyfile = _kwargs_parser(key="keyfile", kwargs=kwargs) or dbconf['keyfile']
self.keyfile_passphrase = _kwargs_parser(key="keyfile_passphrase", kwargs=kwargs) or dbconf[
'keyfile_passphrase']
self.crlfile = _kwargs_parser(key="crlfile", kwargs=kwargs) or dbconf['crlfile']
self.max_tries = _kwargs_parser(key="max_tries", kwargs=kwargs)
self.connection_timeout = _kwargs_parser(key="connection_timeout", kwargs=kwargs)
self.conn = self.connect()
if not self.ssl:
self.ssl = False
if not self.keyfile_passphrase:
@ -77,7 +82,7 @@ class LocalMongoDBConnection(Connection):
print(f'DETAILS: {exc.details}')
raise OperationError from exc
def _connect(self):
def connect(self):
"""Try to connect to the database.
Raises:
@ -127,11 +132,18 @@ class LocalMongoDBConnection(Connection):
except (pymongo.errors.ConnectionFailure,
pymongo.errors.OperationFailure) as exc:
logger.info('Exception in _connect(): {}'.format(exc))
logger.info('Exception in connect(): {}'.format(exc))
raise ConnectionError(str(exc)) from exc
except pymongo.errors.ConfigurationError as exc:
raise ConfigurationError from exc
def close(self):
try:
self.conn.close()
self.conn = None
except Exception as exc:
logger.info('Exception in planetmint.backend.localmongodb.close(): {}'.format(exc))
raise ConnectionError(str(exc)) from exc
MONGO_OPTS = {
'socketTimeoutMS': 20000,

View File

@ -9,7 +9,7 @@ from functools import singledispatch
import logging
from planetmint.config import Config
from planetmint.backend.connection import connect
from planetmint.backend.connection import Connection
from planetmint.transactions.common.exceptions import ValidationError
from planetmint.transactions.common.utils import (
validate_all_values_for_key_in_obj, validate_all_values_for_key_in_list)
@ -83,7 +83,8 @@ def init_database(connection=None, dbname=None):
configuration.
"""
connection = connection or connect()
connection = connection or Connection().conn
print("=========================================", connection.__class__, "=========================================================")
dbname = dbname or Config().get()['database']['name']
create_database(connection, dbname)

View File

@ -9,27 +9,27 @@ import tarantool
from planetmint.config import Config
from planetmint.transactions.common.exceptions import ConfigurationError
from planetmint.utils import Lazy
from planetmint.backend.connection import Connection
from planetmint.backend.connection import DBConnection
logger = logging.getLogger(__name__)
class TarantoolDBConnection(Connection):
class TarantoolDBConnection(DBConnection):
def __init__(
self,
host: str = "localhost",
port: int = 3303,
user: str = None,
login: str = None,
password: str = None,
**kwargs,
):
try:
super().__init__(**kwargs)
self.host = host
self.port = port
# TODO add user support later on
self.init_path = Config().get()["database"]["init_config"]["absolute_path"]
self.drop_path = Config().get()["database"]["drop_config"]["absolute_path"]
super().__init__(host=host, port=port, login=login, password=password, **kwargs)
dbconf = Config().get()["database"]
self.init_path = dbconf["init_config"]["absolute_path"]
self.drop_path = dbconf["drop_config"]["absolute_path"]
self.conn = self.connect()
self.SPACE_NAMES = [
"abci_chains",
"assets",
@ -60,9 +60,17 @@ class TarantoolDBConnection(Connection):
f.close()
return "".join(execute).encode()
def _connect(self):
def connect(self):
return tarantool.connect(host=self.host, port=self.port)
def close(self):
try:
self.conn.close()
self.conn = None
except Exception as exc:
logger.info('Exception in planetmint.backend.tarantool.close(): {}'.format(exc))
raise ConnectionError(str(exc)) from exc
def get_space(self, space_name: str):
return self.conn.space(space_name)

View File

@ -264,8 +264,8 @@ def run_drop(args):
if response != 'y':
return
from planetmint.backend.connection import connect
conn = connect()
from planetmint.backend.connection import Connection
conn = Connection().conn
try:
schema.drop_database(conn)
except DatabaseDoesNotExist:

View File

@ -10,6 +10,7 @@ MongoDB.
import logging
from collections import namedtuple
from uuid import uuid4
from planetmint.backend.connection import Connection
import rapidjson
@ -74,7 +75,7 @@ class Planetmint(object):
self.validation = config_utils.load_validation_plugin(validationPlugin)
else:
self.validation = BaseValidationRules
self.connection = connection if connection is not None else planetmint.backend.connect()
self.connection = connection if connection is not None else Connection().conn
def post_transaction(self, transaction, mode):
"""Submit a valid transaction to the mempool."""

View File

@ -1,5 +1,5 @@
import pytest
from planetmint.backend.connection import connect
from planetmint.backend.connection import Connection
#
@ -27,5 +27,5 @@ from planetmint.backend.connection import connect
@pytest.fixture
def db_conn():
conn = connect()
conn = Connection().conn
return conn

View File

@ -8,9 +8,9 @@ import pytest
def test_get_connection_raises_a_configuration_error(monkeypatch):
from planetmint.transactions.common.exceptions import ConfigurationError
from planetmint.backend.connection import connect
from planetmint.backend.connection import Connection
with pytest.raises(ConfigurationError):
connect('localhost', '1337', 'mydb', 'password', 'msaccess')
Connection().connect('localhost', '1337', 'mydb', 'password', 'msaccess')
with pytest.raises(ConfigurationError):
# We need to force a misconfiguration here
@ -18,4 +18,4 @@ def test_get_connection_raises_a_configuration_error(monkeypatch):
{'catsandra':
'planetmint.backend.meowmeow.Catsandra'})
connect('localhost', '1337', 'mydb', 'password', 'catsandra')
Connection().connect('localhost', '1337', 'mydb', 'password', 'catsandra')

View File

@ -14,6 +14,7 @@ import pytest
from planetmint.config import Config
from planetmint import ValidatorElection
from planetmint.commands.planetmint import run_election_show
from planetmint.backend.connection import Connection
from planetmint.transactions.types.elections.election import Election
from planetmint.lib import Block
from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection
@ -93,9 +94,7 @@ def test__run_init(mocker):
init_db_mock = mocker.patch(
'planetmint.backend.tarantool.connection.TarantoolDBConnection.init_database')
from planetmint.backend.connection import connect
conn = connect()
conn = Connection().conn
conn.init_database()
init_db_mock.assert_called_once_with()

View File

@ -18,7 +18,7 @@ import codecs
from collections import namedtuple
from logging import getLogger
from logging.config import dictConfig
from planetmint.backend.connection import connect
from planetmint.backend.connection import Connection
from planetmint.backend.tarantool.connection import TarantoolDBConnection
import pytest
@ -127,7 +127,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa
print('Initializing test db')
dbname = Config().get()['database']['name']
conn = connect()
conn = Connection().conn
_drop_db(conn, dbname)
schema.init_database(conn, dbname)
@ -136,7 +136,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa
yield
print('Deleting `{}` database'.format(dbname))
conn = connect()
conn = Connection().conn
_drop_db(conn, dbname)
print('Finished deleting `{}`'.format(dbname))
@ -148,7 +148,7 @@ def _bdb(_setup_database, _configure_planetmint):
from planetmint.models import Transaction
from .utils import flush_db
from planetmint.config import Config
conn = connect()
conn = Connection().conn
yield
dbname = Config().get()['database']['name']
flush_db(conn, dbname)
@ -389,7 +389,7 @@ def db_name(db_config):
@pytest.fixture
def db_conn():
return connect()
return Connection().conn
@pytest.fixture

View File

@ -93,7 +93,7 @@ def test_filter_unspent_outputs(b, user_pk, user_sk):
def test_outputs_query_key_order(b, user_pk, user_sk, user2_pk, user2_sk):
from planetmint import backend
from planetmint.backend.connection import connect
from planetmint.backend.connection import Connection
from planetmint.backend import query
tx1 = Create.generate([user_pk],
@ -119,7 +119,7 @@ def test_outputs_query_key_order(b, user_pk, user_sk, user2_pk, user2_sk):
# clean the transaction, metdata and asset collection
# conn = connect()
connection = connect()
connection = Connection().conn
# conn.run(conn.collection('transactions').delete_many({}))
# conn.run(conn.collection('metadata').delete_many({}))
# conn.run(conn.collection('assets').delete_many({}))

View File

@ -68,15 +68,16 @@ def test_bigchain_class_default_initialization(config):
def test_bigchain_class_initialization_with_parameters():
from planetmint import Planetmint
from planetmint.backend import connect
from planetmint.backend import Connection
from planetmint.validation import BaseValidationRules
init_db_kwargs = {
'backend': 'localmongodb',
'host': 'this_is_the_db_host',
'port': 12345,
'name': 'this_is_the_db_name',
}
connection = connect(**init_db_kwargs)
connection = Connection(**init_db_kwargs).conn
planet = Planetmint(connection=connection)
assert planet.connection == connection
assert planet.connection.host == init_db_kwargs['host']