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. 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 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 complaints will be reviewed and investigated and will result in a response that
is appropriate to the circumstances. Maintainers are is appropriate to the circumstances. Maintainers are
obligated to maintain confidentiality with regard to the reporter of an 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 # Include the backend interfaces
from planetmint.backend import schema, query, convert # noqa 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. from and implements this class.
""" """
def __init__(self, host=None, port=None, dbname=None, def __init__(self, host: str =None, port: int = None, login: str = None, password: str = None, backend: str = None,
connection_timeout=None, max_tries=None, connection_timeout: int = None, max_tries: int = None, **kwargs):
**kwargs):
"""Create a new :class:`~.Connection` instance. """Create a new :class:`~.Connection` instance.
Args: Args:
host (str): the host to connect to. host (str): the host to connect to.
@ -110,23 +109,102 @@ class Connection(metaclass=DBSingleton):
**kwargs: arbitrary keyword arguments provided by the **kwargs: arbitrary keyword arguments provided by the
configuration's ``database`` settings configuration's ``database`` settings
""" """
dbconf = Config().get()['database'] dbconf = Config().get()['database']
self.host = host or dbconf['host'] self.connection_timeout = connection_timeout if connection_timeout is not None else Config().get()["database"]
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.max_tries = max_tries if max_tries is not None else dbconf['max_tries'] 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.max_tries_counter = range(self.max_tries) if self.max_tries != 0 else repeat(0)
self._conn = None self.conn = None
@property try:
def conn(self): backend = backend
if self._conn is None: if not backend and kwargs and kwargs.get("backend"):
self.connect() backend = kwargs["backend"]
return self._conn
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): def run(self, query):
"""Run a query. """Run a query.
@ -149,18 +227,12 @@ class Connection(metaclass=DBSingleton):
:exc:`~ConnectionError`: If the connection to the database :exc:`~ConnectionError`: If the connection to the database
fails. fails.
""" """
raise NotImplementedError()
attempt = 0 def close(self):
for i in self.max_tries_counter: """Try to close connection to the database.
attempt += 1 Raises:
try: :exc:`~ConnectionError`: If the connection to the database
self._conn = self._connect() fails.
except ConnectionError as exc: """
logger.warning('Attempt %s/%s. Connection to %s:%s failed after %sms.', raise NotImplementedError()
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

View File

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

View File

@ -9,7 +9,7 @@ from functools import singledispatch
import logging import logging
from planetmint.config import Config 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.exceptions import ValidationError
from planetmint.transactions.common.utils import ( from planetmint.transactions.common.utils import (
validate_all_values_for_key_in_obj, validate_all_values_for_key_in_list) 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. configuration.
""" """
connection = connection or connect() connection = connection or Connection().conn
print("=========================================", connection.__class__, "=========================================================")
dbname = dbname or Config().get()['database']['name'] dbname = dbname or Config().get()['database']['name']
create_database(connection, dbname) create_database(connection, dbname)

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import pytest 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 @pytest.fixture
def db_conn(): def db_conn():
conn = connect() conn = Connection().conn
return conn return conn

View File

@ -8,9 +8,9 @@ import pytest
def test_get_connection_raises_a_configuration_error(monkeypatch): def test_get_connection_raises_a_configuration_error(monkeypatch):
from planetmint.transactions.common.exceptions import ConfigurationError from planetmint.transactions.common.exceptions import ConfigurationError
from planetmint.backend.connection import connect from planetmint.backend.connection import Connection
with pytest.raises(ConfigurationError): with pytest.raises(ConfigurationError):
connect('localhost', '1337', 'mydb', 'password', 'msaccess') Connection().connect('localhost', '1337', 'mydb', 'password', 'msaccess')
with pytest.raises(ConfigurationError): with pytest.raises(ConfigurationError):
# We need to force a misconfiguration here # We need to force a misconfiguration here
@ -18,4 +18,4 @@ def test_get_connection_raises_a_configuration_error(monkeypatch):
{'catsandra': {'catsandra':
'planetmint.backend.meowmeow.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.config import Config
from planetmint import ValidatorElection from planetmint import ValidatorElection
from planetmint.commands.planetmint import run_election_show from planetmint.commands.planetmint import run_election_show
from planetmint.backend.connection import Connection
from planetmint.transactions.types.elections.election import Election from planetmint.transactions.types.elections.election import Election
from planetmint.lib import Block from planetmint.lib import Block
from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection
@ -93,9 +94,7 @@ def test__run_init(mocker):
init_db_mock = mocker.patch( init_db_mock = mocker.patch(
'planetmint.backend.tarantool.connection.TarantoolDBConnection.init_database') 'planetmint.backend.tarantool.connection.TarantoolDBConnection.init_database')
from planetmint.backend.connection import connect conn = Connection().conn
conn = connect()
conn.init_database() conn.init_database()
init_db_mock.assert_called_once_with() init_db_mock.assert_called_once_with()

View File

@ -18,7 +18,7 @@ import codecs
from collections import namedtuple from collections import namedtuple
from logging import getLogger from logging import getLogger
from logging.config import dictConfig from logging.config import dictConfig
from planetmint.backend.connection import connect from planetmint.backend.connection import Connection
from planetmint.backend.tarantool.connection import TarantoolDBConnection from planetmint.backend.tarantool.connection import TarantoolDBConnection
import pytest import pytest
@ -127,7 +127,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa
print('Initializing test db') print('Initializing test db')
dbname = Config().get()['database']['name'] dbname = Config().get()['database']['name']
conn = connect() conn = Connection().conn
_drop_db(conn, dbname) _drop_db(conn, dbname)
schema.init_database(conn, dbname) schema.init_database(conn, dbname)
@ -136,7 +136,7 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa
yield yield
print('Deleting `{}` database'.format(dbname)) print('Deleting `{}` database'.format(dbname))
conn = connect() conn = Connection().conn
_drop_db(conn, dbname) _drop_db(conn, dbname)
print('Finished deleting `{}`'.format(dbname)) print('Finished deleting `{}`'.format(dbname))
@ -148,7 +148,7 @@ def _bdb(_setup_database, _configure_planetmint):
from planetmint.models import Transaction from planetmint.models import Transaction
from .utils import flush_db from .utils import flush_db
from planetmint.config import Config from planetmint.config import Config
conn = connect() conn = Connection().conn
yield yield
dbname = Config().get()['database']['name'] dbname = Config().get()['database']['name']
flush_db(conn, dbname) flush_db(conn, dbname)
@ -389,7 +389,7 @@ def db_name(db_config):
@pytest.fixture @pytest.fixture
def db_conn(): def db_conn():
return connect() return Connection().conn
@pytest.fixture @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): def test_outputs_query_key_order(b, user_pk, user_sk, user2_pk, user2_sk):
from planetmint import backend from planetmint import backend
from planetmint.backend.connection import connect from planetmint.backend.connection import Connection
from planetmint.backend import query from planetmint.backend import query
tx1 = Create.generate([user_pk], 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 # clean the transaction, metdata and asset collection
# conn = connect() # conn = connect()
connection = connect() connection = Connection().conn
# conn.run(conn.collection('transactions').delete_many({})) # conn.run(conn.collection('transactions').delete_many({}))
# conn.run(conn.collection('metadata').delete_many({})) # conn.run(conn.collection('metadata').delete_many({}))
# conn.run(conn.collection('assets').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(): def test_bigchain_class_initialization_with_parameters():
from planetmint import Planetmint from planetmint import Planetmint
from planetmint.backend import connect from planetmint.backend import Connection
from planetmint.validation import BaseValidationRules from planetmint.validation import BaseValidationRules
init_db_kwargs = { init_db_kwargs = {
'backend': 'localmongodb', 'backend': 'localmongodb',
'host': 'this_is_the_db_host', 'host': 'this_is_the_db_host',
'port': 12345, 'port': 12345,
'name': 'this_is_the_db_name', 'name': 'this_is_the_db_name',
} }
connection = connect(**init_db_kwargs) connection = Connection(**init_db_kwargs).conn
planet = Planetmint(connection=connection) planet = Planetmint(connection=connection)
assert planet.connection == connection assert planet.connection == connection
assert planet.connection.host == init_db_kwargs['host'] assert planet.connection.host == init_db_kwargs['host']