#!/usr/bin/env python # # Copyright (c) 2009-2015 Tom Keffer # # See the file LICENSE.txt for your full rights. # """Configure the databases used by weewx""" from __future__ import with_statement import optparse import syslog import sys import time import user.extensions #@UnusedImport import weedb import weewx.manager import weewx.units import weecfg description = """Configure the weewx databases. Most of these functions are handled automatically by weewx, but they may be useful as a utility in special cases. In particular, the 'reconfigure' option can be useful if you decide to add or drop data types from the database schema or change unit systems.""" usage = """wee_database: [CONFIG_FILE|--config=CONFIG_FILE] [--help] [--create-archive] [--drop-daily] [--backfill-daily] [--reconfigure] [--string-check] [--fix] [--binding=BINDING_NAME] """ epilog = """If you are using a MySQL database it is assumed that you have the appropriate permissions for the requested operation.""" def main(): # Set defaults for the system logger: syslog.openlog('wee_database', syslog.LOG_PID|syslog.LOG_CONS) # Create a command line parser: parser = optparse.OptionParser(description=description, usage=usage, epilog=epilog) # Add the various options: parser.add_option("--config", dest="config_path", type=str, metavar="CONFIG_FILE", help="Use configuration file CONFIG_FILE.") parser.add_option("--create-archive", dest="create_archive", action='store_true', help="Create the archive database.") parser.add_option("--drop-daily", dest="drop_daily", action='store_true', help="Drop the daily summary tables from a database.") parser.add_option("--backfill-daily", dest="backfill_daily", action='store_true', help="Backfill a database with daily summaries.") parser.add_option("--reconfigure", action='store_true', help="Create a new archive database using configuration" " information found in the configuration file. In" " particular, the new database will use the" " unit system found in option [StdConvert][target_unit]." " The new database will have the same name as the old" " database, with a '_new' on the end.") parser.add_option("--string-check", dest="string_check", action="store_true", help="Check a sqlite version of the archive database to" " see whether it contains embedded strings.") parser.add_option("--fix", dest="fix", action="store_true", help="Fix any embedded strings in a sqlite database.") parser.add_option("--binding", dest="binding", metavar="BINDING_NAME", default='wx_binding', help="The data binding. Default is 'wx_binding'.") # Now we are ready to parse the command line: (options, args) = parser.parse_args() config_path, config_dict = weecfg.read_config(options.config_path, args) print "Using configuration file %s" % config_path db_binding = options.binding database = config_dict['DataBindings'][db_binding]['database'] print "Using database binding '%s', which is bound to database '%s'" % (db_binding, database) if options.create_archive: createMainDatabase(config_dict, db_binding) if options.drop_daily: dropDaily(config_dict, db_binding) if options.backfill_daily: backfillDaily(config_dict, db_binding) if options.reconfigure: reconfigMainDatabase(config_dict, db_binding) if options.string_check: string_check(config_dict, db_binding, options.fix) def createMainDatabase(config_dict, db_binding): """Create a weewx archive database""" # Try a simple open. If it succeeds, that means the database # exists and is initialized. Otherwise, an exception will be thrown. try: with weewx.manager.open_manager_with_config(config_dict, db_binding) as dbmanager: print "Database '%s' already exists. Nothing done." % (dbmanager.database_name,) except weedb.OperationalError: # Database does not exist. Try again, but allow initialization: with weewx.manager.open_manager_with_config(config_dict, db_binding, initialize=True) as dbmanager: print "Created database '%s'" % (dbmanager.database_name,) def dropDaily(config_dict, db_binding): """Drop the daily summaries from a weewx database""" manager_dict = weewx.manager.get_manager_dict_from_config(config_dict, db_binding) database_name = manager_dict['database_dict']['database_name'] ans = None while ans not in ['y', 'n']: print "Proceeding will delete all your daily summaries from database '%s'" % database_name ans = raw_input("Are you sure you want to proceed (y/n)? ") if ans == 'y' : print "Dropping daily summary tables from '%s' ... " % database_name try: with weewx.manager.open_manager_with_config(config_dict, db_binding) as dbmanager: try: dbmanager.drop_daily() except weedb.OperationalError, e: print "Got error '%s'\nPerhaps there was no daily summary?" % e else: print "Dropped daily summary tables from database '%s'" % (database_name,) except weedb.OperationalError: # No daily summaries. Nothing to be done. print "No daily summaries found in database '%s'. Nothing done." % (database_name,) def backfillDaily(config_dict, db_binding): """Backfill the daily summaries""" manager_dict = weewx.manager.get_manager_dict_from_config(config_dict, db_binding) database_name = manager_dict['database_dict']['database_name'] print "Backfilling daily summaries in database '%s'" % database_name t1 = time.time() # Open up the archive. This will create the tables necessary for the daily summaries if they # don't already exist: with weewx.manager.open_manager_with_config(config_dict, db_binding, initialize=True) as dbmanager: nrecs, ndays = dbmanager.backfill_day_summary() tdiff = time.time() - t1 if nrecs: print "Backfilled '%s' with %d records over %d days in %.2f seconds" % (database_name, nrecs, ndays, tdiff) else: print "Daily summaries up to date in '%s'." % database_name def reconfigMainDatabase(config_dict, db_binding): """Create a new database, then populate it with the contents of an old database""" manager_dict = weewx.manager.get_manager_dict_from_config(config_dict, db_binding) # Make a copy for the new database (we will be modifying it) new_database_dict = dict(manager_dict['database_dict']) # Now modify the database name new_database_dict['database_name'] = manager_dict['database_dict']['database_name']+'_new' # First check and see if the new database already exists. If it does, check # with the user whether it's ok to delete it. try: weedb.create(new_database_dict) except weedb.DatabaseExists: ans = None while ans not in ['y', 'n']: ans = raw_input("New database '%s' already exists. Delete it first (y/n)? " % (new_database_dict['database_name'],)) if ans == 'y': weedb.drop(new_database_dict) elif ans == 'n': print "Nothing done." return # Get the unit system of the old archive: with weewx.manager.Manager.open(manager_dict['database_dict']) as old_dbmanager: old_unit_system = old_dbmanager.std_unit_system if old_unit_system is None: print "Old database has not been initialized. Nothing to be done." return # Get the unit system of the new archive: try: target_unit_nickname = config_dict['StdConvert']['target_unit'] except KeyError: target_unit_system = None else: target_unit_system = weewx.units.unit_constants[target_unit_nickname.upper()] ans = None while ans not in ['y', 'n']: print "Copying archive database '%s' to '%s'" % (manager_dict['database_dict']['database_name'], new_database_dict['database_name']) if target_unit_system is None or old_unit_system==target_unit_system: print "The new archive will use the same unit system as the old ('%s')." % (weewx.units.unit_nicknames[old_unit_system],) else: print "Units will be converted from the '%s' system to the '%s' system." % (weewx.units.unit_nicknames[old_unit_system], weewx.units.unit_nicknames[target_unit_system]) ans = raw_input("Are you sure you wish to proceed (y/n)? ") if ans == 'y': weewx.manager.reconfig(manager_dict['database_dict'], new_database_dict, new_unit_system=target_unit_system, new_schema=manager_dict['schema']) print "Done." elif ans == 'n': print "Nothing done." def string_check(config_dict, db_binding, fix=False): print "Checking archive database for strings..." # Open up the main database archive with weewx.manager.open_manager_with_config(config_dict, db_binding) as dbmanager: obs_pytype_list = [] obs_list = [] # Get the schema and extract the Python type each observation type should be for column in dbmanager.connection.genSchemaOf('archive'): schema_type = column[2] if schema_type == 'INTEGER': schema_type = int elif schema_type == 'REAL': schema_type = float elif schema_type == 'STR': schema_type = str # Save the observation type for this column (eg, 'outTemp'): obs_list.append(column[1]) # Save the Python type for this column (eg, 'int'): obs_pytype_list.append(schema_type) # Cycle through each row in the database for record in dbmanager.genBatchRows(): # Now examine each column for icol in range(len(record)): # Check to see if this column is an instance of the correct Python type if record[icol] is not None and not isinstance(record[icol], obs_pytype_list[icol]): # Oops. Found a bad one. Print it out sys.stdout.write("Timestamp = %s; record['%s']= %r; ... " % (record[0], obs_list[icol], record[icol])) if fix: # Cooerce to the correct type. If it can't be done, then set it to None try: corrected_value = obs_pytype_list[icol](record[icol]) except ValueError: corrected_value = None # Update the database with the new value dbmanager.updateValue(record[0], obs_list[icol], corrected_value) # Inform the user sys.stdout.write("changed to %r\n" % corrected_value) else: sys.stdout.write("ignored.\n") if __name__=="__main__" : main()