Loading src/common/database.py +44 −57 Original line number Diff line number Diff line #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ This class is responsible for interacting with the EAGLE-I database. This class provides an abstract method of interacting with a (by default) PostgreSQL database. However, the connection paramater may be specified to open any type of connection through implementation of this abstract class. """ import traceback from abc import ABC import psycopg2 import pandas as pd from abc import ABC, abstractmethod from common.logz import create_logger from common.env import check_environment as ce Loading Loading @@ -44,6 +45,12 @@ class Database(ABC): DEFAULT_HOST = ce('DATABASE_HOST', "localhost") DEFAULT_PORT = int(ce('DATABASE_PORT', 5432)) DEFAULT_SCHEMA = ce('DATABASE_SCHEMA', 'public') DEFAULT_ENGINE = ce('DATABASE_ENGINE', 'postgresql') # define a URI string if URI is perferred to connect DEFAULT_URI = (DEFAULT_ENGINE + '://' + DEFAULT_USER + ':' + str(DEFAULT_PW) + '@' + DEFAULT_HOST + '/' + DEFAULT_DB) def __init__(self, connection_info={'dbName': DEFAULT_DB, 'dbUser': DEFAULT_USER, Loading @@ -51,21 +58,18 @@ class Database(ABC): 'dbSchema': DEFAULT_SCHEMA, 'dbHost': DEFAULT_HOST, 'dbPort': DEFAULT_PORT, 'dbTimeout': DEFAULT_TIMEOUT} ): 'dbTimeout': DEFAULT_TIMEOUT, 'dbEngine': DEFAULT_ENGINE, 'uri': DEFAULT_URI}, **kwargs): """ Create a Database object. This *does not* open a connection to the database. Use open() to establish a database connection. database. Use open() or `with` to establish a database connection. :param connection_info: a dictionary containing connection info """ self.logger = create_logger() self.dbName = connection_info['dbName'] self.user = connection_info['dbUser'] self.pw = connection_info['dbPassword'] self.schema = connection_info['dbSchema'] self.host = connection_info['dbHost'] self.port = connection_info['dbPort'] self.timeout = connection_info['dbTimeout'] self._connection = None self._cursor = None self.conection_info = connection_info self.connection = None self.cursor = None self.logger.debug('Database object successfully initialized') def __del__(self): Loading @@ -89,40 +93,9 @@ class Database(ABC): """ hande database closing when leaving with """ self.close() @abstractmethod def open(self): """ Explicitly open the database connection :note: This will send email to Database.CONNECTION_ERR_EMAIL_RECPS if unable to establish connection :note: Added as part of changes for EI-2649 :return: True if connection established, else false """ self.logger.debug('Opening Database Connection and creating Cursor') try: """ print(self.dbName + ',' + self.user + ',' + self.host + "," + str(self.port) + "," + str(self.timeout)) """ self._connection = psycopg2.connect(database=self.dbName, user=self.user, password=self.pw, host=self.host, port=self.port, connect_timeout=self.timeout) self._connection.set_client_encoding('UTF8') self.logger.debug('Successfully opened connection to database') self._cursor = self._connection.cursor() self.logger.debug('Successfully created a cursor') # TODO @Jonathan v--- not the default search paths - env var? """ ^---- need this re-explained """ self._cursor.execute("""SET search_path TO public, outage_data, utility, system_monitoring;""") self.logger.debug('Successfully set search_path') except psycopg2.OperationalError as e: # TODO @Jonathan other errors? self.logger.error(f'Database Error: {e}') return False return True pass def silent_open(self): """Open database silently without returning anything.""" Loading @@ -144,19 +117,19 @@ class Database(ABC): :return: None """ if self._cursor: self._cursor.close() self._cursor = None if self.cursor: self.cursor.close() self.cursor = None if self._connection: self._connection.commit() self._connection.close() self._connection = None if self.connection: self.connection.commit() self.connection.close() self.connection = None def is_open(self): """Determine if the database is open or not.""" # TODO @Jonathan -- code review please if self._cursor is not None and self._connection is not None: if self.cursor is not None and self._connection is not None: # Test if both connection and cursor are on return True # If both are on, return True # If one or both connection and cursor are missing, return False Loading Loading @@ -184,6 +157,20 @@ class Database(ABC): self.close() return results def override_connection(self, connection): """Override the default connection, useful for using other connections than `psycopg2` for downstream development. :param connection: any type of database connection object. """ self.connection = connection def modify_connection_info(self, variable, value): """Modify a `variable` and set it to `value` in the connection_info attribute. """ self.connection_info[variable] = value def query(self, query): """Query the database.""" # TODO @Jonathan -- add logic here to send a query to the database Loading src/common/mixins/__init__.py 0 → 100644 +3 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Provide mixins for multiple abstract classes.""" src/common/mixins/postgres.py 0 → 100644 +43 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Allow opening with a psycopg2 connection.""" import psycopg2 class Postgres_Mixin(): """Serve common connection method for postgres.""" def open(self, search_paths='public'): """ Explicitly open the database connection :note: This will send email to Database.CONNECTION_ERR_EMAIL_RECPS if unable to establish connection :return: True if connection established, else false """ self.logger.debug('Opening Database Connection and creating Cursor') try: """ print(self.dbName + ',' + self.user + ',' + self.host + "," + str(self.port) + "," + str(self.timeout)) """ self.connection = psycopg2.connect( database=self.connection_info['dbName'], user=self.connection_info['dbUser'], password=self.connection_info['dbPassword'], host=self.connection_info['dbHost'], port=self.connection_info['dbPort'], connect_timeout=self.connection_info['dbTimeout']) self.connection.set_client_encoding('UTF8') self.logger.debug('Successfully opened connection to database') self.cursor = self._connection.cursor() self.logger.debug('Successfully created a cursor') # TODO @Jonathan v--- not the default search paths - env var? """ ^---- need this re-explained """ # @TODO @Jonathan add searth_paths as a possible variable to pass # if passed as a list, split and concat into commas self.cursor.execute("""SET search_path TO public, outage_data, utility, system_monitoring;""") self.logger.debug('Successfully set search_path') except psycopg2.OperationalError as e: # TODO @Jonathan other errors? self.logger.error(f'Database Error: {e}') return False return True src/install.sh +1 −1 Original line number Diff line number Diff line #!/usr/bin/env bash python3 -m pip --no-cache-dir install common --extra-index-url https://__token__:dzK386VBsiR-iV8g2Ekc@code.ornl.gov/api/v4/projects/9965/packages/pypi/simple python3 -m pip --no-cache-dir install common --index-url https://Install:dzK386VBsiR-iV8g2Ekc@code.ornl.gov/api/v4/projects/9965/packages/pypi/simple src/setup.py +2 −2 Original line number Diff line number Diff line Loading @@ -5,10 +5,10 @@ import os from setuptools import setup # update version information here _version = '0.1.0' _version = '0.1.4' # packages _packages = ['common'] _packages = ['common', 'common.mixins'] # retrieve metadata and packages based on files _here = os.path.abspath(os.path.dirname(__file__)) Loading Loading
src/common/database.py +44 −57 Original line number Diff line number Diff line #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ This class is responsible for interacting with the EAGLE-I database. This class provides an abstract method of interacting with a (by default) PostgreSQL database. However, the connection paramater may be specified to open any type of connection through implementation of this abstract class. """ import traceback from abc import ABC import psycopg2 import pandas as pd from abc import ABC, abstractmethod from common.logz import create_logger from common.env import check_environment as ce Loading Loading @@ -44,6 +45,12 @@ class Database(ABC): DEFAULT_HOST = ce('DATABASE_HOST', "localhost") DEFAULT_PORT = int(ce('DATABASE_PORT', 5432)) DEFAULT_SCHEMA = ce('DATABASE_SCHEMA', 'public') DEFAULT_ENGINE = ce('DATABASE_ENGINE', 'postgresql') # define a URI string if URI is perferred to connect DEFAULT_URI = (DEFAULT_ENGINE + '://' + DEFAULT_USER + ':' + str(DEFAULT_PW) + '@' + DEFAULT_HOST + '/' + DEFAULT_DB) def __init__(self, connection_info={'dbName': DEFAULT_DB, 'dbUser': DEFAULT_USER, Loading @@ -51,21 +58,18 @@ class Database(ABC): 'dbSchema': DEFAULT_SCHEMA, 'dbHost': DEFAULT_HOST, 'dbPort': DEFAULT_PORT, 'dbTimeout': DEFAULT_TIMEOUT} ): 'dbTimeout': DEFAULT_TIMEOUT, 'dbEngine': DEFAULT_ENGINE, 'uri': DEFAULT_URI}, **kwargs): """ Create a Database object. This *does not* open a connection to the database. Use open() to establish a database connection. database. Use open() or `with` to establish a database connection. :param connection_info: a dictionary containing connection info """ self.logger = create_logger() self.dbName = connection_info['dbName'] self.user = connection_info['dbUser'] self.pw = connection_info['dbPassword'] self.schema = connection_info['dbSchema'] self.host = connection_info['dbHost'] self.port = connection_info['dbPort'] self.timeout = connection_info['dbTimeout'] self._connection = None self._cursor = None self.conection_info = connection_info self.connection = None self.cursor = None self.logger.debug('Database object successfully initialized') def __del__(self): Loading @@ -89,40 +93,9 @@ class Database(ABC): """ hande database closing when leaving with """ self.close() @abstractmethod def open(self): """ Explicitly open the database connection :note: This will send email to Database.CONNECTION_ERR_EMAIL_RECPS if unable to establish connection :note: Added as part of changes for EI-2649 :return: True if connection established, else false """ self.logger.debug('Opening Database Connection and creating Cursor') try: """ print(self.dbName + ',' + self.user + ',' + self.host + "," + str(self.port) + "," + str(self.timeout)) """ self._connection = psycopg2.connect(database=self.dbName, user=self.user, password=self.pw, host=self.host, port=self.port, connect_timeout=self.timeout) self._connection.set_client_encoding('UTF8') self.logger.debug('Successfully opened connection to database') self._cursor = self._connection.cursor() self.logger.debug('Successfully created a cursor') # TODO @Jonathan v--- not the default search paths - env var? """ ^---- need this re-explained """ self._cursor.execute("""SET search_path TO public, outage_data, utility, system_monitoring;""") self.logger.debug('Successfully set search_path') except psycopg2.OperationalError as e: # TODO @Jonathan other errors? self.logger.error(f'Database Error: {e}') return False return True pass def silent_open(self): """Open database silently without returning anything.""" Loading @@ -144,19 +117,19 @@ class Database(ABC): :return: None """ if self._cursor: self._cursor.close() self._cursor = None if self.cursor: self.cursor.close() self.cursor = None if self._connection: self._connection.commit() self._connection.close() self._connection = None if self.connection: self.connection.commit() self.connection.close() self.connection = None def is_open(self): """Determine if the database is open or not.""" # TODO @Jonathan -- code review please if self._cursor is not None and self._connection is not None: if self.cursor is not None and self._connection is not None: # Test if both connection and cursor are on return True # If both are on, return True # If one or both connection and cursor are missing, return False Loading Loading @@ -184,6 +157,20 @@ class Database(ABC): self.close() return results def override_connection(self, connection): """Override the default connection, useful for using other connections than `psycopg2` for downstream development. :param connection: any type of database connection object. """ self.connection = connection def modify_connection_info(self, variable, value): """Modify a `variable` and set it to `value` in the connection_info attribute. """ self.connection_info[variable] = value def query(self, query): """Query the database.""" # TODO @Jonathan -- add logic here to send a query to the database Loading
src/common/mixins/__init__.py 0 → 100644 +3 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Provide mixins for multiple abstract classes."""
src/common/mixins/postgres.py 0 → 100644 +43 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Allow opening with a psycopg2 connection.""" import psycopg2 class Postgres_Mixin(): """Serve common connection method for postgres.""" def open(self, search_paths='public'): """ Explicitly open the database connection :note: This will send email to Database.CONNECTION_ERR_EMAIL_RECPS if unable to establish connection :return: True if connection established, else false """ self.logger.debug('Opening Database Connection and creating Cursor') try: """ print(self.dbName + ',' + self.user + ',' + self.host + "," + str(self.port) + "," + str(self.timeout)) """ self.connection = psycopg2.connect( database=self.connection_info['dbName'], user=self.connection_info['dbUser'], password=self.connection_info['dbPassword'], host=self.connection_info['dbHost'], port=self.connection_info['dbPort'], connect_timeout=self.connection_info['dbTimeout']) self.connection.set_client_encoding('UTF8') self.logger.debug('Successfully opened connection to database') self.cursor = self._connection.cursor() self.logger.debug('Successfully created a cursor') # TODO @Jonathan v--- not the default search paths - env var? """ ^---- need this re-explained """ # @TODO @Jonathan add searth_paths as a possible variable to pass # if passed as a list, split and concat into commas self.cursor.execute("""SET search_path TO public, outage_data, utility, system_monitoring;""") self.logger.debug('Successfully set search_path') except psycopg2.OperationalError as e: # TODO @Jonathan other errors? self.logger.error(f'Database Error: {e}') return False return True
src/install.sh +1 −1 Original line number Diff line number Diff line #!/usr/bin/env bash python3 -m pip --no-cache-dir install common --extra-index-url https://__token__:dzK386VBsiR-iV8g2Ekc@code.ornl.gov/api/v4/projects/9965/packages/pypi/simple python3 -m pip --no-cache-dir install common --index-url https://Install:dzK386VBsiR-iV8g2Ekc@code.ornl.gov/api/v4/projects/9965/packages/pypi/simple
src/setup.py +2 −2 Original line number Diff line number Diff line Loading @@ -5,10 +5,10 @@ import os from setuptools import setup # update version information here _version = '0.1.0' _version = '0.1.4' # packages _packages = ['common'] _packages = ['common', 'common.mixins'] # retrieve metadata and packages based on files _here = os.path.abspath(os.path.dirname(__file__)) Loading