Files
weewx/bin/wee_database
Bill Richter 93436a927c once again try to update my fork (#2)
* Got test suites working again.

* Updated TODO

* indicate specific firmware for cc3000

* clarify rainwise firmware

* Further refinement

- max and maxtime now updated
- reworked the progress function, now a method in the class

Have left __main__ code in that has been used for testing windSpeed
recalculation fix

* indicate when calibrations are ignored

* Now emits the barometer trend in LOOP packets as field 'trendIcon'

Vantage only.

* Updated TODO guide, reflecting this morning's phone call.

* Presses on, despite database error

* Simplified patching by moving metadata code to DaySummaryManager

* Got rid of _getVersion()

Can now get it from _read_metadata

* Remove interval weighting fix from weewx startup

Refer TODO.txt

* Update TODO.txt

* added write timeout to ultimeter driver

* better logging when cc3000 loses contact with sensors

* document some of the channel, sensor, and logger specifics for the wmr100/wmr200 hardware

* Removed the p word, patch.py is now database.py, rejigged wee_database as per skype and followup emails

- progress function for fixes included as a method in base class that
can be overridden
- much changing of logging to give consistent results/output
- believe I retained Tom's recent changes to patch.py (commit 4acf752)

* Picks daily summary weights on the basis of database version

* Rewrote the backfill routine (again)

* wee_database now uses new version of backfill_day_summary

* Fixed problem that prevented cold startup

* Revised to reflect latest wee_database incantation

* Modified weighting fix

Should not mess with lessUpdate
Delete lastWeightPatch after successful patch

* Check to make sure the daily summaries have not been partially updated.

* Revised to reflect latest wee_database incantation

* Revised wee_database and interval weighting paras, added windSpeed recalc para

* Removed vacuum

* Updated what has been done and committed

* Better diagnostics with partial update of the daily summaries.

* MySQL now uses transaction isolation level READ COMMITTED

* Add .config (#204)

* Changed semantics of "$last_xxx" tags.

* Template test updates including fixing issue #201 (#205)

* Add .config

* Standardise test skin for index.html.tml to remove "%x %X" locale dependent formatting. Note: Used 24 hour time as AM/PM can also be locale dependent.

Also include fixes for line formatting in some other test files and expected results.

* sysctlbyname is not available on every platform, so catch AttributeError too, otherwise cheetah fails with a name error

* Got rid of tabs

* update usb mode info for acurite models

* Removed intervalgenRoundTS and archiveDaysAgoSpan.

* Simplified tags. Got template test working again.

* Somehow, style "indent" got lost

* Documented new "$ago" tags.

* Documented $ago options, as well as .start and .end

* Update changes doc

* Stupid typo

* use markdown instead of html for README

* indenting seems to confuse markdown's handling of urls

* Left over $last_day in $spans example (#206)

* Add .config

* Standardise test skin for index.html.tml to remove "%x %X" locale dependent formatting. Note: Used 24 hour time as AM/PM can also be locale dependent.

Also include fixes for line formatting in some other test files and expected results.

* Left over $last_day in $spans example.

* a user-specified sensor_map will update, not replace, the default sensor_map

* Updated the upgrade guide.

* ensure that weewx-multi works on deb and rpm as well as setup.py

* document the sensor mapping changes

* Added comprehensive example to customizing guide.

* Updated TODO

* Got the weighting update to work on MySQL

* added crude caching of pressure in wmr300

* Fixed typo

* Fixed little errors. Consistently use "tag", instead of "dot code."

* no altimeter, just pressure and barometer

* Fixed little HTML problems.

* Reformatted, in anticipation of a refactoring of some sections.

* Fixed location of 'Version'

So it works with automatic replacement of 'Version'

* initial experiment with different fonts

* override jquery-ui hijacking of toc font family

* bring header highlighting to h2 to correlate with toc.  reduce post-header margins.

* fix some table header cruft.  prevent indent sections from overflowing right margin.  let the table cells breath.

* fix broken tty formatting

* Checkpoint

Work in progress.

* Refined examples of creating new units and groups

* Polishing. Or, maybe fiddling?

* minor css fixes.  rearrange troubleshooting sections about pressure.

* minor cleanup to readme

* Fixed test suites

Add MySQL back to template tests.

* Reworked the iteration examples.

* Corrected and clarified the units used in the "electricity" example.

* remove write_timeout since naming is inconsistent between pyserial versions and there is no backward compatibility

* remove write_timeout from ultimeter

* Minor changes to users guide

* increase body size to 100%.  background for code.  weeWX in titles.  true bold for monospace.  prep for direct font comparisons.

* Clarified the role of encoding

* update utilities guide with suggested wee_database descriptions and args

* more compaction

* increase margins on html examples.  use droid serif for html examples.

* Clarified a few things

* decode weewx into weeWX

* include transaction limit defaults

* eliminate transaction-limit

* weeWX fixes in install pages.  more fixes to utilities.

* missed a few code spans

* fix version label alignment

* use only major.minor for docs

* Fix error where import fields that are None can cause Source.mapRawData() to crash in some cases

* Updated TODO and NEW_FEATURES

* There will be no daykeys if the daily summaries have been dropped.

* Restructure usage string, hard code transaction days

* dry-run goes with fix-strings, not check-strings

* Log daily summary version

* No need to check for weewx.debug

* include examples in manifest

* added examples and extensions to data_files

* do not install sample extensions

* Can now specify date field separator for Cumulus imports, weewx -> weeWX

* Clarified option strings

* Rudimentary test of selective daily summary rebuild

* No longer allows selective rebuild of the daily summaries

if the summaries are not complete

* Hardwired UTF-8 encoding, but with a warning comment.

* Hardwired UTF-8 encoding, but with a warning comment.

* Documented Cumulus import separator config option

* Added comments about Tools.

* Changed to execute in user environment

* Ported to PyMySQL as an alternative to python-mysqldb

See https://github.com/PyMySQL/PyMySQL for a description of PyMySQL

* Recognize additional MySQL "Can't connect" error

* Fixed error in test suites

Subsequent tests depended on ordering of a dictionary.

* pymysql seems to have problems connecting via file socket

unless it is told explicitly about it.

* Workaround for pypy compiler

* Defaults now support MySQLdb over pymysql

* Fixed bug in record augmentation.

The augmentation was happening without giving StdConvert a chance to do
its thing.

* More clear msg when encountering an ImportError

* Clarified the relationship between archive period and report_timing option, aded note regarding primacy of the report cycle

* Reverted back to MySQLdb only version.

* Changed config option names but never changed the code!

* One transaction for updating daily high/lows and archive record

Formerly, these were done in two transactions.

* v3.7.0a2

* Adjustable value for how long to wait after a bad uploader login.

Option retry_login. Fixes issue #212.

* Fixed Cumulus import rain field issue

wee_import will try to use field 26(AA) - midnight reset daily rainfall
but if not available due it will revert to field 9(J) or 11(L)

* Switched back to __str__ when extracting string out of template.

.respond() doesn't seem to encode Unicode characters properly

* Fix errors in wee_import WU step-by-step, remove Cumulus version caveat on rain

* Now uses dedicated test users 'weewx1' and 'weewx2'

* Formal check of the various MySQLdb exceptions.

* Added sqlite3 exceptions.

* Reworked check_strings() output (#213)

Reworked check_strings() screen and syslog output:
- now gives progress ala --rebuild-daily
- syslog is silent for --check-strings and --fix-strings with --dry-run
- left 'Preparing' (rather than 'Starting') but added 'this may take a
while' as there is a significant delay in dbmanager.genBatchRows()
initialising at line 619 (well there was for 400k records)

* reduce debug log spewage in wmr300 driver

* Finished formal test of errors

* First cut at V3.7 exception hierarchy

* Ported the weedb sqlite driver to the new exception hierarchy.

* Ported MySQLdb to the new database exception hierarchy.

* windSpeed fix now gracefully handles no windSpeed summary table, tweaked --update output/logging

* Now picks up absence of windSpeed daily summary

* The weedb Connection object can now be used in a "with" clause.

* The weedb Cursor object can now be used in a "with" clause.

* V3.7.0a3

* more code removal

* code formatting only

* use apt instructions for debian installs

* Reworked --wee-database section of Utilities Guide to reflect current wee-database operation

- revised usage
- reword --rebuild-daily
- reword --check
- reword --update

* More details on upgrading

* Moved start time to just before applying the patch

* Accumulator is now initialized with override values from weewx.conf

* Added sentences about wee_import/interval and weight patching multiple dbs

* remove extensions from rpm and deb packages

* v3.7.0b1

* Cleaned up some HTML warnings.

* must do a try loop at the read level so we can skip the no data 'errors' and return empty buffer so that a subsequent write will get the station to talk again

* Slightly more robust mechanism for decoding last time a file was FTP'd.

* adjust wording of weighting description

* added examples

* simplify

* fixed typo

* fixed typo

* read /etc/default before bailing out

* make init script work properly with /etc/default/weewx

* make output consistent

* more simplification

* keep PEP happy

* avoid resource consumption from slow reports by extending the StdReport.max_wait.  provide log messages when it happens.

* new features have move to the roadmap

* no more todo items left

* do not emit default sensor_map to the config

* aborted attempt to get additional battery status

* bump to 3.7.0b2

* do recipe using wget instead of curl

* forgot the O option to wget

* do not warn when calibrations are ignored - the implementation resulted in too many log entries

* get rid of tabs

* added notes about wmr300 rain counter and logger

* added norwegian 'no data available' as 'Ingen data er tilgjengelige'

* simplify.  eliminate more passive voice and gerunds.

* avoid run-on

* provide better feedback for operational errors.  make manager logging more consistent.

* fix typo in wmr300 ConfEditor

* decode heatindex and windchill from wmr300 sensor outputs

* fixed bad extract_signed invocation

* fixed wmr* partial packets note

* added mysqldb install instructions to userguide

* minor html fixes

* added link to wee_extension

* clarify acurite sensor transmission periods.

* added battery status for all wmr100 remote t/h sensors

* added battery status for all wmr100 remote t sensors

* document changes to wmr100, wmr200, wmr9x8 drivers.  fix 'Calculatios' typo.

* fixed inverted wmr200 battery status

* rename fault_out to out_fault to match pattern of other faults

* make battery status labels consistent across all wmr drivers

* wmr300 driver moves from rc to 0.18

* bump to 3.7.0b3

* css fixes: neutralize the glaring yellow; brighten the note green to more closely match the tone of warning red; @media tweaks to match font changes.

* fix column title

* distinguish selection color from code color

* fixed shift bug in weewx-multi

* Fixed (I think) issue #219

* update logwatch script to properly handle revised generator log messages

refer commit
03c3e4ef57 (diff-3cefdd7265f340e9683b0a2d0417b70f)

* normalize the quick-start

* Merge branch 'development', remote branch 'origin'

* fix layout table width on installation pages

* v3.7.0b4

* use released_versions instead of previous_versions

* Merge branch 'development', remote branch 'origin'

* parameterize release rule.  make release rule idempotent.

* replace cheetahtemplate.org with pythonhosted.org

* wee_database --help output was slightly different to reality

* Removed BOM at beginning of customizing.htm

* wee_database --help output was slightly different to reality

* Reworded comment on whether to --update daily summaries.

* cater for change in manager log output

* remove misleading windGustDir info

* bump to 3.7.0

* Added date to change log

* escape the dollars in release target

* fixed log syntax

* adjust log level for wmr100 bad usb report

* emit rapidfire cache info only when debug >= 3

* enable post_interval overrides for WOW uploader

* Fixes issue #230, exception when using Rapidfire with metric units

* Added StdRESTbase back in.

It seems that some uploaders still depend on it.

* Fixed problem that prevented a MySQL port from being specified.

* Added antialias GIF to list of files to be installed.

* Make sure GIF files get uploaded

* distribute examples in a single directory

* distinguish docs/examples vs examples

* Fixed bug that prevented a port from being specified for MySQL installations.

* Removed redundant change log entry

* Add MySQL Error 2003 to exceptions (#234)

* Added PR #234 to change log

* Documented change in location of the examples

* update examples paths in remaining guides.  explicitly list all path changes for examples.

* By default, autocommit is now enabled for the MySQL driver. Fixes issue #237.

Included regression test.
2017-03-20 00:41:34 -07:00

794 lines
36 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright (c) 2009-2015 Tom Keffer <tkeffer@gmail.com>
#
# See the file LICENSE.txt for your full rights.
#
"""Configure databases used by weeWX"""
from __future__ import with_statement
# python imports
import datetime
import optparse
import syslog
import sys
import time
# weewx imports
import user.extensions #@UnusedImport
import weecfg.database
import weedb
import weewx.manager
import weewx.units
from weeutil.weeutil import TimeSpan, timestamp_to_string
usage = """wee_database --help
wee_database --create
wee_database --reconfigure
wee_database --transfer --dest-binding=BINDING_NAME [--dry-run]
wee_database --check
wee_database --update [--dry-run]
wee_database --check-strings
wee_database --fix-strings [--dry-run]
wee_database --drop-daily
wee_database --rebuild-daily [--date=YYYY-mm-dd |
--from=YYYY-mm-dd --to=YYYY-mm-dd]
Description:
Manipulate the weeWX database. Most of these operations are handled
automatically by weeWX, but they may be useful in special cases."""
# List of 'dest' settings used by our 'verbs', note 'dest' may be explicit or
# implicit. If adding more 'verbs' need to add corresponding 'dest' here.
dest_list = ['create', 'drop_daily', 'rebuild_daily', 'reconfigure', 'transfer',
'check', 'update', 'check_strings', 'fix']
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(usage=usage)
# 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", dest="create_database", action='store_true',
help="Create the weeWX database and initialize it with the"
" schema.")
parser.add_option("--drop-daily", dest="drop_daily", action='store_true',
help="Drop the daily summary tables from a database.")
parser.add_option("--rebuild-daily", dest="rebuild_daily",
action='store_true',
help="Rebuild the daily summaries from data in the archive"
" table.")
parser.add_option("--date", dest="date", type=str, metavar="YYYY-mm-dd",
help="This date only (option --rebuild-daily only).")
parser.add_option("--from", dest="from_date", type=str, metavar="YYYY-mm-dd",
help="Start with this date (option --rebuild-daily only).")
parser.add_option("--to", dest="to_date", type=str, metavar="YYYY-mm-dd",
help="End with this date (option --rebuild-daily only).")
parser.add_option("--reconfigure", action='store_true',
help="Create a new 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("--transfer", dest="transfer", action='store_true',
help="Transfer the weeWX archive from source database to"
" destination database.")
parser.add_option("--check", dest="check", action="store_true",
help="Check the calculations in the daily summary tables.")
parser.add_option("--update", dest="update", action="store_true",
help="Update the daily summary tables if required and"
" recalculate the daily summary maximum windSpeed values.")
parser.add_option("--check-strings", dest="check_strings", action="store_true",
help="Check the archive table for null strings that may"
" have been introduced by a SQL editing program.")
parser.add_option("--fix-strings", dest="fix", action='store_true',
help="Fix any null strings in a SQLite database.")
parser.add_option("--binding", dest="binding", metavar="BINDING_NAME",
default='wx_binding',
help="The data binding to use. Default is 'wx_binding'.")
parser.add_option("--dest-binding", dest="dest_binding",
metavar="BINDING_NAME",
help="The destination data binding (option --transfer only).")
parser.add_option('--dry-run', dest="dry_run", action='store_true',
default=False,
help='Print what would happen but do not do it. Default'
' is False.')
# Now we are ready to parse the command line:
(options, args) = parser.parse_args()
# Do a check to see if the user used more than 1 'verb' . More than 1 verb
# should be avoided - what did the user really want to do?
# first get a list of options actually used
options_list = [opt for opt, val in options.__dict__.items() if val is not None]
# now a list of the 'dest' settings used
dest_used = [opt for opt in options_list if opt in dest_list]
# If we have more than 1 entry in dest_used then we have more than 1 'verb'
# used. Then inform the user and exit. If only 1 'verb' then carry on.
if len(dest_used) > 1:
# generate the message but we need to speak 'options' (aka verbs)
# not 'dest'
# get a dict of {option.dest: option.str} from our universe of options
opt_dict = dict((x.dest, x.get_opt_string()) for x in parser.option_list[1:])
# now get a list of 'verbs' used
verbs_used = [opt_dict[x] for x in dest_used]
# get rid of the [ and ]
verbs_str = ', '.join(verbs_used)
# our exit message
exit_str = ("Multiple verbs found in command line %s. Only one verb permitted.\nNothing done. Aborting." %
(verbs_str, ))
# now exit with our message
sys.exit(exit_str)
# get config_dict to use
config_path, config_dict = weecfg.read_config(options.config_path, args)
print "Using configuration file %s" % config_path
# set syslog level based
if int(config_dict.get('debug', 0)):
syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
else:
syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_INFO))
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_database:
createMainDatabase(config_dict, db_binding)
if options.drop_daily:
dropDaily(config_dict, db_binding)
if options.rebuild_daily:
rebuildDaily(config_dict, db_binding, options)
if options.reconfigure:
reconfigMainDatabase(config_dict, db_binding)
if options.transfer:
transferDatabase(config_dict, db_binding, options)
if options.check:
check(config_dict, db_binding, options)
if options.update:
update(config_dict, db_binding, options)
if options.check_strings:
check_strings(config_dict, db_binding, options, fix=False)
if options.fix:
check_strings(config_dict, db_binding, options, fix=True)
def createMainDatabase(config_dict, db_binding):
"""Create the weeWX 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' :
t1 = time.time()
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:
tdiff = time.time() - t1
print "Daily summary tables dropped from database '%s' in %.2f seconds" % (database_name, tdiff)
except weedb.OperationalError:
# No daily summaries. Nothing to be done.
print "No daily summaries found in database '%s'. Nothing done." % (database_name,)
def rebuildDaily(config_dict, db_binding, options):
"""Rebuild the daily summaries."""
manager_dict = weewx.manager.get_manager_dict_from_config(config_dict,
db_binding)
database_name = manager_dict['database_dict']['database_name']
# determine the period over which we are rebuilding from any command
# line date parameters
start_d, stop_d = _parse_dates(options)
# advise the user/log what we will do
if start_d is None and stop_d is None:
_msg = "All daily summaries will be rebuilt."
elif start_d and not stop_d:
_msg = "Daily summaries from %s through the end will be rebuilt." % start_d
elif not start_d and stop_d:
_msg = "Daily summaries from the begining through date %s will be rebuilt." % stop_d
elif start_d == stop_d:
_msg = "Daily summary for date %s will be rebuilt" % start_d
else:
_msg = "Daily summaries from %s through %s inclusive will be rebuilt." % (start_d, stop_d)
syslog.syslog(syslog.LOG_INFO, _msg)
print _msg
ans = None
while ans not in ['y', 'n']:
ans = raw_input("Proceed (y/n)? ")
if ans == 'y':
break
elif ans == 'n':
print "Nothing done."
return
t1 = time.time()
# Open up the database. 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:
syslog.syslog(syslog.LOG_INFO, "Rebuilding daily summaries in database '%s' ..." % database_name)
print "Rebuilding daily summaries in database '%s' ..." % database_name
if options.dry_run:
print "Dry run. Nothing done."
return
else:
# now do the actual rebuild
nrecs, ndays = dbmanager.backfill_day_summary(start_d=start_d,
stop_d=stop_d,
trans_days=20)
tdiff = time.time() - t1
# advise the user/log what we did
syslog.syslog(syslog.LOG_INFO, "Rebuild of daily summaries in database '%s' complete" % database_name)
if nrecs:
sys.stdout.flush()
print
print "Processed %d records to rebuild %d day summaries in %.2f seconds " % (nrecs,
ndays,
tdiff)
print "Rebuild of daily summaries in database '%s' complete" % database_name
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 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 database 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':
t1 = time.time()
weewx.manager.reconfig(manager_dict['database_dict'],
new_database_dict,
new_unit_system=target_unit_system,
new_schema=manager_dict['schema'])
tdiff = time.time() - t1
print "Database '%s' copied to '%s' in %.2f seconds." % (manager_dict['database_dict']['database_name'],
new_database_dict['database_name'],
tdiff)
elif ans == 'n':
print "Nothing done."
def transferDatabase(config_dict, db_binding, options):
"""Transfer 'archive' data from one database to another"""
# do we have enough to go on, must have a dest binding
if options.dest_binding is None:
print "Destination binding not specified. Nothing Done. Aborting."
return
# get manager dict for our source binding
src_manager_dict = weewx.manager.get_manager_dict_from_config(config_dict,
db_binding)
# get manager dict for our dest binding
try:
dest_manager_dict = weewx.manager.get_manager_dict_from_config(config_dict,
options.dest_binding)
except weewx.UnknownBinding:
# if we can't find the binding display a message then return
print ("Unknown destination binding '%s', confirm destination binding." %
(options.dest_binding, ))
print "Nothing Done. Aborting."
return
except weewx.UnknownDatabase:
# if we can't find the database display a message then return
print "Error accessing destination database, confirm destination binding and/or database."
print "Nothing Done. Aborting."
return
except (ValueError, AttributeError):
# maybe a schema issue
print "Error accessing destination database."
print ("Maybe the destination schema is incorrectly specified in binding '%s' in weewx.conf?" %
(options.dest_binding, ))
print "Nothing Done. Aborting."
return
except (weewx.UnknownDatabaseType):
# maybe a [Databases] issue
print "Error accessing destination database."
print "Maybe the destination database is incorrectly defined in weewx.conf?"
print "Nothing Done. Aborting."
return
# get a manager for our source
with weewx.manager.Manager.open(src_manager_dict['database_dict']) as src_manager:
# get first and last timestamps from the source so we can count the
# records to transfer and display an appropriate message
first_ts = src_manager.firstGoodStamp()
last_ts = src_manager.lastGoodStamp()
if first_ts is not None and last_ts is not None:
# we have source records
num_recs = src_manager.getAggregate(TimeSpan(first_ts, last_ts),
'dateTime', 'count')[0]
else:
# we have no source records to transfer so abort with a message
print ("No records found in source database '%s' for transfer." %
(src_manager.database_name, ))
print "Nothing done. Aborting."
exit()
ans = None
if not options.dry_run: # is it a dry run ?
# not a dry run, actually do the transfer
while ans not in ['y', 'n']:
ans = raw_input("Transfer %s records from source database '%s' to destination database '%s' (y/n)? " %
(num_recs, src_manager.database_name, dest_manager_dict['database_dict']['database_name']))
if ans == 'y':
t1 = time.time()
# wrap in a try..except in case we have an error
try:
with weewx.manager.Manager.open_with_create(dest_manager_dict['database_dict'],
table_name=dest_manager_dict['table_name'],
schema=dest_manager_dict['schema']) as dest_manager:
sys.stdout.write("transferring, this may take a while.... ")
sys.stdout.flush()
# do the transfer, should be quick as it's done as a
# single transaction
dest_manager.addRecord(src_manager.genBatchRecords())
print "complete"
# get first and last timestamps from the dest so we can
# count the records transferred and display a message
first_ts = dest_manager.firstGoodStamp()
last_ts = dest_manager.lastGoodStamp()
tdiff = time.time() - t1
if first_ts is not None and last_ts is not None:
num_recs = dest_manager.getAggregate(TimeSpan(first_ts, last_ts),
'dateTime', 'count')[0]
print ("%s records transferred from source database '%s' to" %
(num_recs, src_manager.database_name))
print ("destination database '%s' in %.2f seconds." %
(dest_manager.database_name, tdiff))
else:
print ("Error. No records were transferred from source database '%s' to destination database '%s'." %
(src_manager.database_name, dest_manager.database_name))
except ImportError:
# Probably when trying to load db driver
print ("Error accessing destination database '%s'." %
(dest_manager_dict['database_dict']['database_name'], ))
print "Nothing done. Aborting."
raise
except (OSError, weedb.OperationalError):
# probably a weewx.conf typo or MySQL db not created
print ("Error accessing destination database '%s'." %
(dest_manager_dict['database_dict']['database_name'], ))
print "Maybe it does not exist (MySQL) or is incorrectly defined in weewx.conf?"
print "Nothing done. Aborting."
return
elif ans == 'n':
# we decided not to do the transfer
print "Nothing done."
return
else:
# it's a dry run so say what we would have done then return
print ("Transfer %s records from source database '%s' to destination database '%s'." %
(num_recs, src_manager.database_name, dest_manager_dict['database_dict']['database_name']))
print "Dry run, nothing done."
def check(config_dict, db_binding, options):
"""Check database and report outstanding fixes/issues.
Performs the following checks:
- checks database version
- checks for null strings in SQLite database
"""
t1 = time.time()
# Check interval weighting
print "Checking daily summary tables version..."
# Get a database manager object
dbm = weewx.manager.open_manager_with_config(config_dict, db_binding)
# check the daily summary version
_daily_summary_version = dbm._read_metadata('Version')
msg = "Daily summary tables are at version %s" % _daily_summary_version
syslog.syslog(syslog.LOG_INFO, msg)
print msg
if _daily_summary_version is not None and _daily_summary_version >= '2.0':
# interval weighting fix has been applied
msg = "Interval Weighting Fix is not required."
syslog.syslog(syslog.LOG_INFO, msg)
print msg
else:
print "Recommend running --update to recalculate interval weightings."
print "Daily summary tables version check completed in %0.2f seconds." % (time.time() - t1)
# now check for null strings
check_strings(config_dict, db_binding, options, fix=False)
def update(config_dict, db_binding, options):
"""Apply any required database fixes.
Applies the following fixes:
- checks if database version is 2.0, if not interval weighting fix is
applied
- recalculates windSpeed daily summary max and maxtime fields from
archive
"""
# prompt for confirmation if it is not a dry run
ans = 'y' if options.dry_run else None
while ans not in ['y', 'n']:
ans = raw_input("The update process does not affect archive data, but does alter the database.\nContinue (y/n)? ")
if ans == 'n':
# we decided not to update the summary tables
msg = "Update cancelled"
syslog.syslog(syslog.LOG_INFO, msg)
print msg
return
if options.dry_run:
_log_level = syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_WARNING))
msg = "Preparing Interval Weighting Fix..."
syslog.syslog(syslog.LOG_INFO, msg)
print msg
# notify if this is a dry run
if options.dry_run:
print "This is a dry run: weighted intervals will be calculated but not saved."
# Get a database manager object
dbm = weewx.manager.open_manager_with_config(config_dict, db_binding)
database_name = dbm.database_name
# Interval weighting
# first construct an interval weighting config dict
weighting_config_dict = {'name': 'Interval Weighting Fix',
'binding': db_binding,
'trans_days': 100,
'dry_run': options.dry_run}
# create an interval weighting fix object
weight_obj = weecfg.database.IntervalWeighting(config_dict,
weighting_config_dict)
# check the daily summary version
_daily_summary_version = dbm._read_metadata('Version')
msg = "Daily summary tables are at version %s" % _daily_summary_version
syslog.syslog(syslog.LOG_INFO, msg)
print msg
if _daily_summary_version is not None and _daily_summary_version >= '2.0':
# interval weighting fix has been applied
msg = "Interval Weighting Fix is not required."
syslog.syslog(syslog.LOG_INFO, msg)
print msg
else:
# apply the interval weighting
msg = "Calculating interval weights..."
syslog.syslog(syslog.LOG_INFO, msg)
print msg
t1 = time.time()
weight_obj.run()
msg = "Interval Weighting Fix completed in %0.2f seconds." % (time.time() - t1)
syslog.syslog(syslog.LOG_INFO, msg)
print msg
# recalc the max/maxtime windSpeed values
_fix_wind(config_dict, db_binding, options)
# just in case, set the syslog level back where we found it
if options.dry_run:
syslog.setlogmask(_log_level)
def _fix_wind(config_dict, db_binding, options):
"""Recalculate the windSpeed daily summary max and maxtime fields.
Create a WindSpeedRecalculation object and call its run() method to
recalculate the max and maxtime fields from archive data. This process is
idempotent so it can be called repeatedly with no ill effect.
"""
t1 = time.time()
msg = "Preparing Maximum windSpeed Fix..."
syslog.syslog(syslog.LOG_INFO, msg)
print msg
# notify if this is a dry run
if options.dry_run:
print "This is a dry run: maximum windSpeed will be calculated but not saved."
# construct a windSpeed recalculation config dict
wind_config_dict = {'name': 'Maximum windSpeed Fix',
'binding': db_binding,
'trans_days': 100,
'dry_run': options.dry_run}
# create a windSpeedRecalculation object
wind_obj = weecfg.database.WindSpeedRecalculation(config_dict,
wind_config_dict)
# perform the recalculation, wrap in a try..except to catch any db errors
try:
wind_obj.run()
except weedb.NoTableError, e:
msg = "Maximum windSpeed Fix applied: no windSpeed found"
syslog.syslog(syslog.LOG_INFO, msg)
print msg
else:
msg = "Maximum windSpeed Fix completed in %0.2f seconds" % (time.time() - t1)
syslog.syslog(syslog.LOG_INFO, msg)
print msg
def check_strings(config_dict, db_binding, options, fix=False):
"""Scan the archive table for null strings.
Identifies and lists any null string occurrences in the archive table. If
fix is True then any null strings that are found are fixed.
"""
t1 = time.time()
if options.dry_run or not fix:
_log_level = syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_WARNING))
if fix:
syslog.syslog(syslog.LOG_INFO, "Preparing Null String Fix")
print "Preparing Null String Fix, this may take a while..."
# notify if this is a dry run
if options.dry_run:
print "This is a dry run: null strings will be detected but not fixed"
else:
print "Preparing Null String Check, this may take awhile..."
# open up the main database archive table
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)
records = 0
_found = []
# cycle through each row in the database
for record in dbmanager.genBatchRows():
records += 1
# 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.
if fix:
syslog.syslog(syslog.LOG_INFO,
"Timestamp = %s; record['%s']= %r; ... "
% (record[0],
obs_list[icol],
record[icol]))
if fix:
# coerce 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 but only if
# it's not a dry run
if not options.dry_run:
dbmanager.updateValue(record[0], obs_list[icol], corrected_value)
_found.append((record[0], obs_list[icol], record[icol], corrected_value))
# log it
syslog.syslog(syslog.LOG_INFO, " changed to %r\n" % corrected_value)
else:
_found.append((record[0], obs_list[icol], record[icol]))
# notify the user of progress
if records % 1000 == 0:
print >>sys.stdout, "Checking record: %d; Timestamp: %s\r" % \
(records, timestamp_to_string(record[0])),
sys.stdout.flush()
# update our final count now that we have finished
print >>sys.stdout, "Checking record: %d; Timestamp: %s\r" % \
(records, timestamp_to_string(record[0])),
print
tdiff = time.time() - t1
# now display details of what we found if we found any null strings
if len(_found) > 0:
print "The following null strings were found:"
for item in _found:
if len(item) == 4:
print "Timestamp = %s; record['%s'] = %r; ... changed to %r" % item
else:
print "Timestamp = %s; record['%s'] = %r; ... ignored" % item
# how many did we fix?
fixed = len([a for a in _found if len(a)==4])
# summarise our results
if len(_found) == 0:
# found no null strings, log it and display on screen
syslog.syslog(syslog.LOG_INFO, "No null strings found.")
print "No null strings found."
elif fixed == len(_found):
# fixed all we found
if options.dry_run:
# its a dry run so display to screen but not to log
print "%d of %d null strings found would have been fixed." % (fixed,
len(_found))
else:
# really did fix so log and display to screen
syslog.syslog(syslog.LOG_INFO,
"%d of %d null strings found were fixed." % (fixed,
len(_found)))
print "%d of %d null strings found were fixed." % (fixed, len(_found))
elif fix:
# this should never occur - found some but didn't fix them all when we
# should have
if options.dry_run:
# its a dry run so say what would have happened
print "Could not fix all null strings. %d of %d null strings found would have been fixed." % (fixed,
len(_found))
else:
# really did fix so log and display to screen
syslog.syslog(syslog.LOG_INFO,
"Could not fix all null strings. %d of %d null strings found were fixed." % (fixed,
len(_found)))
print "Could not fix all null strings. %d of %d null strings found were fixed." % (fixed,
len(_found))
else:
# found some null string but it was only a check not a fix, just
# display to screen
print ("%d null strings were found.\r\n"
"Recommend running --fix-strings to fix these strings." % len(_found))
# and finally details on time taken
if fix:
syslog.syslog(syslog.LOG_INFO, "Applied Null String Fix in %0.2f seconds." % tdiff)
print "Applied Null String Fix in %0.2f seconds." % tdiff
else:
# it was a check not a fix so just display to screen
print "Completed Null String Check in %0.2f seconds." % tdiff
# just in case, set the syslog level back where we found it
if options.dry_run or not fix:
syslog.setlogmask(_log_level)
def _parse_dates(options):
"""Parse --date, --from and --to command line options.
Inputs:
options: the optparse options
Returns: A two-way tuple (start_d, stop_d), where
start_d: A datetime.date object holding the first date. May be None
stop_d: A dateimte.date object holding the last date. May be None
"""
# Default is None, unless user has specified an option
_first_d = None
_last_d = None
if options.date:
# Make sure we are not overspecified
if options.from_date or options.to_date:
raise ValueError("Specify either --date or a --from and --to combination; not both")
# there is a --date but is it valid
try:
_first_d = datetime.datetime.strptime(options.date, "%Y-%m-%d").date()
except ValueError:
raise ValueError("Invalid --date option specified.")
else:
_last_d = _first_d
return (_first_d, _last_d)
if options.from_date:
try:
_first_d = datetime.datetime.strptime(options.from_date, "%Y-%m-%d").date()
except ValueError:
raise ValueError("Invalid --from option specified.")
if options.to_date:
try:
_last_d = datetime.datetime.strptime(options.to_date, "%Y-%m-%d").date()
except ValueError:
raise ValueError("Invalid --to option specified.")
if _first_d and _last_d:
# If both --from and --to have been specified, make sure the latter is greater
# than the former
if _last_d < _first_d:
raise weewx.ViolatedPrecondition("--from value is later than --to value.")
return (_first_d, _last_d)
if __name__=="__main__" :
main()