Merge branch 'custom_wx' into develop
[infoex-autowx.git] / infoex-autowx.py
index fdaec3485e2f09534568f11b6d3ef94dc6938300..c191e5c767c627f539ab1c8194ab8f6314ce5c81 100755 (executable)
@@ -39,7 +39,7 @@ import zeep
 import zeep.cache
 import zeep.transports
 
-__version__ = '2.1.0'
+__version__ = '2.2.0'
 
 LOG = logging.getLogger(__name__)
 LOG.setLevel(logging.NOTSET)
@@ -85,7 +85,7 @@ def setup_config(config):
         station = dict()
         station['provider'] = config['station']['type']
 
-        if station['provider'] not in ['nrcs', 'mesowest']:
+        if station['provider'] not in ['nrcs', 'mesowest', 'python']:
             print("Please specify either nrcs or mesowest as the station type.")
             sys.exit(1)
 
@@ -112,9 +112,12 @@ def setup_config(config):
                                 '&stid=' + station['station_id'] + \
                                 '&vars=' + station['desired_data']
 
+        if station['provider'] == 'python':
+            station['path'] = config['station']['path']
+
     except KeyError as err:
         LOG.critical("%s not defined in configuration file", err)
-        exit(1)
+        sys.exit(1)
 
     # all sections/values present in config file, final sanity check
     try:
@@ -124,7 +127,7 @@ def setup_config(config):
                     raise ValueError
     except ValueError:
         LOG.critical("Config value '%s.%s' is empty", key, subkey)
-        exit(1)
+        sys.exit(1)
 
     return (infoex, station)
 
@@ -184,14 +187,23 @@ def main():
     iemap = setup_infoex_counterparts_mapping(station['provider'])
 
     # override units if user selected metric
-    if station['units'] == 'metric':
-        final_data = switch_units_to_metric(final_data, fmap)
+    try:
+        if station['units'] == 'metric':
+            final_data = switch_units_to_metric(final_data, fmap)
+    except KeyError:
+        if station['provider'] != 'python':
+            LOG.error("Please specify the units in the configuration "
+                      "file")
+            sys.exit(1)
 
     (begin_date, end_date) = setup_time_values()
 
-    # get the data
-    LOG.debug("Getting %s data from %s to %s", str(station['desired_data']),
-              str(begin_date), str(end_date))
+    if station['provider'] == 'python':
+        LOG.debug("Getting custom data from external Python program")
+    else:
+        LOG.debug("Getting %s data from %s to %s",
+                  str(station['desired_data']),
+                  str(begin_date), str(end_date))
 
     time_all_elements = time.time()
 
@@ -201,6 +213,38 @@ def main():
     elif station['provider'] == 'mesowest':
         infoex['wx_data'] = get_mesowest_data(begin_date, end_date,
                                               station)
+    elif station['provider'] == 'python':
+        try:
+            import importlib.util
+
+            spec = importlib.util.spec_from_file_location('custom_wx',
+                                                          station['path'])
+            mod = importlib.util.module_from_spec(spec)
+            spec.loader.exec_module(mod)
+            mod.LOG = LOG
+
+            try:
+                infoex['wx_data'] = mod.get_custom_data()
+
+                if infoex['wx_data'] is None:
+                    infoex['wx_data'] = []
+            except Exception as exc:
+                LOG.error("Python program for custom Wx data failed in "
+                          "execution: " + str(exc))
+                sys.exit(1)
+
+            LOG.info("Successfully executed external Python program")
+        except ImportError:
+            LOG.error("Please upgrade to Python 3.3 or later")
+            sys.exit(1)
+        except FileNotFoundError:
+            LOG.error("Specified Python program for custom Wx data "
+                      "was not found")
+            sys.exit(1)
+        except Exception as exc:
+            LOG.error("A problem was encountered when attempting to "
+                      "load your custom Wx program: " + str(exc))
+            sys.exit(1)
 
     LOG.info("Time taken to get all data : %.3f sec", time.time() -
              time_all_elements)
@@ -245,13 +289,14 @@ def main():
 
     LOG.debug("final_data: %s", str(final_data))
 
-    if not write_local_csv(infoex['csv_filename'], final_data):
-        LOG.warning('Could not write local CSV file: %s',
-                    infoex['csv_filename'])
-        return 1
+    if len(infoex['wx_data']) > 0:
+        if not write_local_csv(infoex['csv_filename'], final_data):
+            LOG.warning('Could not write local CSV file: %s',
+                        infoex['csv_filename'])
+            return 1
 
-    if not options.dry_run:
-        upload_csv(infoex['csv_filename'], infoex)
+        if not options.dry_run:
+            upload_csv(infoex['csv_filename'], infoex)
 
     LOG.debug('DONE')
     return 0
@@ -336,6 +381,18 @@ def setup_infoex_counterparts_mapping(provider):
         iemap['wind_speed'] = 'windSpeedNum'
         iemap['wind_direction'] = 'windDirectionNum'
         iemap['wind_gust'] = 'windGustSpeedNum'
+    elif provider == 'python':
+        # we expect Python programs to use the InfoEx data type names
+        iemap['precipitationGauge'] = 'precipitationGauge'
+        iemap['tempPres'] = 'tempPres'
+        iemap['tempMaxHour'] = 'tempMaxHour'
+        iemap['tempMinHour'] = 'tempMinHour'
+        iemap['hS'] = 'hS'
+        iemap['baro'] = 'baro'
+        iemap['rH'] = 'rH'
+        iemap['windSpeedNum'] = 'windSpeedNum'
+        iemap['windDirectionNum'] = 'windDirectionNum'
+        iemap['windGustSpeedNum'] = 'windGustSpeedNum'
 
     return iemap