diff --git a/bin/weewx/cheetahgenerator.py b/bin/weewx/cheetahgenerator.py index 4e927103..2f31d60c 100644 --- a/bin/weewx/cheetahgenerator.py +++ b/bin/weewx/cheetahgenerator.py @@ -314,9 +314,8 @@ class CheetahGenerator(weewx.reportengine.ReportGenerator): self.outputted_dict] # Then add the V3.X style search list extensions - db_factory = weewx.tags.DBFactory(self.db_binder, default_binding) for obj in self.search_list_objs: - searchList += obj.get_extension_list(timespan, db_factory) + searchList += obj.get_extension_list(timespan, self.db_binder, default_binding) return searchList @@ -385,14 +384,17 @@ class SearchList(object): """ self.generator = generator - def get_extension_list(self, timespan, db_factory): + def get_extension_list(self, timespan, db_binder, default_binding): """For weewx V3.x extensions. Should return a list of objects whose attributes or keys define the extension. timespan: An instance of weeutil.weeutil.TimeSpan. This will hold the start and stop times of the domain of valid times. - db_factory: An instance of class weewx.tags.DBFactory + db_binder: An instance of class weewx.manager.DBBinder + + default_binding: In the absence of a binding, the binding name + to be used. """ return [self] @@ -463,32 +465,24 @@ class Stats(SearchList): as $day.outTemp.max""" - def get_extension_list(self, timespan, db_factory): + def get_extension_list(self, timespan, db_binder, default_binding): try: trend_dict = self.generator.skin_dict['Units']['Trend'] except KeyError: trend_dict = {'time_delta' : 10800, 'time_grace' : 300} - # Build a "current record" from the default database binding. This - # will allow the database to be hit only once for the most common - # lookups - dbmanager = db_factory.get_database() - record = dbmanager.getRecord(timespan.stop) - current = weewx.tags.CurrentRecord(record, context='current', - formatter=self.generator.formatter, - converter=self.generator.converter) + stats = weewx.tags.TimeBinder(db_binder, + default_binding, + timespan.stop, + formatter=self.generator.formatter, + converter=self.generator.converter, + week_start=self.generator.stn_info.week_start, + rain_year_start=self.generator.stn_info.rain_year_start, + trend=trend_dict, + skin_dict=self.generator.skin_dict) - stats = weewx.tags.FactoryBinder(db_factory, - timespan.stop, - formatter=self.generator.formatter, - converter=self.generator.converter, - week_start=self.generator.stn_info.week_start, - rain_year_start=self.generator.stn_info.rain_year_start, - trend=trend_dict, - skin_dict=self.generator.skin_dict) - - return [{'current':current}, stats] + return [stats] class UnitInfo(SearchList): """Class that implements the $unit tag.""" diff --git a/bin/weewx/tags.py b/bin/weewx/tags.py index ffdef96c..0dbc0ed1 100644 --- a/bin/weewx/tags.py +++ b/bin/weewx/tags.py @@ -12,72 +12,26 @@ from weeutil.weeutil import to_int import weewx.units from weewx.units import ValueTuple -#=============================================================================== -# Class DBFactory -#=============================================================================== - -class DBFactory(object): - """Binds a database cache, with a default database.""" - - def __init__(self, db_binder, default_binding='wx_binding'): - self.db_binder = db_binder - self.default_binding = default_binding - - def get_database(self, binding=None): - if binding is None: - binding = self.default_binding - return self.db_binder.get_database(binding) #=============================================================================== -# Class FactoryBinder +# Class TimeBinder #=============================================================================== -class FactoryBinder(object): - """Binds a DBFactory and an end time together. - - This class sits on the top of chain of helper classes that enable - syntax such as $db($data_binding='wx_binding').month.rain.sum in the Cheetah templates.""" - - def __init__(self, dbfactory, report_time, - formatter=weewx.units.Formatter(), converter=weewx.units.Converter(), **option_dict): - - self.dbfactory = dbfactory - self.report_time = report_time - self.formatter = formatter - self.converter = converter - self.option_dict = option_dict - - def db(self, data_binding=None): - opendb = self.dbfactory.get_database(data_binding) - return DatabaseBinder(opendb, self.report_time, self.formatter, self.converter, **self.option_dict) - - def __getattr__(self, attr): - # The following is so the Python version of Cheetah's NameMapper does not think I'm a dictionary. - if attr == 'has_key': - raise AttributeError(attr) - # For syntax such as $month.outTemp.max, the funcion db() above will not - # get called and instead, we will be queried for an attribute such as 'month'. - # So, make the call to db() with default values, then ask it for the - # attribute. - return getattr(self.db(), attr) - -#=============================================================================== -# Class DatabaseBinder -#=============================================================================== - -class DatabaseBinder(object): - """Binds to a specific database. Can be queried for time attributes, such as month. +class TimeBinder(object): + """Binds to a specific time. Can be queried for time attributes, such as month. When a time period is given as an attribute to it, such as obj.month, the next item in the chain is returned, in this case an instance of - TimeBinder, which binds the database with the time period. + TimespanBinder, which binds things to a timespan. """ - def __init__(self, opendb, report_time, + def __init__(self, db_binder, data_binding, report_time, formatter=weewx.units.Formatter(), converter=weewx.units.Converter(), **option_dict): """Initialize an instance of DatabaseBinder. - opendb: A Database from which the aggregates are to be extracted. + db_binder: An instance of weewx.manager.DBBinder + + data_binding: In the absence of an explicit binding, use this data binding. report_time: The time for which the report should be run. @@ -92,61 +46,74 @@ class DatabaseBinder(object): option_dict: Other options which can be used to customize calculations. [Optional.] """ - self.opendb = opendb - self.report_time = report_time - self.formatter = formatter - self.converter = converter - self.option_dict = option_dict + self.db_binder = db_binder + self.data_binding = data_binding + self.report_time = report_time + self.formatter = formatter + self.converter = converter + self.option_dict = option_dict # What follows is the list of time period attributes: - @property - def current(self, timestamp=None): + def current(self, timestamp=None, data_binding=None): """Return a CurrentObj""" if timestamp is None: timestamp = self.report_time - return CurrentObj(self.opendb, timestamp, + if data_binding is None: + data_binding = self.data_binding + return CurrentObj(self.db_binder, data_binding, timestamp, self.formatter, self.converter, **self.option_dict) - def trend(self, time_delta=None, time_grace=None): + def trend(self, time_delta=None, time_grace=None, data_binding=None): """Returns a TrendObj that is bound to the trend parameters.""" if time_delta is None: time_delta = to_int(self.option_dict['trend'].get('time_delta', 10800)) if time_grace is None: time_grace = to_int(self.option_dict['trend'].get('time_grace', 300)) - - return TrendObj(time_delta, time_grace, self.opendb, self.report_time, + if data_binding is None: + data_binding = self.data_binding + return TrendObj(time_delta, time_grace, self.db_binder, data_binding, self.report_time, self.formatter, self.converter, **self.option_dict) - @property - def day(self): - return TimeBinder(weeutil.weeutil.archiveDaySpan(self.report_time), self.opendb, - 'day', self.formatter, self.converter, **self.option_dict) - @property - def week(self): + def day(self, data_binding=None): + if data_binding is None: + data_binding = self.data_binding + return TimespanBinder(weeutil.weeutil.archiveDaySpan(self.report_time), + self.db_binder, data_binding, + 'day', self.formatter, self.converter, **self.option_dict) + def week(self, data_binding=None): + if data_binding is None: + data_binding = self.data_binding week_start = to_int(self.option_dict.get('week_start', 6)) - return TimeBinder(weeutil.weeutil.archiveWeekSpan(self.report_time, week_start), self.opendb, - 'week', self.formatter, self.converter, **self.option_dict) - @property - def month(self): - return TimeBinder(weeutil.weeutil.archiveMonthSpan(self.report_time), self.opendb, - 'month', self.formatter, self.converter, **self.option_dict) - @property - def year(self): - return TimeBinder(weeutil.weeutil.archiveYearSpan(self.report_time), self.opendb, - 'year', self.formatter, self.converter, **self.option_dict) - @property - def rainyear(self): + return TimespanBinder(weeutil.weeutil.archiveWeekSpan(self.report_time, week_start), + self.db_binder, data_binding, + 'week', self.formatter, self.converter, **self.option_dict) + def month(self, data_binding=None): + if data_binding is None: + data_binding = self.data_binding + return TimespanBinder(weeutil.weeutil.archiveMonthSpan(self.report_time), + self.db_binder, data_binding, + 'month', self.formatter, self.converter, **self.option_dict) + def year(self, data_binding=None): + if data_binding is None: + data_binding = self.data_binding + return TimespanBinder(weeutil.weeutil.archiveYearSpan(self.report_time), + self.db_binder, data_binding, + 'year', self.formatter, self.converter, **self.option_dict) + def rainyear(self, data_binding=None): + if data_binding is None: + data_binding = self.data_binding rain_year_start = to_int(self.option_dict.get('rain_year_start', 1)) - return TimeBinder(weeutil.weeutil.archiveRainYearSpan(self.report_time, rain_year_start), self.opendb, - 'rainyear', self.formatter, self.converter, **self.option_dict) + return TimespanBinder(weeutil.weeutil.archiveRainYearSpan(self.report_time, rain_year_start), + self.db_binder, data_binding, + 'rainyear', self.formatter, self.converter, **self.option_dict) #=============================================================================== -# Class TimeBinder +# Class TimespanBinder #=============================================================================== -class TimeBinder(object): +class TimespanBinder(object): """Holds a binding between a database and a timespan. This class is the next class in the chain of helper classes. @@ -164,14 +131,17 @@ class TimeBinder(object): # Print maximum temperature for each month in the year: print monthStats.outTemp.max """ - def __init__(self, timespan, opendb, context='current', formatter=weewx.units.Formatter(), + def __init__(self, timespan, db_binder, data_binding, context='current', + formatter=weewx.units.Formatter(), converter=weewx.units.Converter(), **option_dict): - """Initialize an instance of TimeBinder. + """Initialize an instance of TimespanBinder. timespan: An instance of weeutil.Timespan with the time span over which the statistics are to be calculated. - opendb: A database from which the stats are to be extracted. + db_binder: An instance of weewx.manager.DBBinder + + data_binding: In the absence of an explicit binding, use this data binding. context: A tag name for the timespan. This is something like 'current', 'day', 'week', etc. This is used to find an appropriate label, if necessary. @@ -189,36 +159,43 @@ class TimeBinder(object): """ self.timespan = timespan - self.opendb = opendb + self.db_binder = db_binder + self.data_binding= data_binding self.context = context self.formatter = formatter self.converter = converter self.option_dict = option_dict # Iterate over days in the time period: - @property - def days(self): - return TimeBinder._seqGenerator(weeutil.weeutil.genDaySpans, self.timespan, self.opendb, - 'day', self.formatter, self.converter, **self.option_dict) + def days(self, data_binding=None): + if data_binding is None: + data_binding = self.data_binding + return TimespanBinder._seqGenerator(weeutil.weeutil.genDaySpans, self.timespan, + self.db_binder, data_binding, + 'day', self.formatter, self.converter, **self.option_dict) # Iterate over months in the time period: - @property - def months(self): - return TimeBinder._seqGenerator(weeutil.weeutil.genMonthSpans, self.timespan, self.opendb, - 'month', self.formatter, self.converter, **self.option_dict) + def months(self, data_binding=None): + if data_binding is None: + data_binding = self.data_binding + return TimespanBinder._seqGenerator(weeutil.weeutil.genMonthSpans, self.timespan, + self.db_binder, data_binding, + 'month', self.formatter, self.converter, **self.option_dict) # Iterate over years in the time period: - @property - def years(self): - return TimeBinder._seqGenerator(weeutil.weeutil.genYearSpans, self.timespan, self.opendb, - 'year', self.formatter, self.converter, **self.option_dict) + def years(self, data_binding=None): + if data_binding is None: + data_binding = self.data_binding + return TimespanBinder._seqGenerator(weeutil.weeutil.genYearSpans, self.timespan, + self.db_binder, data_binding, + 'year', self.formatter, self.converter, **self.option_dict) # Static method used to implement the iteration: @staticmethod def _seqGenerator(genSpanFunc, timespan, *args, **option_dict): - """Generator function that returns TimeBinder for the appropriate timespans""" + """Generator function that returns TimespanBinder for the appropriate timespans""" for span in genSpanFunc(timespan.start, timespan.stop): - yield TimeBinder(span, *args, **option_dict) + yield TimespanBinder(span, *args, **option_dict) # Return the start time of the time period as a ValueHelper @property @@ -239,21 +216,9 @@ class TimeBinder(object): if obs_type == 'has_key': raise AttributeError - # If we represent the "current" time, then no aggregation is possible. Just return - # a ValueHelper. - if self.context == 'current': - max_delta = self.option_dict.get('max_delta') - record_dict = self.opendb.getRecord(self.timespan.stop, max_delta) - if record_dict is not None: - vt = weewx.units.as_value_tuple(record_dict, obs_type) - else: - vt = (None, None, None) - vh = weewx.units.ValueHelper(vt, context='current', formatter=self.formatter, converter=self.converter) - return vh - - # For other contexts, an aggregation is possible. Return an ObservationBinder: if an attribute is - # requested from it, an aggregation value will be returned instead. - return ObservationBinder(obs_type, self.timespan, self.opendb, self.context, + # Return an ObservationBinder: if an attribute is + # requested from it, an aggregation value will be returned. + return ObservationBinder(obs_type, self.timespan, self.db_binder, self.data_binding, self.context, self.formatter, self.converter, **self.option_dict) #=============================================================================== @@ -268,7 +233,7 @@ class ObservationBinder(object): query against the database, assembles the result, and returns it as a ValueHelper. """ - def __init__(self, obs_type, timespan, opendb, context, + def __init__(self, obs_type, timespan, db_binder, data_binding, context, formatter=weewx.units.Formatter(), converter=weewx.units.Converter(), **option_dict): """ Initialize an instance of ObservationBinder @@ -278,7 +243,9 @@ class ObservationBinder(object): timespan: An instance of TimeSpan holding the time period over which the query is to be run - opendb: The database from which the stats are to be extracted. + db_binder: An instance of weewx.manager.DBBinder + + data_binding: In the absence of an explicit binding, use this data binding. context: A tag name for the timespan. This is something like 'current', 'day', 'week', etc. This is used to find an appropriate label, if necessary. @@ -295,13 +262,14 @@ class ObservationBinder(object): [Optional.] """ - self.obs_type = obs_type - self.timespan = timespan - self.opendb = opendb - self.context = context - self.formatter = formatter - self.converter = converter - self.option_dict = option_dict + self.obs_type = obs_type + self.timespan = timespan + self.db_binder = db_binder + self.data_binding = data_binding + self.context = context + self.formatter = formatter + self.converter = converter + self.option_dict = option_dict def max_ge(self, val): return self._do_query('max_ge', val=val) @@ -332,17 +300,17 @@ class ObservationBinder(object): @property def exists(self): - - return self.opendb.exists(self.obs_type) + return self.db_binder.get_database(self.data_binding).exists(self.obs_type) @property def has_data(self): - - return self.opendb.has_data(self.obs_type, self.timespan) + return self.db_binder.get_database(self.data_binding).has_data(self.obs_type, self.timespan) def _do_query(self, aggregateType, val=None): """Run a query against the databases, using the given aggregation type.""" - result = self.opendb.getAggregate(self.timespan, self.obs_type, aggregateType, val=val, **self.option_dict) + db_manager = self.db_binder.get_database(self.data_binding) + result = db_manager.getAggregate(self.timespan, self.obs_type, aggregateType, + val=val, **self.option_dict) return weewx.units.ValueHelper(result, self.context, self.formatter, self.converter) #=============================================================================== @@ -356,11 +324,12 @@ class CurrentObj(object): $current.barometer """ - def __init__(self, opendb, timestamp, formatter, converter, **option_dict): - self.opendb = opendb - self.timestamp = timestamp - self.formatter = formatter - self.converter = converter + def __init__(self, db_binder, data_binding, timestamp, formatter, converter, **option_dict): + self.db_binder = db_binder + self.data_binding = data_binding + self.timestamp = timestamp + self.formatter = formatter + self.converter = converter def __getattr__(self, obs_type): """Return the given observation type.""" @@ -369,9 +338,13 @@ class CurrentObj(object): if obs_type == 'has_key': raise AttributeError - # Get the current record: - record = self.opendb.getRecord(self.timestamp) + # Get the appropriate database manager + db_manager = self.db_binder.get_database(self.data_binding) + # Get the current record from it + record = db_manager.getRecord(self.timestamp) + # Form a ValueTuple vt = weewx.units.as_value_tuple(record, obs_type) + # Finally, return a ValueHelper return weewx.units.ValueHelper(vt, 'current', self.formatter, self.converter) @@ -387,7 +360,7 @@ class TrendObj(object): $trend.barometer """ - def __init__(self, time_delta, time_grace, opendb, nowtime, formatter, converter, **option_dict): + def __init__(self, time_delta, time_grace, db_binder, data_binding, nowtime, formatter, converter, **option_dict): """Initialize a Trend object time_delta: The time difference over which the trend is to be calculated @@ -396,7 +369,8 @@ class TrendObj(object): """ self.time_delta_val = time_delta self.time_grace_val = time_grace - self.opendb = opendb + self.db_binder = db_binder + self.data_binding = data_binding self.nowtime = nowtime self.formatter = formatter self.converter = converter @@ -416,9 +390,10 @@ class TrendObj(object): if obs_type == 'has_key': raise AttributeError + db_manager = self.db_binder.get_database(self.data_binding) # Get the current record, and one "time_delta" ago: - now_record = self.opendb.getRecord(self.nowtime, self.time_grace_val) - then_record = self.opendb.getRecord(self.nowtime - self.time_delta_val, self.time_grace_val) + now_record = db_manager.getRecord(self.nowtime, self.time_grace_val) + then_record = db_manager.getRecord(self.nowtime - self.time_delta_val, self.time_grace_val) # Do both records exist? if now_record is None or then_record is None: diff --git a/bin/weewx/test/test_skins/StandardTest/index.html.tmpl b/bin/weewx/test/test_skins/StandardTest/index.html.tmpl index 45e7b198..2ccf4a6e 100644 --- a/bin/weewx/test/test_skins/StandardTest/index.html.tmpl +++ b/bin/weewx/test/test_skins/StandardTest/index.html.tmpl @@ -88,7 +88,7 @@ Outside Temperature (with explicit database binding) - $db($data_binding='wx_binding').current.outTemp + $current($data_binding='wx_binding').outTemp Outside Temperature trend ($trend.time_delta.hour.format("%.0f"))