Example #1
0
 def setUp(self):
     self.logger = SEKLogger(__name__, 'DEBUG')
     self.configer = SIConfiger()
     self.conn = SEKDBConnector(
         dbName=self.configer.configOptionValue('Database', 'db_name'),
         dbHost=self.configer.configOptionValue('Database', 'db_host'),
         dbPort=self.configer.configOptionValue('Database', 'db_port'),
         dbUsername=self.configer.configOptionValue('Database',
                                                    'db_username'),
         dbPassword=self.configer.configOptionValue(
             'Database', 'db_password')).connectDB()
     self.cursor = self.conn.cursor()
     self.dbUtil = SEKDBUtil()
     self.dataUtil = SIDataUtil()
     self.inserter = SingleFileLoader('data/test-meter/log.csv')
     self.data = '"2014-07-12 16:22:30",0,,,1187488464896.00,' \
                 '2322185846784.00,1134697381888.00,35184644096.00,' \
                 '290857353216.00,10133100822528.00,367.13,' \
                 '-17660932096.00,1078.01,17660934144.00,-7.86,1.80,8.06,' \
                 '-0.97,244.01,122.00,32.93,60.01,-7.09,1.42,7.24,8.06,' \
                 '3.34,8.35,-40.18,-5.68,40.52,516.72,403.12,0,' \
                 '8797179904.00,47518.67,0,86.03,50.23,4198.40,' \
                 '281475022848.00,2251868602368.00,0,6820.01,' \
                 '8796095488.00,0,178.83,188.30,0,620.07,505.19,' \
                 '288230389841920.02,12668.18,68729384.00,0,-3.68,-4.18,,' \
                 '1.00,0.79,,3.81,4.25,,-0.97,-0.98,,244.01,,,121.54,' \
                 '122.46,,31.28,34.59,'
     self.testMeterName = 'test-meter'
    def __init__(self):
        """
        Constructor.
        """

        self._config = ConfigParser.ConfigParser()
        self.logger = SEKLogger(__name__, 'INFO')
        self.fileUtil = SEKFileUtil()
        self.dbUtil = SEKDBUtil()
        self.cursor = None

        configFilePath = '~/.smart-inverter.cfg'

        if self.fileUtil.isMoreThanOwnerReadableAndWritable(
                os.path.expanduser(configFilePath)):
            self.logger.log(
                "Configuration file permissions are too permissive. Operation "
                "will not continue.", 'error')
            sys.exit(-1)

        try:
            self._config.read(['site.cfg', os.path.expanduser(configFilePath)])
        except:
            self.logger.log(
                "Critical error: The data in {} cannot be "
                "accessed successfully.".format(configFilePath), 'ERROR')
            sys.exit(-1)
 def setUp(self):
     self.logger = SEKLogger(__name__,'DEBUG')
     self.configer = SIConfiger()
     self.conn = SEKDBConnector(
         dbName = self.configer.configOptionValue('Database', 'db_name'),
         dbHost = self.configer.configOptionValue('Database', 'db_host'),
         dbPort = self.configer.configOptionValue('Database', 'db_port'),
         dbUsername = self.configer.configOptionValue('Database',
                                                      'db_username'),
         dbPassword = self.configer.configOptionValue('Database',
                                                      'db_password')).connectDB()
     self.cursor = self.conn.cursor()
     self.dbUtil = SEKDBUtil()
     self.dataUtil = SIDataUtil()
     self.inserter = SingleFileLoader('data/test-meter/log.csv')
     self.data = '"2014-07-12 16:22:30",0,,,1187488464896.00,' \
                 '2322185846784.00,1134697381888.00,35184644096.00,' \
                 '290857353216.00,10133100822528.00,367.13,' \
                 '-17660932096.00,1078.01,17660934144.00,-7.86,1.80,8.06,' \
                 '-0.97,244.01,122.00,32.93,60.01,-7.09,1.42,7.24,8.06,' \
                 '3.34,8.35,-40.18,-5.68,40.52,516.72,403.12,0,' \
                 '8797179904.00,47518.67,0,86.03,50.23,4198.40,' \
                 '281475022848.00,2251868602368.00,0,6820.01,' \
                 '8796095488.00,0,178.83,188.30,0,620.07,505.19,' \
                 '288230389841920.02,12668.18,68729384.00,0,-3.68,-4.18,,' \
                 '1.00,0.79,,3.81,4.25,,-0.97,-0.98,,244.01,,,121.54,' \
                 '122.46,,31.28,34.59,'
     self.testMeterName = 'test-meter'
    def __init__(self, filepath=''):
        """
        Constructor.

        :param testing: Flag indicating if testing mode is on.
        """

        self.logger = SEKLogger(__name__, DEBUG)
        self.configer = SIConfiger()
        self.dbUtil = SEKDBUtil()
        self.dataUtil = SIDataUtil()
        self.logger.log('making new db conn for filepath {}'.format(filepath),
                        SILENT)
        sys.stdout.flush()

        try:
            self.conn = SEKDBConnector(
                dbName=self.configer.configOptionValue('Database', 'db_name'),
                dbHost=self.configer.configOptionValue('Database', 'db_host'),
                dbPort=self.configer.configOptionValue('Database', 'db_port'),
                dbUsername=self.configer.configOptionValue(
                    'Database', 'db_username'),
                dbPassword=self.configer.configOptionValue(
                    'Database', 'db_password')).connectDB()
        except:
            raise Exception("Unable to get DB connection.")
        self.cursor = self.conn.cursor()
        self.exitOnError = False

        # An empty file path is used during creating of meter table entries.
        if filepath == '':
            self.filepath = None
            self.meterID = None
            self.meterDataTable = None
        else:
            self.filepath = filepath
            self.meterID = self.getOrMakeMeterID(self.meterName())
            assert self.meterID is not None
            self.meterDataTable = "MeterData_{}".format(self.meterName())
            # @todo Test existence of meter data table.
        self.timestampColumn = 0  # timestamp col in the raw data
        self.exceptionCount = 0
    def __init__(self, filepath = ''):
        """
        Constructor.

        :param testing: Flag indicating if testing mode is on.
        """

        self.logger = SEKLogger(__name__, DEBUG)
        self.configer = SIConfiger()
        self.dbUtil = SEKDBUtil()
        self.dataUtil = SIDataUtil()
        self.logger.log('making new db conn for filepath {}'.format(filepath), SILENT)
        sys.stdout.flush()

        try:
            self.conn = SEKDBConnector(
                dbName = self.configer.configOptionValue('Database', 'db_name'),
                dbHost = self.configer.configOptionValue('Database', 'db_host'),
                dbPort = self.configer.configOptionValue('Database', 'db_port'),
                dbUsername = self.configer.configOptionValue('Database',
                                                             'db_username'),
                dbPassword = self.configer.configOptionValue('Database',
                                                             'db_password')).connectDB()
        except:
            raise Exception("Unable to get DB connection.")
        self.cursor = self.conn.cursor()
        self.exitOnError = False

        # An empty file path is used during creating of meter table entries.
        if filepath == '':
            self.filepath = None
            self.meterID = None
            self.meterDataTable = None
        else:
            self.filepath = filepath
            self.meterID = self.getOrMakeMeterID(self.meterName())
            assert self.meterID is not None
            self.meterDataTable = "MeterData_{}".format(self.meterName())
            # @todo Test existence of meter data table.
        self.timestampColumn = 0 # timestamp col in the raw data
        self.exceptionCount = 0
    global COMMAND_LINE_ARGS
    parser = argparse.ArgumentParser(
        description = 'Perform insertion of data contained in multiple files '
                      'to the SI database.')
    parser.add_argument('--basepath', required = True,
                        help = 'A base path from which to process data files.')
    COMMAND_LINE_ARGS = parser.parse_args()


if __name__ == '__main__':
    processCommandLineArguments()
    tableBase = "MeterData"
    pkey = 'meter_id, time_utc'
    logger = SEKLogger(__name__, 'debug')
    configer = SIConfiger()
    dbUtil = SEKDBUtil()
    conn = SEKDBConnector(
        dbName = configer.configOptionValue('Database', 'db_name'),
        dbHost = configer.configOptionValue('Database', 'db_host'),
        dbPort = configer.configOptionValue('Database', 'db_port'),
        dbUsername = configer.configOptionValue('Database', 'db_username'),
        dbPassword = configer.configOptionValue('Database',
                                                'db_password')).connectDB()
    cursor = conn.cursor()

    tableOwner = configer.configOptionValue('Database', 'table_owner')
    for meterName in SIUtil().meters(basepath = COMMAND_LINE_ARGS.basepath):
        logger.log('creating table {}'.format(tableBase + "_" + meterName))
        sql = 'CREATE TABLE "{1}_{0}" ( CHECK ( meter_id = meter_id(\'{' \
              '0}\'))) INHERITS ("{1}"); ALTER TABLE ONLY "{1}_{0}" ADD ' \
              'CONSTRAINT "{1}_{0}_pkey" PRIMARY KEY ({3}); ALTER TABLE ONLY ' \
Example #7
0
class SingleFileLoaderTester(unittest.TestCase):
    def setUp(self):
        self.logger = SEKLogger(__name__, 'DEBUG')
        self.configer = SIConfiger()
        self.conn = SEKDBConnector(
            dbName=self.configer.configOptionValue('Database', 'db_name'),
            dbHost=self.configer.configOptionValue('Database', 'db_host'),
            dbPort=self.configer.configOptionValue('Database', 'db_port'),
            dbUsername=self.configer.configOptionValue('Database',
                                                       'db_username'),
            dbPassword=self.configer.configOptionValue(
                'Database', 'db_password')).connectDB()
        self.cursor = self.conn.cursor()
        self.dbUtil = SEKDBUtil()
        self.dataUtil = SIDataUtil()
        self.inserter = SingleFileLoader('data/test-meter/log.csv')
        self.data = '"2014-07-12 16:22:30",0,,,1187488464896.00,' \
                    '2322185846784.00,1134697381888.00,35184644096.00,' \
                    '290857353216.00,10133100822528.00,367.13,' \
                    '-17660932096.00,1078.01,17660934144.00,-7.86,1.80,8.06,' \
                    '-0.97,244.01,122.00,32.93,60.01,-7.09,1.42,7.24,8.06,' \
                    '3.34,8.35,-40.18,-5.68,40.52,516.72,403.12,0,' \
                    '8797179904.00,47518.67,0,86.03,50.23,4198.40,' \
                    '281475022848.00,2251868602368.00,0,6820.01,' \
                    '8796095488.00,0,178.83,188.30,0,620.07,505.19,' \
                    '288230389841920.02,12668.18,68729384.00,0,-3.68,-4.18,,' \
                    '1.00,0.79,,3.81,4.25,,-0.97,-0.98,,244.01,,,121.54,' \
                    '122.46,,31.28,34.59,'
        self.testMeterName = 'test-meter'

    def test_columns(self):
        self.assertEquals(len(self.dataUtil.dbColumns), NUM_DB_COLS)

    def test_insert_data(self):
        self.logger.log('testing data insert')
        self.assertTrue(self.inserter.insertData(self.data))
        self.conn.commit()

    def test_sql_formatted_values(self):
        self.logger.log('data: {}'.format(
            self.dataUtil.sqlFormattedValues(self.data)))

    def test_meter_id(self):
        self.logger.log('testing meter id')
        meter_id = self.inserter.meterID(self.testMeterName)
        self.logger.log('meter id {}'.format(meter_id))
        self.assertTrue(isinstance(meter_id, (int, long)))
        self.logger.log('getting meter id')
        sql = 'SELECT meter_id FROM "Meters" WHERE meter_name = \'{}\''.format(
            self.testMeterName)
        success = self.dbUtil.executeSQL(self.cursor, sql, exitOnFail=True)
        if success:
            result = self.cursor.fetchall()
            self.assertEquals(1, len(result))
        else:
            self.logger.log('failed to retrieve meter id', 'error')

    def test_meter_name(self):
        """
        Test getting the meter name.
        :return:
        """
        self.assertEquals(self.inserter.meterName(), self.testMeterName)

    def test_insert_data_from_file(self):
        self.inserter.insertDataFromFile()
        sql = 'SELECT * FROM "MeterData" WHERE meter_id = {}'.format(
            self.inserter.meterID(self.testMeterName))
        success = self.dbUtil.executeSQL(self.cursor, sql, exitOnFail=True)
        if success:
            result = self.cursor.fetchall()
            self.assertEquals(len(result), 10)

        self.assertTrue(success)

    def tearDown(self):
        self.logger.log('teardown', 'debug')
        sql = 'SELECT meter_id FROM "Meters" WHERE meter_name = \'{}\''.format(
            self.testMeterName)
        success = self.dbUtil.executeSQL(self.cursor, sql, exitOnFail=True)
        if success:
            result = self.cursor.fetchall()
            if len(result) == 1:
                sql = 'DELETE FROM "Meters" WHERE meter_id = {}'.format(
                    result[0][0])
                success = self.dbUtil.executeSQL(self.cursor,
                                                 sql,
                                                 exitOnFail=True)
                if success:
                    self.conn.commit()
                sql = 'SELECT meter_id FROM "Meters" WHERE meter_name = \'{' \
                      '}\''.format(self.testMeterName)
                success = self.dbUtil.executeSQL(self.cursor,
                                                 sql,
                                                 exitOnFail=True)
                result = self.cursor.fetchall()
                self.assertEquals(0, len(result))
class SingleFileLoader(object):
    """
    Perform insertion of data contained in a single file to the Smart Inverter database
    specified in the configuration file.
    """
    def __init__(self, filepath=''):
        """
        Constructor.

        :param testing: Flag indicating if testing mode is on.
        """

        self.logger = SEKLogger(__name__, DEBUG)
        self.configer = SIConfiger()
        self.dbUtil = SEKDBUtil()
        self.dataUtil = SIDataUtil()
        self.logger.log('making new db conn for filepath {}'.format(filepath),
                        SILENT)
        sys.stdout.flush()

        try:
            self.conn = SEKDBConnector(
                dbName=self.configer.configOptionValue('Database', 'db_name'),
                dbHost=self.configer.configOptionValue('Database', 'db_host'),
                dbPort=self.configer.configOptionValue('Database', 'db_port'),
                dbUsername=self.configer.configOptionValue(
                    'Database', 'db_username'),
                dbPassword=self.configer.configOptionValue(
                    'Database', 'db_password')).connectDB()
        except:
            raise Exception("Unable to get DB connection.")
        self.cursor = self.conn.cursor()
        self.exitOnError = False

        # An empty file path is used during creating of meter table entries.
        if filepath == '':
            self.filepath = None
            self.meterID = None
            self.meterDataTable = None
        else:
            self.filepath = filepath
            self.meterID = self.getOrMakeMeterID(self.meterName())
            assert self.meterID is not None
            self.meterDataTable = "MeterData_{}".format(self.meterName())
            # @todo Test existence of meter data table.
        self.timestampColumn = 0  # timestamp col in the raw data
        self.exceptionCount = 0

    def newDataForMeterExists(self):
        """
        :return: Boolean true if file has new data.
        """

        try:
            if (self.dataUtil.maxTimeStamp(self.filepath) >
                    self.dataUtil.maxTimeStampDB(self.meterName())):
                return True
            return False
        except TypeError as detail:
            # @todo Log the cause of the exception.
            self.logger.log('Exception: {}'.format(detail), CRITICAL)
            self.exceptionCount += 1
            return False

    def insertDataFromFile(self):
        """
        Process input file as a stream from the object attribute's filepath.
        :return: (Int, Int) Tuple of Int count of inserted records or None on
        error and Int count of exceptions encountered.
        """

        insertCnt = 0
        with open(self.filepath) as dataFile:
            lineCnt = 1
            result = False

            # @todo handle io errors
            self.logger.log('loading data from {}'.format(dataFile), DEBUG)
            for line in dataFile:
                result = self.insertData(
                    line.rstrip('\n')) if lineCnt != 1 else False
                if result is None:
                    self.logger.log('Critical insert failure', CRITICAL)
                    raise Exception('Insert did not complete')
                    # self.logger.log('insert did not complete', ERROR)
                    # return None
                if insertCnt > 0 and insertCnt % COMMIT_INTERVAL == 0:
                    self.conn.commit()
                    self.logger.log('committing at {}'.format(insertCnt),
                                    DEBUG)
                    sys.stdout.flush()
                if result:
                    insertCnt += 1
                lineCnt += 1
            self.conn.commit()
            self.logger.log('final commit at {}'.format(insertCnt), DEBUG)
        return (insertCnt, self.exceptionCount)

    def insertData(self, values, commitOnEvery=False):
        """
        Insert a row of data to the database.
        :param values: String of raw values from the source CSV files.
        :return: Boolean indicating success or failure.
        """

        if not values or self.dataUtil.badData(values):
            return False

        if self.removeDupe(values):
            self.logger.log('duplicate found', DEBUG)

        sql = 'INSERT INTO "{0}" ({1}) VALUES({2}, {3})'.format(
            self.meterDataTable,
            ','.join("\"" + c + "\"" for c in self.dataUtil.dbColumns),
            self.meterID, self.dataUtil.sqlFormattedValues(values))
        self.logger.log('sql: {}'.format(sql), DEBUG)

        if self.dbUtil.executeSQL(self.cursor,
                                  sql,
                                  exitOnFail=self.exitOnError):
            if commitOnEvery:
                self.conn.commit()
            return True
        else:
            return False

    def removeDupe(self, values):
        def deleteDupe(myMeterID, myTimeUTC):
            sql = 'DELETE FROM "{0}" WHERE meter_id = {1} AND time_utc = {' \
                  '2}'.format(self.meterDataTable, myMeterID, myTimeUTC)
            if self.dbUtil.executeSQL(self.cursor,
                                      sql,
                                      exitOnFail=self.exitOnError):
                return True
            else:
                return False

        if not values:
            return False

        timeUTC = self.timeUTC(values)

        # This is dependendent on the quote style used for time UTC in the
        # raw data.
        sql = 'SELECT time_utc FROM "{0}" WHERE meter_id = {1} AND time_utc = ' \
              '{2}'.format(
            self.meterDataTable, self.meterID, timeUTC)

        if self.dbUtil.executeSQL(self.cursor,
                                  sql,
                                  exitOnFail=self.exitOnError):
            rows = self.cursor.fetchone()

            if rows and len(rows) == 1:
                if deleteDupe(self.meterID, timeUTC):
                    return True
                else:
                    raise Exception(
                        "Unable to remove dupe for meter ID {}, time UTC {}".
                        format(self.meterID, timeUTC))
        return False

    def timeUTC(self, values):
        def makeSingleQuotes(x):
            return str(x).replace('"', "'")

        return makeSingleQuotes(values.split(',')[self.timestampColumn])

    def meterName(self):
        """
        The meter name is the name of the containing folder.
        :return:
        """

        # @todo validate meter name
        def validMeterName(name):
            pass

        return os.path.basename(os.path.dirname(self.filepath))

    def getOrMakeMeterID(self, meterName):
        """
        Given a meter name, return its meter ID.
        If the meter name has no ID, create a new one and return its ID.
        :param meterName: String
        :return: Int of meter ID
        """
        def __meterID(name):
            """
            :param name: String of meter name
            :return: Int or None
            """
            sql = 'SELECT meter_id FROM "Meters" WHERE meter_name = \'{' \
                  '}\''.format(name)
            success = self.dbUtil.executeSQL(self.cursor,
                                             sql,
                                             exitOnFail=False)
            if success:
                result = self.cursor.fetchall()
                assert len(result) == 1 or len(result) == 0
                if result:
                    return int(result[0][0])
                else:
                    return None
            else:
                return None

        def __makeNewMeter(name):
            """
            :param name: String of meter name
            :return: Int or None
            """
            id = __meterID(name)
            if id:
                return id

            self.logger.log('making new meter', DEBUG)
            sql = 'INSERT INTO "Meters" (meter_name) VALUES (\'{}\')'.format(
                name)
            success = self.dbUtil.executeSQL(self.cursor,
                                             sql,
                                             exitOnFail=False)
            self.conn.commit()
            if success:
                sql = 'SELECT CURRVAL(\'meter_id_seq\')'
                success = self.dbUtil.executeSQL(self.cursor,
                                                 sql,
                                                 exitOnFail=False)
                if success:
                    return int(self.cursor.fetchall()[0][0])
            else:
                return None

        id = __meterID(meterName)

        # Python 3: if isinstance( id, int ):
        if isinstance(id, (int, long)):
            return int(id)
        else:
            return __makeNewMeter(meterName)

    def __del__(self):
        self.logger.log('Destroying single file inserter', DEBUG)
        self.conn.close()
class SingleFileLoader(object):
    """
    Perform insertion of data contained in a single file to the Smart Inverter database
    specified in the configuration file.
    """

    def __init__(self, filepath = ''):
        """
        Constructor.

        :param testing: Flag indicating if testing mode is on.
        """

        self.logger = SEKLogger(__name__, DEBUG)
        self.configer = SIConfiger()
        self.dbUtil = SEKDBUtil()
        self.dataUtil = SIDataUtil()
        self.logger.log('making new db conn for filepath {}'.format(filepath), SILENT)
        sys.stdout.flush()

        try:
            self.conn = SEKDBConnector(
                dbName = self.configer.configOptionValue('Database', 'db_name'),
                dbHost = self.configer.configOptionValue('Database', 'db_host'),
                dbPort = self.configer.configOptionValue('Database', 'db_port'),
                dbUsername = self.configer.configOptionValue('Database',
                                                             'db_username'),
                dbPassword = self.configer.configOptionValue('Database',
                                                             'db_password')).connectDB()
        except:
            raise Exception("Unable to get DB connection.")
        self.cursor = self.conn.cursor()
        self.exitOnError = False

        # An empty file path is used during creating of meter table entries.
        if filepath == '':
            self.filepath = None
            self.meterID = None
            self.meterDataTable = None
        else:
            self.filepath = filepath
            self.meterID = self.getOrMakeMeterID(self.meterName())
            assert self.meterID is not None
            self.meterDataTable = "MeterData_{}".format(self.meterName())
            # @todo Test existence of meter data table.
        self.timestampColumn = 0 # timestamp col in the raw data
        self.exceptionCount = 0


    def newDataForMeterExists(self):
        """
        :return: Boolean true if file has new data.
        """

        try:
            if (self.dataUtil.maxTimeStamp(
                    self.filepath) > self.dataUtil.maxTimeStampDB(
                    self.meterName())):
                return True
            return False
        except TypeError as detail:
            # @todo Log the cause of the exception.
            self.logger.log('Exception: {}'.format(detail), CRITICAL)
            self.exceptionCount += 1
            return False


    def insertDataFromFile(self):
        """
        Process input file as a stream from the object attribute's filepath.
        :return: (Int, Int) Tuple of Int count of inserted records or None on
        error and Int count of exceptions encountered.
        """

        insertCnt = 0
        with open(self.filepath) as dataFile:
            lineCnt = 1
            result = False

            # @todo handle io errors
            self.logger.log('loading data from {}'.format(dataFile), DEBUG)
            for line in dataFile:
                result = self.insertData(
                    line.rstrip('\n')) if lineCnt != 1 else False
                if result is None:
                    self.logger.log('Critical insert failure', CRITICAL)
                    raise Exception('Insert did not complete')
                    # self.logger.log('insert did not complete', ERROR)
                    # return None
                if insertCnt > 0 and insertCnt % COMMIT_INTERVAL == 0:
                    self.conn.commit()
                    self.logger.log('committing at {}'.format(insertCnt), DEBUG)
                    sys.stdout.flush()
                if result:
                    insertCnt += 1
                lineCnt += 1
            self.conn.commit()
            self.logger.log('final commit at {}'.format(insertCnt), DEBUG)
        return (insertCnt, self.exceptionCount)


    def insertData(self, values, commitOnEvery = False):
        """
        Insert a row of data to the database.
        :param values: String of raw values from the source CSV files.
        :return: Boolean indicating success or failure.
        """

        if not values or self.dataUtil.badData(values):
            return False

        if self.removeDupe(values):
            self.logger.log('duplicate found', DEBUG)

        sql = 'INSERT INTO "{0}" ({1}) VALUES({2}, {3})'.format(
            self.meterDataTable,
            ','.join("\"" + c + "\"" for c in self.dataUtil.dbColumns),
            self.meterID, self.dataUtil.sqlFormattedValues(values))
        self.logger.log('sql: {}'.format(sql), DEBUG)

        if self.dbUtil.executeSQL(self.cursor, sql,
                                  exitOnFail = self.exitOnError):
            if commitOnEvery:
                self.conn.commit()
            return True
        else:
            return False


    def removeDupe(self, values):

        def deleteDupe(myMeterID, myTimeUTC):
            sql = 'DELETE FROM "{0}" WHERE meter_id = {1} AND time_utc = {' \
                  '2}'.format(self.meterDataTable, myMeterID, myTimeUTC)
            if self.dbUtil.executeSQL(self.cursor, sql,
                                  exitOnFail = self.exitOnError):
                return True
            else:
                return False


        if not values:
            return False

        timeUTC = self.timeUTC(values)

        # This is dependendent on the quote style used for time UTC in the
        # raw data.
        sql = 'SELECT time_utc FROM "{0}" WHERE meter_id = {1} AND time_utc = ' \
              '{2}'.format(
            self.meterDataTable, self.meterID, timeUTC)

        if self.dbUtil.executeSQL(self.cursor, sql,
                                  exitOnFail = self.exitOnError):
            rows = self.cursor.fetchone()

            if rows and len(rows) == 1:
                if deleteDupe(self.meterID, timeUTC):
                    return True
                else:
                    raise Exception(
                        "Unable to remove dupe for meter ID {}, time UTC {}".format(
                            self.meterID, timeUTC))
        return False


    def timeUTC(self, values):
        def makeSingleQuotes(x):
            return str(x).replace('"', "'")


        return makeSingleQuotes(values.split(',')[self.timestampColumn])


    def meterName(self):
        """
        The meter name is the name of the containing folder.
        :return:
        """
        # @todo validate meter name
        def validMeterName(name):
            pass


        return os.path.basename(os.path.dirname(self.filepath))


    def getOrMakeMeterID(self, meterName):
        """
        Given a meter name, return its meter ID.
        If the meter name has no ID, create a new one and return its ID.
        :param meterName: String
        :return: Int of meter ID
        """


        def __meterID(name):
            """
            :param name: String of meter name
            :return: Int or None
            """
            sql = 'SELECT meter_id FROM "Meters" WHERE meter_name = \'{' \
                  '}\''.format(name)
            success = self.dbUtil.executeSQL(self.cursor, sql,
                                             exitOnFail = False)
            if success:
                result = self.cursor.fetchall()
                assert len(result) == 1 or len(result) == 0
                if result:
                    return int(result[0][0])
                else:
                    return None
            else:
                return None


        def __makeNewMeter(name):
            """
            :param name: String of meter name
            :return: Int or None
            """
            id = __meterID(name)
            if id:
                return id

            self.logger.log('making new meter', DEBUG)
            sql = 'INSERT INTO "Meters" (meter_name) VALUES (\'{}\')'.format(
                name)
            success = self.dbUtil.executeSQL(self.cursor, sql,
                                             exitOnFail = False)
            self.conn.commit()
            if success:
                sql = 'SELECT CURRVAL(\'meter_id_seq\')'
                success = self.dbUtil.executeSQL(self.cursor, sql,
                                                 exitOnFail = False)
                if success:
                    return int(self.cursor.fetchall()[0][0])
            else:
                return None


        id = __meterID(meterName)

        # Python 3: if isinstance( id, int ):
        if isinstance(id, ( int, long )):
            return int(id)
        else:
            return __makeNewMeter(meterName)


    def __del__(self):
        self.logger.log('Destroying single file inserter', DEBUG)
        self.conn.close()
Example #10
0
    def __init__(self):
        self.logger = SEKLogger(__name__, DEBUG)
        self.configer = SIConfiger()
        self.dbUtil = SEKDBUtil()
        try:
            self.conn = SEKDBConnector(
                dbName = self.configer.configOptionValue('Database', 'db_name'),
                dbHost = self.configer.configOptionValue('Database', 'db_host'),
                dbPort = self.configer.configOptionValue('Database', 'db_port'),
                dbUsername = self.configer.configOptionValue('Database',
                                                             'db_username'),
                dbPassword = self.configer.configOptionValue('Database',
                                                             'db_password')).connectDB()
        except:
            raise Exception("Unable to get DB connection.")
        self.cursor = self.conn.cursor()
        self.exitOnError = False

        self.dbColumns = [
            "meter_id", "time_utc", "error", "lowalarm", "highalarm",
            "Accumulated Real Energy Net (kWh)",
            "Real Energy Quadrants 1 & 4, Import (kWh)",
            "Real Energy Quadrants 2 & 3, Export (kWh)",
            "Reactive Energy Quadrant 1 (VARh)",
            "Reactive Energy Quadrant 2 (VARh)",
            "Reactive Energy Quadrant 3 (VARh)",
            "Reactive Energy Quadrant 4 (VARh)", "Apparent Energy Net (VAh)",
            "Apparent Energy Quadrants 1 & 4 (VAh)",
            "Apparent Energy Quadrants 2 & 3 (VAh)",
            "Total Net Instantaneous Real Power (kW)",
            "Total Net Instantaneous Reactive Power (kVAR)",
            "Total Net Instantaneous Apparent Power (kVA)",
            "Total Power Factor", "Voltage, L-L, 3p Ave (Volts)",
            "Voltage, L-N, 3p Ave (Volts)", "Current, 3p Ave (Amps)",
            "Frequency (Hz)", "Total Real Power Present Demand (kW)",
            "Total Reactive Power Present Demand (kVAR)",
            "Total Apparent Power Present Demand (kVA)",
            "Total Real Power Max Demand, Import (kW)",
            "Total Reactive Power Max Demand, Import (kVAR)",
            "Total Apparent Power Max Demand, Import (kVA)",
            "Total Real Power Max Demand, Export (kW)",
            "Total Reactive Power Max Demand, Export (kVAR)",
            "Total Apparent Power Max Demand, Export (kVA)",
            "Accumulated Real Energy, Phase A, Import (kW)",
            "Accumulated Real Energy, Phase B, Import (kW)",
            "Accumulated Real Energy, Phase C, Import (kW)",
            "Accumulated Real Energy, Phase A, Export (kW)",
            "Accumulated Real Energy, Phase B, Export (kW)",
            "Accumulated Real Energy, Phase C, Export (kW)",
            "Accumulated Q1 Reactive Energy, Phase A, Import (VARh)",
            "Accumulated Q1 Reactive Energy, Phase B, Import (VARh)",
            "Accumulated Q1 Reactive Energy, Phase C, Import (VARh)",
            "Accumulated Q2 Reactive Energy, Phase A, Import (VARh)",
            "Accumulated Q2 Reactive Energy, Phase B, Import (VARh)",
            "Accumulated Q2 Reactive Energy, Phase C, Import (VARh)",
            "Accumulated Q3 Reactive Energy, Phase A, Export (VARh)",
            "Accumulated Q3 Reactive Energy, Phase B, Export (VARh)",
            "Accumulated Q3 Reactive Energy, Phase C, Export (VARh)",
            "Accumulated Q4 Reactive Energy, Phase A, Export (VARh)",
            "Accumulated Q4 Reactive Energy, Phase B, Export (VARh)",
            "Accumulated Q4 Reactive Energy, Phase C, Export (VARh)",
            "Accumulated Apparent Energy, Phase A, Import (VAh)",
            "Accumulated Apparent Energy, Phase B, Import (VAh)",
            "Accumulated Apparent Energy, Phase C, Import (VAh)",
            "Accumulated Apparent Energy, Phase A, Export (VAh)",
            "Accumulated Apparent Energy, Phase B, Export (VAh)",
            "Accumulated Apparent Energy, Phase C, Export (VAh)",
            "Real Power, Phase A (kW)", "Real Power, Phase B (kW)",
            "Real Power, Phase C (kW)", "Reactive Power, Phase A (kVAR)",
            "Reactive Power, Phase B (kVAR)", "Reactive Power, Phase C (kVAR)",
            "Apparent Power, Phase A (kVA)", "Apparent Power, Phase B (kVA)",
            "Apparent Power, Phase C (kVA)", "Power Factor, Phase A",
            "Power Factor, Phase B", "Power Factor, Phase C",
            "Voltage, Phase A-B (Volts)", "Voltage, Phase B-C (Volts)",
            "Voltage, Phase A-C (Volts)", "Voltage, Phase A-N (Volts)",
            "Voltage, Phase B-N (Volts)", "Voltage, Phase C-N (Volts)",
            "Current, Phase A (Amps)", "Current, Phase B (Amps)",
            "Current, Phase C (Amps)"
        ]
Example #11
0
class SIDataUtil(object):
    """
    Utility methods for Smart Inverter data including the full list of SI
    data columns.
    """


    def __init__(self):
        self.logger = SEKLogger(__name__, DEBUG)
        self.configer = SIConfiger()
        self.dbUtil = SEKDBUtil()
        try:
            self.conn = SEKDBConnector(
                dbName = self.configer.configOptionValue('Database', 'db_name'),
                dbHost = self.configer.configOptionValue('Database', 'db_host'),
                dbPort = self.configer.configOptionValue('Database', 'db_port'),
                dbUsername = self.configer.configOptionValue('Database',
                                                             'db_username'),
                dbPassword = self.configer.configOptionValue('Database',
                                                             'db_password')).connectDB()
        except:
            raise Exception("Unable to get DB connection.")
        self.cursor = self.conn.cursor()
        self.exitOnError = False

        self.dbColumns = [
            "meter_id", "time_utc", "error", "lowalarm", "highalarm",
            "Accumulated Real Energy Net (kWh)",
            "Real Energy Quadrants 1 & 4, Import (kWh)",
            "Real Energy Quadrants 2 & 3, Export (kWh)",
            "Reactive Energy Quadrant 1 (VARh)",
            "Reactive Energy Quadrant 2 (VARh)",
            "Reactive Energy Quadrant 3 (VARh)",
            "Reactive Energy Quadrant 4 (VARh)", "Apparent Energy Net (VAh)",
            "Apparent Energy Quadrants 1 & 4 (VAh)",
            "Apparent Energy Quadrants 2 & 3 (VAh)",
            "Total Net Instantaneous Real Power (kW)",
            "Total Net Instantaneous Reactive Power (kVAR)",
            "Total Net Instantaneous Apparent Power (kVA)",
            "Total Power Factor", "Voltage, L-L, 3p Ave (Volts)",
            "Voltage, L-N, 3p Ave (Volts)", "Current, 3p Ave (Amps)",
            "Frequency (Hz)", "Total Real Power Present Demand (kW)",
            "Total Reactive Power Present Demand (kVAR)",
            "Total Apparent Power Present Demand (kVA)",
            "Total Real Power Max Demand, Import (kW)",
            "Total Reactive Power Max Demand, Import (kVAR)",
            "Total Apparent Power Max Demand, Import (kVA)",
            "Total Real Power Max Demand, Export (kW)",
            "Total Reactive Power Max Demand, Export (kVAR)",
            "Total Apparent Power Max Demand, Export (kVA)",
            "Accumulated Real Energy, Phase A, Import (kW)",
            "Accumulated Real Energy, Phase B, Import (kW)",
            "Accumulated Real Energy, Phase C, Import (kW)",
            "Accumulated Real Energy, Phase A, Export (kW)",
            "Accumulated Real Energy, Phase B, Export (kW)",
            "Accumulated Real Energy, Phase C, Export (kW)",
            "Accumulated Q1 Reactive Energy, Phase A, Import (VARh)",
            "Accumulated Q1 Reactive Energy, Phase B, Import (VARh)",
            "Accumulated Q1 Reactive Energy, Phase C, Import (VARh)",
            "Accumulated Q2 Reactive Energy, Phase A, Import (VARh)",
            "Accumulated Q2 Reactive Energy, Phase B, Import (VARh)",
            "Accumulated Q2 Reactive Energy, Phase C, Import (VARh)",
            "Accumulated Q3 Reactive Energy, Phase A, Export (VARh)",
            "Accumulated Q3 Reactive Energy, Phase B, Export (VARh)",
            "Accumulated Q3 Reactive Energy, Phase C, Export (VARh)",
            "Accumulated Q4 Reactive Energy, Phase A, Export (VARh)",
            "Accumulated Q4 Reactive Energy, Phase B, Export (VARh)",
            "Accumulated Q4 Reactive Energy, Phase C, Export (VARh)",
            "Accumulated Apparent Energy, Phase A, Import (VAh)",
            "Accumulated Apparent Energy, Phase B, Import (VAh)",
            "Accumulated Apparent Energy, Phase C, Import (VAh)",
            "Accumulated Apparent Energy, Phase A, Export (VAh)",
            "Accumulated Apparent Energy, Phase B, Export (VAh)",
            "Accumulated Apparent Energy, Phase C, Export (VAh)",
            "Real Power, Phase A (kW)", "Real Power, Phase B (kW)",
            "Real Power, Phase C (kW)", "Reactive Power, Phase A (kVAR)",
            "Reactive Power, Phase B (kVAR)", "Reactive Power, Phase C (kVAR)",
            "Apparent Power, Phase A (kVA)", "Apparent Power, Phase B (kVA)",
            "Apparent Power, Phase C (kVA)", "Power Factor, Phase A",
            "Power Factor, Phase B", "Power Factor, Phase C",
            "Voltage, Phase A-B (Volts)", "Voltage, Phase B-C (Volts)",
            "Voltage, Phase A-C (Volts)", "Voltage, Phase A-N (Volts)",
            "Voltage, Phase B-N (Volts)", "Voltage, Phase C-N (Volts)",
            "Current, Phase A (Amps)", "Current, Phase B (Amps)",
            "Current, Phase C (Amps)"
        ]


    def badData(self, values):
        """
        :param values: String
        :return: Boolean
        """
        # DB cols contain an extra column for the meter ID that is not
        # found in individual raw data files.

        if len(self.dbColumns) - 1 != len(values.split(',')):
            return True

        if not re.match('^\"\d+-\d+-\d+\s\d+:\d+:\d+\"', values.split(',')[0]):
            self.logger.log('bad date {}'.format(values.split(',')[0]), ERROR)
            return True

        return False


    def sqlFormattedValues(self, values):
        """
        :param values: String of raw values from the source CSV files.
        :return: String of PostgreSQL compatible values.
        """


        def makeNULL(x):
            return x == '' and 'NULL' or str(x)


        def makeSingleQuotes(x):
            return str(x).replace('"', "'")


        return ','.join(
            map(lambda x: makeSingleQuotes(makeNULL(x)), values.split(',')))


    def maxTimeStamp(self, filepath = ''):
        """
        :param filepath: String
        :return: datetime or None
        """

        with open(filepath) as dataFile:
            max = None
            lineCnt = 1
            for line in dataFile:
                values = line.rstrip('\n') if lineCnt != 1 else False
                if values and not self.badData(values):
                    dateString = self.sqlFormattedValues(values).split(',')[0]
                    if dateString.startswith("'") and dateString.endswith("'"):
                        dateString = dateString[1:-1]
                    curr = datetime.strptime(dateString, '%Y-%m-%d %H:%M:%S')
                    if not max or curr > max:
                        max = curr
                lineCnt += 1
        return max


    def maxTimeStampDB(self, meter = ''):
        sql = 'SELECT MAX(time_utc) FROM "MeterData_{}"'.format(meter)
        if self.dbUtil.executeSQL(self.cursor, sql,
                                  exitOnFail = self.exitOnError):
            row = self.cursor.fetchone()
            if row and len(row) == 1:
                # self.logger.log('row {}'.format(row), DEBUG)
                return row[0]
        return None
Example #12
0
    parser = argparse.ArgumentParser(
        description='Perform insertion of data contained in multiple files '
        'to the SI database.')
    parser.add_argument('--basepath',
                        required=True,
                        help='A base path from which to process data files.')
    COMMAND_LINE_ARGS = parser.parse_args()


if __name__ == '__main__':
    processCommandLineArguments()
    tableBase = "MeterData"
    pkey = 'meter_id, time_utc'
    logger = SEKLogger(__name__, 'debug')
    configer = SIConfiger()
    dbUtil = SEKDBUtil()
    conn = SEKDBConnector(
        dbName=configer.configOptionValue('Database', 'db_name'),
        dbHost=configer.configOptionValue('Database', 'db_host'),
        dbPort=configer.configOptionValue('Database', 'db_port'),
        dbUsername=configer.configOptionValue('Database', 'db_username'),
        dbPassword=configer.configOptionValue('Database',
                                              'db_password')).connectDB()
    cursor = conn.cursor()

    tableOwner = configer.configOptionValue('Database', 'table_owner')
    for meterName in SIUtil().meters(basepath=COMMAND_LINE_ARGS.basepath):
        logger.log('creating table {}'.format(tableBase + "_" + meterName))
        sql = 'CREATE TABLE "{1}_{0}" ( CHECK ( meter_id = meter_id(\'{' \
              '0}\'))) INHERITS ("{1}"); ALTER TABLE ONLY "{1}_{0}" ADD ' \
              'CONSTRAINT "{1}_{0}_pkey" PRIMARY KEY ({3}); ALTER TABLE ONLY ' \
class SingleFileLoaderTester(unittest.TestCase):
    def setUp(self):
        self.logger = SEKLogger(__name__,'DEBUG')
        self.configer = SIConfiger()
        self.conn = SEKDBConnector(
            dbName = self.configer.configOptionValue('Database', 'db_name'),
            dbHost = self.configer.configOptionValue('Database', 'db_host'),
            dbPort = self.configer.configOptionValue('Database', 'db_port'),
            dbUsername = self.configer.configOptionValue('Database',
                                                         'db_username'),
            dbPassword = self.configer.configOptionValue('Database',
                                                         'db_password')).connectDB()
        self.cursor = self.conn.cursor()
        self.dbUtil = SEKDBUtil()
        self.dataUtil = SIDataUtil()
        self.inserter = SingleFileLoader('data/test-meter/log.csv')
        self.data = '"2014-07-12 16:22:30",0,,,1187488464896.00,' \
                    '2322185846784.00,1134697381888.00,35184644096.00,' \
                    '290857353216.00,10133100822528.00,367.13,' \
                    '-17660932096.00,1078.01,17660934144.00,-7.86,1.80,8.06,' \
                    '-0.97,244.01,122.00,32.93,60.01,-7.09,1.42,7.24,8.06,' \
                    '3.34,8.35,-40.18,-5.68,40.52,516.72,403.12,0,' \
                    '8797179904.00,47518.67,0,86.03,50.23,4198.40,' \
                    '281475022848.00,2251868602368.00,0,6820.01,' \
                    '8796095488.00,0,178.83,188.30,0,620.07,505.19,' \
                    '288230389841920.02,12668.18,68729384.00,0,-3.68,-4.18,,' \
                    '1.00,0.79,,3.81,4.25,,-0.97,-0.98,,244.01,,,121.54,' \
                    '122.46,,31.28,34.59,'
        self.testMeterName = 'test-meter'


    def test_columns(self):
        self.assertEquals(len(self.dataUtil.dbColumns), NUM_DB_COLS)


    def test_insert_data(self):
        self.logger.log('testing data insert')
        self.assertTrue(self.inserter.insertData(self.data))
        self.conn.commit()


    def test_sql_formatted_values(self):
        self.logger.log(
            'data: {}'.format(self.dataUtil.sqlFormattedValues(self.data)))


    def test_meter_id(self):
        self.logger.log('testing meter id')
        meter_id = self.inserter.meterID(self.testMeterName)
        self.logger.log('meter id {}'.format(meter_id))
        self.assertTrue(isinstance(meter_id, ( int, long )))
        self.logger.log('getting meter id')
        sql = 'SELECT meter_id FROM "Meters" WHERE meter_name = \'{}\''.format(
            self.testMeterName)
        success = self.dbUtil.executeSQL(self.cursor, sql, exitOnFail = True)
        if success:
            result = self.cursor.fetchall()
            self.assertEquals(1, len(result))
        else:
            self.logger.log('failed to retrieve meter id', 'error')


    def test_meter_name(self):
        """
        Test getting the meter name.
        :return:
        """
        self.assertEquals(self.inserter.meterName(), self.testMeterName)


    def test_insert_data_from_file(self):
        self.inserter.insertDataFromFile()
        sql = 'SELECT * FROM "MeterData" WHERE meter_id = {}'.format(
            self.inserter.meterID(self.testMeterName))
        success = self.dbUtil.executeSQL(self.cursor, sql, exitOnFail = True)
        if success:
            result = self.cursor.fetchall()
            self.assertEquals(len(result), 10)

        self.assertTrue(success)


    def tearDown(self):
        self.logger.log('teardown', 'debug')
        sql = 'SELECT meter_id FROM "Meters" WHERE meter_name = \'{}\''.format(
            self.testMeterName)
        success = self.dbUtil.executeSQL(self.cursor, sql, exitOnFail = True)
        if success:
            result = self.cursor.fetchall()
            if len(result) == 1:
                sql = 'DELETE FROM "Meters" WHERE meter_id = {}'.format(
                    result[0][0])
                success = self.dbUtil.executeSQL(self.cursor, sql,
                                                 exitOnFail = True)
                if success:
                    self.conn.commit()
                sql = 'SELECT meter_id FROM "Meters" WHERE meter_name = \'{' \
                      '}\''.format(self.testMeterName)
                success = self.dbUtil.executeSQL(self.cursor, sql,
                                                 exitOnFail = True)
                result = self.cursor.fetchall()
                self.assertEquals(0, len(result))