I'm with you Greg, that's definitely a good philosophy. :-)
However if you (or others) want to travel to the edge...
On 17/09/2020, Glenn McKechnie <[email protected]> wrote:
> https://github.com/weewx/weewx/wiki/WeeWX-v4-and-logging
>
> That link has a section at the end under "Maintaining backwards
> compatibility" which shows the code style that will incorporate both
> syslog and weeutil.logger style.
> However, the way ausearch.py has been coded requires a little more
> work that just dropping that code into it. There are 18 odd syslog
> statements that would need tweaking.
I've attached a twaked version where it uses the code as per the above style.
I can't check this here as I stopped running ausearch.py
If anyone wants to try it, the usual caveats apply.
Make a back up of the original. Rename the attached file to
ausearch.py and then replace the original. Restart weewx.
Hopefully I caught all occurences, and that it works as intended.
--
Cheers
Glenn
rorpi - read only raspberry pi & various weewx addons
https://github.com/glennmckechnie
--
You received this message because you are subscribed to the Google Groups
"weewx-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/weewx-user/CAAraAzgm27eLOriS-uHA2v7yeZJWwxyDVCBFaEj-b_r6YeQ5TA%40mail.gmail.com.
import datetime
import pytz
import time
import dateutil.parser
import dateutil.tz
import os.path
import pprint
import xml.etree.cElementTree as ET
import json
from urllib.request import urlopen, Request
import weewx.units
import weeutil.weeutil
from weewx.cheetahgenerator import SearchList
try:
# Test for new-style weewx logging by trying to import weeutil.logger
import weeutil.logger
import logging
log = logging.getLogger(__name__)
def logdbg(msg):
log.debug(msg)
def loginf(msg):
log.info(msg)
def logerr(msg):
log.error(msg)
except ImportError:
# Old-style weewx logging
import syslog
def logmsg(level, msg):
# Replace '__name__' with something to identify your application.
syslog.syslog(level, 'ausearch: %s:' % msg)
def logdbg(msg):
logmsg(syslog.LOG_DEBUG, msg)
def loginf(msg):
logmsg(syslog.LOG_INFO, msg)
def logerr(msg):
logmsg(syslog.LOG_ERR, msg)
#feeslike lookups
DaySummerCoastRanges = { -40: 'Cold', 16: 'Cool', 22: 'Mild', 27: 'Warm', 32: 'Hot', 37: 'Very Hot' }
DaySummerInlandPlains = { -40: 'Cold', 20: 'Cool', 25: 'Mild', 30: 'Warm', 35: 'Hot', 40: 'Very Hot' }
DaySummerTropics = { -40: '-', 35: 'Hot', 40: '-' }
NightSummerSouthRanges = { -40: 'Cold', 10: 'Cool', 15: 'Mild', 18: 'Warm', 22: 'Hot' }
NightSummerNorth = { -40: 'Cold', 13: 'Cool', 18: 'Mild', 21: 'Warm', 25: 'Hot' }
NightSummerTropics = { -40: 'Cool', 20: '-' }
DayWinterSouthRanges = { -40: 'Very Cold', 10: 'Cold', 13: 'Cool', 16: 'Mild', 20: 'Warm' }
DayWinterNorth = { -40: 'Very Cold', 10: 'Cold', 15: 'Cool', 20: 'Mild', 25: 'Warm' }
DayWinterTropics = { -40: 'Cold', 20: 'Cool', 26: '-' }
NightWinterSouthRanges = { -40: 'Very Cold', 1: 'Cold', 5: 'Cool', 10: 'Mild' }
NightWinterNorth = { -40: 'Very Cold', 5: 'Cold', 10: 'Cool', 15: 'Mild' }
NightWinterTropics = { -40: 'Cold', 13: 'Cool', 18: '-' }
feelslikeDict = {
'DaySummerCoastRanges': DaySummerCoastRanges,
'DaySummerInlandPlains': DaySummerInlandPlains,
'DaySummerTropics': DaySummerTropics,
'NightSummerSouthRanges': NightSummerSouthRanges,
'NightSummerNorth': NightSummerNorth,
'NightSummerTropics': NightSummerTropics,
'DayWinterSouthRanges': DayWinterSouthRanges,
'DayWinterNorth': DayWinterNorth,
'DayWinterTropics': DayWinterTropics,
'NightWinterSouthRanges': NightWinterSouthRanges,
'NightWinterNorth': NightWinterNorth,
'NightWinterTropics': NightWinterTropics
}
feelslikeLocalDefaults = ['DaySummerCoastRanges', 'NightSummerSouthRanges', 'DayWinterSouthRanges', 'NightWinterSouthRanges']
class ausutils(SearchList):
"""Class that implements the '$aus' tag."""
def __init__(self, generator):
SearchList.__init__(self, generator)
self.aus = { "feelslike" : self.feelslikeFunc }
try:
feelslikeLocalList = weeutil.weeutil.option_as_list(self.generator.skin_dict['AusSearch']['feelslike']['feelslikeLocal'])
except KeyError:
feelslikeLocalList = feelslikeLocalDefaults
try:
self.feelslikeLocal = {}
self.feelslikeLocal['DaySummer'] = feelslikeDict[feelslikeLocalList[0]]
self.feelslikeLocal['NightSummer'] = feelslikeDict[feelslikeLocalList[1]]
self.feelslikeLocal['DayWinter'] = feelslikeDict[feelslikeLocalList[2]]
self.feelslikeLocal['NightWinter'] = feelslikeDict[feelslikeLocalList[3]]
logdbg("aussearch: feelslikeLocal['DaySummer'] = %s" % (pprint.pformat(self.feelslikeLocal['DaySummer'])))
logdbg("aussearch: feelslikeLocal['NightSummer'] = %s" % (pprint.pformat(self.feelslikeLocal['NightSummer'])))
logdbg("aussearch: feelslikeLocal['DayWinter'] = %s" % (pprint.pformat(self.feelslikeLocal['DayWinter'])))
logdbg("aussearch: feelslikeLocal['NightWinter'] = %s" % (pprint.pformat(self.feelslikeLocal['NightWinter'])))
except KeyError:
logerr("aussearch: invalid feelslikeLocal settings [%s, %s, %s, %s]"
% feelslikeLocalList[0], feelslikeLocalList[1], feelslikeLocalList[2], feelslikeLocalList[3])
self.aus['icons'] = {
'1' : 'http://www.bom.gov.au/images/symbols/large/sunny.png',
'2' : 'http://www.bom.gov.au/images/symbols/large/clear.png',
'3' : 'http://www.bom.gov.au/images/symbols/large/partly-cloudy.png',
'4' : 'http://www.bom.gov.au/images/symbols/large/cloudy.png',
'5' : '',
'6' : 'http://www.bom.gov.au/images/symbols/large/haze.png',
'7' : '',
'8' : 'http://www.bom.gov.au/images/symbols/large/light-rain.png',
'9' : 'http://www.bom.gov.au/images/symbols/large/wind.png',
'10' : 'http://www.bom.gov.au/images/symbols/large/fog.png',
'11' : 'http://www.bom.gov.au/images/symbols/large/showers.png',
'12' : 'http://www.bom.gov.au/images/symbols/large/rain.png',
'13' : 'http://www.bom.gov.au/images/symbols/large/dust.png',
'14' : 'http://www.bom.gov.au/images/symbols/large/frost.png',
'15' : 'http://www.bom.gov.au/images/symbols/large/snow.png',
'16' : 'http://www.bom.gov.au/images/symbols/large/storm.png',
'17' : 'http://www.bom.gov.au/images/symbols/large/light-showers.png',
'18' : 'http://www.bom.gov.au/images/symbols/large/heavy-showers.png'
}
self.aus['iconsSml'] = {
'1' : 'http://www.bom.gov.au/images/symbols/small/sunny.png',
'2' : 'http://www.bom.gov.au/images/symbols/small/clear.png',
'3' : 'http://www.bom.gov.au/images/symbols/small/partly-cloudy.png',
'4' : 'http://www.bom.gov.au/images/symbols/small/cloudy.png',
'5' : '',
'6' : 'http://www.bom.gov.au/images/symbols/small/haze.png',
'7' : '',
'8' : 'http://www.bom.gov.au/images/symbols/small/light-rain.png',
'9' : 'http://www.bom.gov.au/images/symbols/small/wind.png',
'10' : 'http://www.bom.gov.au/images/symbols/small/fog.png',
'11' : 'http://www.bom.gov.au/images/symbols/small/showers.png',
'12' : 'http://www.bom.gov.au/images/symbols/small/rain.png',
'13' : 'http://www.bom.gov.au/images/symbols/small/dust.png',
'14' : 'http://www.bom.gov.au/images/symbols/small/frost.png',
'15' : 'http://www.bom.gov.au/images/symbols/small/snow.png',
'16' : 'http://www.bom.gov.au/images/symbols/small/storm.png',
'17' : 'http://www.bom.gov.au/images/symbols/small/light-showers.png',
'18' : 'http://www.bom.gov.au/images/symbols/small/heavy-showers.png'
}
self.aus['rainImgs'] = {
'0%' : 'http://www.bom.gov.au/images/ui/weather/rain_0.gif',
'5%' : 'http://www.bom.gov.au/images/ui/weather/rain_5.gif',
'10%' : 'http://www.bom.gov.au/images/ui/weather/rain_10.gif',
'20%' : 'http://www.bom.gov.au/images/ui/weather/rain_20.gif',
'30%' : 'http://www.bom.gov.au/images/ui/weather/rain_30.gif',
'40%' : 'http://www.bom.gov.au/images/ui/weather/rain_40.gif',
'50%' : 'http://www.bom.gov.au/images/ui/weather/rain_50.gif',
'60%' : 'http://www.bom.gov.au/images/ui/weather/rain_60.gif',
'70%' : 'http://www.bom.gov.au/images/ui/weather/rain_70.gif',
'80%' : 'http://www.bom.gov.au/images/ui/weather/rain_80.gif',
'90%' : 'http://www.bom.gov.au/images/ui/weather/rain_90.gif',
'95%' : 'http://www.bom.gov.au/images/ui/weather/rain_95.gif',
'100%' : 'http://www.bom.gov.au/images/ui/weather/rain_100.gif'
}
try:
self.cache_root = self.generator.skin_dict['AusSearch']['cache_root']
except KeyError:
self.cache_root = '/var/lib/weewx/aussearch'
try:
self.user_agent = self.generator.skin_dict['AusSearch']['user_agent']
except KeyError:
self.user_agent = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'
try:
self.staleness_time = float(self.generator.skin_dict['AusSearch']['staleness_time'])
except KeyError:
self.staleness_time = 15 * 60 #15 minutes
try:
self.refresh_time = float(self.generator.skin_dict['AusSearch']['refresh_time'])
except KeyError:
self.refresh_time = 30 * 60 #30 minutes
if not os.path.exists(self.cache_root):
os.makedirs(self.cache_root)
try:
xml_files = self.generator.skin_dict['AusSearch']['xml_files']
except KeyError:
xml_files = None
if xml_files is not None:
for xml_file in xml_files:
self.aus[xml_file] = XmlFileHelper(self.generator.skin_dict['AusSearch']['xml_files'][xml_file],
self,
generator.formatter,
generator.converter)
try:
json_files = self.generator.skin_dict['AusSearch']['json_files']
except KeyError:
json_files = None
if json_files is not None:
for json_file in json_files:
self.aus[json_file] = JsonFileHelper(self.generator.skin_dict['AusSearch']['json_files'][json_file],
self,
generator.formatter,
generator.converter)
try:
localization = self.generator.skin_dict['AusSearch']['local']
except KeyError:
localization = None
if localization is not None:
for localization_object in localization:
try:
self.aus[localization_object] = \
self.aus[self.generator.skin_dict['AusSearch']['local'][localization_object]]
except KeyError:
logerr("aussearch: localization error for %s" % (localization_object))
try:
index_locality = self.generator.skin_dict['AusSearch']['localities']['index_locality']
except KeyError:
index_locality = 'Sydney'
self.aus['index_locality'] = index_locality
def get_extension_list(self, timespan, db_lookup):
return [self]
def feelslikeFunc(self, T_C=None, TS=None):
if T_C is None or TS is None:
return None
try:
DT = datetime.datetime.fromtimestamp(TS)
if DT.month >= 10 or DT.month <= 3:
# October to March - 'Summer'
if DT.hour >= 9 and DT.hour < 21:
#Daytime
feelslikeLookup = self.feelslikeLocal['DaySummer']
else:
#Nighttime
feelslikeLookup = self.feelslikeLocal['NightSummer']
else:
# April to September - 'Winter'
if DT.hour >= 9 or DT.hour < 21:
#Daytime
feelslikeLookup = self.feelslikeLocal['DayWinter']
else:
#Nighttime
feelslikeLookup = self.feelslikeLocal['NightWinter']
return feelslikeLookup[max(k for k in feelslikeLookup if k < T_C)]
except:
return "-"
class XmlFileHelper(object):
"""Helper class used for for the xml file template tag."""
def __init__(self, xml_file, searcher, formatter, converter):
"""
xml_file: xml file we are reading. <amoc><next-routine-issue-time-utc> used by default for staleness checking
formatter: an instance of Formatter
converter: an instance of Converter
"""
self.xml_file = xml_file
try:
xml_file_path_parts = xml_file.split('/')
xml_file_parts = xml_file_path_parts[-1].split('.')
except ValueError as e:
logerr("aussearch: bad xml file format: %s: %s" % (xml_file, e.message))
return
self.local_file = xml_file_path_parts[-1]
self.local_file_path = os.path.join(searcher.cache_root, self.local_file)
self.is_ftp = True if xml_file_path_parts[0] == "ftp:" else False
# Build remote amoc file path from remote xml file path
# ftp://ftp.bom.gov.au/anon/gen/fwo/IDN11060.xml
# => ftp://ftp.bom.gov.au/anon/gen/fwo/IDN11060.amoc.xml
self.xml_file_amoc = '/'.join(
[
'/'.join(xml_file_path_parts[0:-1]),
'.'.join([xml_file_parts[0], "amoc", xml_file_parts[-1]])
])
if os.path.exists(self.local_file_path):
try:
self.dom = ET.parse(open(self.local_file_path, "r"))
self.root = self.dom.getroot()
except ET.ParseError as e:
logerr("aussearch: bad cache xml file %s: %s" % (self.local_file_path, e))
self.root = None
if self.root is not None:
file_stale = False
nextIssue = self.root.find('amoc/next-routine-issue-time-utc')
if nextIssue is not None:
check_datetime = dateutil.parser.parse(nextIssue.text) + \
datetime.timedelta(seconds=searcher.staleness_time)
# For completeness we need to make sure that we are comparing timezone aware times
# since the parse of next-routine-issue-time-utc wil return a timezone aware time
# hence we pass a utc timezone to get the current utc time as a timezone aware time
# eg. datetime.datetime(2016, 12, 4, 0, 52, 34, tzinfo=tzutc())
now_datetime = datetime.datetime.now(dateutil.tz.tzutc())
logdbg("aussearch: check xml file: %s expires %s" %
(self.local_file_path, check_datetime))
if now_datetime >= check_datetime:
file_stale = True
logdbg("aussearch: xml file is stale: %s" % (self.local_file_path))
else:
check_datetime = datetime.datetime.utcfromtimestamp(os.path.getmtime(self.local_file_path)) + \
datetime.timedelta(seconds=searcher.staleness_time)
now_datetime = datetime.datetime.utcnow()
if now_datetime >= check_datetime:
file_stale = True
# If not stale from cache information, check if amoc XML exists and check its sent time
# Generally only ftp files have amoc check files
if not file_stale and self.is_ftp:
logdbg("aussearch: xml: checking cache sent-time va remote amoc sent-time: %s" %
(self.local_file))
try:
request = Request(self.xml_file_amoc, headers={'User-Agent':searcher.user_agent})
fp = urlopen(request)
data = fp.read().decode()
fp.close()
amoc_dom = ET.fromstring(data)
sentTimeCache = self.root.find('amoc/sent-time').text
sentTimeAmoc = amoc_dom.find('sent-time').text
logdbg("aussearch: xml: %s: sent-time: %s" % (self.local_file_path, sentTimeCache))
logdbg("aussearch: xml: %s: sent-time: %s" % (self.xml_file_amoc, sentTimeAmoc))
if sentTimeAmoc != sentTimeCache:
logdbg("aussearch: xml file is stale: %s" %
(self.local_file_path))
file_stale = True
except (AttributeError, IOError, ET.ParseError):
# Amoc file may not exist or parse well. That is fine, we just won't use
pass
else:
file_stale = True
else:
file_stale = True
if file_stale:
try:
request = Request(self.xml_file, headers={'User-Agent':searcher.user_agent})
fp = urlopen(request)
data = fp.read().decode()
fp.close()
with open(self.local_file_path, 'w') as f:
f.write(data)
logdbg("aussearch: xml file downloaded: %s" % (self.xml_file))
self.dom = ET.parse(open(self.local_file_path, "r"))
self.root = self.dom.getroot()
except IOError as e:
logerr("aussearch: cannot download xml file %s: %s" % (self.xml_file, e))
self.root = None
if self.root is not None:
self.root_node = XMLNode(self.root)
@property
def xmlFile(self):
"""Return the xml_file we are using"""
return self.xml_file
def __getattr__(self, child_or_attrib):
# This is to get around bugs in the Python version of Cheetah's namemapper:
if child_or_attrib in ['__call__', 'has_key']:
raise AttributeError
if self.root_node is not None:
return getattr(self.root_node, child_or_attrib)
else:
raise AttributeError
class XMLNode(object):
def __init__(self, node):
self.node = node
def __call__(self, *args, **kwargs):
return self.search(*args, **kwargs)
def getNodes(self, tag=None):
#TODO getNodes to support search and use node.findAll
#For now this should do in getting all locations in a forecast
nodes = self.node.iter(tag)
xmlNodes = []
for node in nodes:
xmlNodes.append(XMLNode(node))
return xmlNodes
def getNode(self, *args, **kwargs):
"""
Enables getting the current node.
Useful for calls like
$aus.NSWMETRO.forecast('area',description="Parramatta").getNode('forecast_period',index="0").text
"""
return self.search(*args, **kwargs)
def search(self, *args, **kwargs):
"""Lookup a node using a attribute pairs"""
node_search = ""
if len(args) == 0 and len(kwargs) == 0:
#can't build a meaningful search string, return self
return self
elif len(args) == 1 and len(kwargs) == 0:
#assume that caller is passing a full XMLPath search string
node_search = args[0]
else:
node_search = "."
for xArg in args:
node_search += '/'
node_search += xArg
for key, value in kwargs.items():
node_search += str.format("[@{0}='{1}']", key, value)
childNode = self.node.find(node_search)
return XMLNode(childNode) if childNode is not None else None
def toString(self, addLabel=True, useThisFormat=None, NONE_string=None):
#TODO: What to do here? We are not dealing with value tuples. YET!
return self.node.text if self.node.text is not None else NONE_string
def __str__(self):
"""Return as string"""
return self.toString()
@property
def string(self, NONE_string=None):
"""Return as string with an optional user specified string to be
used if None"""
return self.toString(NONE_string=NONE_string)
def __getattr__(self, child_or_attrib):
child_or_attrib = child_or_attrib.replace("__", "-")
#try to look up an attribute
if ET.iselement(self.node):
attrib = self.node.get(child_or_attrib)
if attrib is not None:
return attrib
#try to find a child
childNode = self.node.find(child_or_attrib)
if childNode is not None:
return XMLNode(childNode)
else:
raise AttributeError
class JsonFileHelper(object):
"""Helper class used for for the xml file template tag."""
def __init__(self, json_file, searcher, formatter, converter):
"""
json_file: xml file we are reading.
formatter: an instance of Formatter
converter: an instance of Converter
"""
self.json_file = json_file
self.local_file = json_file.split('/')[-1]
self.local_file_path = os.path.join(searcher.cache_root, self.local_file)
if os.path.exists(self.local_file_path):
try:
self.root = json.load(open(self.local_file_path, "r"))
except IOError as e:
logerr("aussearch: cannot load local json file %s: %s" % (self.local_file_path, e))
self.root = None
if self.root is not None:
try:
latest_data_utc = self.root['observations']['data'][0]['aifstime_utc']
except KeyError:
latest_data_utc = None
if latest_data_utc is not None:
check_datetime = dateutil.parser.parse(latest_data_utc + "UTC") + \
datetime.timedelta(seconds=searcher.refresh_time) + \
datetime.timedelta(seconds=searcher.staleness_time)
# For completeness we need to make sure that we are comparing timezone aware times
# since the parse of next-routine-issue-time-utc wil return a timezone aware time
# hence we pass a utc timezone to get the current utc time as a timezone aware time
# eg. datetime.datetime(2016, 12, 4, 0, 52, 34, tzinfo=tzutc())
now_datetime = datetime.datetime.now(dateutil.tz.tzutc())
logdbg("aussearch: check json file: %s expires %s" %
(self.local_file_path, check_datetime))
if now_datetime <= check_datetime:
file_stale = False
else:
file_stale = True
logdbg("aussearch: json file is stale: %s" % (self.local_file_path))
else:
file_stale = True
logdbg("aussearch: json file bad data assuming stale: %s" %
(self.local_file_path))
else:
file_stale = True
logdbg("aussearch: json file does not exist: %s" %
(self.local_file_path))
if file_stale:
try:
request = Request(self.json_file, headers={'User-Agent':searcher.user_agent})
fp = urlopen(request)
data = fp.read().decode()
fp.close()
with open(self.local_file_path, 'w') as f:
f.write(data)
logdbg("aussearch: json file downloaded: %s" % (self.json_file))
self.root = json.load(open(self.local_file_path, "r"))
except IOError as e:
logerr("aussearch: cannot download json file %s: %s" % (self.json_file, e))
if self.root is not None:
self.root_node = JSONNode(self.root)
@property
def jsonFile(self):
"""Return the json_file we are using"""
return self.json_file
def __getattr__(self, key_or_index):
# This is to get around bugs in the Python version of Cheetah's namemapper:
if key_or_index in ['__call__', 'has_key']:
raise AttributeError
if self.root_node is not None:
return getattr(self.root_node, key_or_index)
else:
raise AttributeError
class JSONNode(object):
def __init__(self, node):
self.node = node
def __call__(self, key_or_index):
return self.walk(key_or_index)
def walk(self, key_or_index):
"""Walk the json data"""
childNode = None
if isinstance(self.node, dict):
try:
childNode = self.node[key_or_index]
except KeyError:
raise AttributeError
elif isinstance(self.node, list):
try:
index = int(key_or_index)
childNode = self.node[index]
except ValueError:
pass
except IndexError:
raise AttributeError
if childNode is None:
try:
childNode = self.node[0][key_or_index]
except KeyError:
raise AttributeError
else:
raise AttributeError
return JSONNode(childNode) if childNode is not None else None
def toString(self, addLabel=True, useThisFormat=None, NONE_string=None):
#TODO: What to do here? We are not dealing with value tuples. YET!
return str(self.node) if self.node is not None else NONE_string
def __str__(self):
"""Return as string"""
return self.toString()
@property
def string(self, NONE_string=None):
"""Return as string with an optional user specified string to be
used if None"""
return self.toString(NONE_string=NONE_string)
def __getattr__(self, key_or_index):
key_or_index = key_or_index.replace("__", "-")
return self.walk(key_or_index)