diff --git a/bin/xmet-afos-ingest b/bin/xmet-afos-ingest index 548021d..4fa8026 100755 --- a/bin/xmet-afos-ingest +++ b/bin/xmet-afos-ingest @@ -2,22 +2,24 @@ import argparse -from xmet.db import Database -from xmet.afos import AFOSMessageParser -from xmet.util import each_chunk +from xmet.config import Config +from xmet.db import Database +from xmet.afos import AFOSMessageParser +from xmet.util import each_chunk +parser = argparse.ArgumentParser( description = 'Ingest National Weather Service text bulletin products' ) parser.add_argument('--quiet', action='store_true', help='Suppress output') parser.add_argument('--dry-run', action='store_true', help='Do not actually ingest products') -parser.add_argument('db', help='XMET SQLite3 database') parser.add_argument('afos-text-file', help='AFOS text bulletin product file') args = parser.parse_args() -db = Database.connect(args.db) +config = Config.load() +db = Database.from_config(config) db.execute('begin transaction') parser = AFOSMessageParser() diff --git a/bin/xmet-event-ingest b/bin/xmet-event-ingest index 4bb8090..37fe7d4 100755 --- a/bin/xmet-event-ingest +++ b/bin/xmet-event-ingest @@ -2,8 +2,9 @@ import argparse -from xmet.db import Database -from xmet.storm import StormEvent +from xmet.config import Config +from xmet.db import Database +from xmet.storm import StormEvent parser = argparse.ArgumentParser( description = 'Ingest events from StormEvent_details_*.csv.gz files' @@ -11,12 +12,12 @@ parser = argparse.ArgumentParser( parser.add_argument('--quiet', action='store_true', help='Suppress output') parser.add_argument('--dry-run', action='store_true', help='Do not actually ingest events') -parser.add_argument('db', help='XMET SQLite3 database') parser.add_argument('csv-event-details', nargs='+', help='Compressed storm event details CSV file') args = parser.parse_args() -db = Database.connect(args.db) +config = Config.load() +db = Database.from_config(config) if not args.dry_run: db.execute('begin transaction') diff --git a/bin/xmet-igra-ingest b/bin/xmet-igra-ingest index 1249dbb..0ebfb41 100755 --- a/bin/xmet-igra-ingest +++ b/bin/xmet-igra-ingest @@ -2,8 +2,9 @@ import argparse -from xmet.db import Database -from xmet.igra import IGRAReader +from xmet.config import Config +from xmet.db import Database +from xmet.igra import IGRAReader parser = argparse.ArgumentParser( description = 'Ingest IGRA soundings' @@ -17,7 +18,8 @@ parser.add_argument('igra-sounding-file', nargs='+', help='IGRA sounding file') args = parser.parse_args() -db = Database.connect(args.db) +config = Config.load() +db = Database.from_config(config) if not args.dry_run: db.execute('begin transaction') diff --git a/bin/xmet-nexrad-archive b/bin/xmet-nexrad-archive index 1211252..3f1b777 100755 --- a/bin/xmet-nexrad-archive +++ b/bin/xmet-nexrad-archive @@ -2,6 +2,7 @@ import argparse +from xmet.config import Config from xmet.db import Database from xmet.s3 import S3Bucket from xmet.storm import StormEvent @@ -18,13 +19,13 @@ group = parser.add_mutually_exclusive_group() group.add_argument('--exclude', action='append', type=str, help='Exclude types of events from ingest') group.add_argument('--type', action='append', type=str, help='Specify only given types of events to ingest') -parser.add_argument('db', help='XMET SQLite3 database') parser.add_argument('csv-event-details', nargs='+', help='Compressed storm event details CSV file') parser.add_argument('archive-dir', help='Target archive directory') args = parser.parse_args() -db = Database.connect(args.db) +config = Config.load() +db = Database.from_config(config) bucket = S3Bucket() archive = Archive(getattr(args, 'archive-dir'), bucket) exclude = None diff --git a/bin/xmet-raob-ingest b/bin/xmet-raob-ingest index 8f9364b..bffa9c2 100755 --- a/bin/xmet-raob-ingest +++ b/bin/xmet-raob-ingest @@ -2,9 +2,10 @@ import argparse -from xmet.db import Database -from xmet.raob import RAOBReader -from xmet.igra import IGRAStation +from xmet.config import Config +from xmet.db import Database +from xmet.raob import RAOBReader +from xmet.igra import IGRAStation parser = argparse.ArgumentParser( description = 'Ingest RAOB soundings' @@ -13,12 +14,12 @@ parser = argparse.ArgumentParser( parser.add_argument('--quiet', action='store_true', help='Suppress output') parser.add_argument('--dry-run', action='store_true', help='Do not actually ingest data') -parser.add_argument('db', help='XMET SQLite3 database') parser.add_argument('raob-sounding-file', nargs='+', help='RAOB sounding file') args = parser.parse_args() -db = Database.connect(args.db) +config = Config.load() +db = Database.from_config(config) if not args.dry_run: db.execute('begin transaction') diff --git a/bin/xmet-sounding-plot b/bin/xmet-sounding-plot index d05bb2e..335597f 100755 --- a/bin/xmet-sounding-plot +++ b/bin/xmet-sounding-plot @@ -4,6 +4,7 @@ import argparse import cairo import shapely +from xmet.config import Config from xmet.db import Database from xmet.sounding import Sounding from xmet.skew_t import SkewTGraph, SkewTLegend @@ -60,14 +61,14 @@ parser = argparse.ArgumentParser( parser.add_argument('--skew-t', type=str, help='Plot a Skew-T chart to the specified SVG file') parser.add_argument('--hodograph', type=str, help='Plot a hodograph to the specified SVG file') -parser.add_argument('db', help='XMET SQLite3 database') parser.add_argument('lat', help='Latitude') parser.add_argument('lon', help='Longitude') parser.add_argument('timestamp', help='Timestamp in YYYY-MM-DD HH:MM:SS (UTC)') args = parser.parse_args() -db = Database.connect(args.db) +config = Config.load() +db = Database.from_config(config) location = shapely.Point(float(args.lon), float(args.lat)) sounding = Sounding.valid_by_location(db, location, args.timestamp) diff --git a/bin/xmet-spc-render-file b/bin/xmet-spc-render-file index 7b34b04..388c2f3 100755 --- a/bin/xmet-spc-render-file +++ b/bin/xmet-spc-render-file @@ -3,7 +3,8 @@ import argparse import cairo -from xmet.db import Database +from xmet.config import Config +from xmet.db import Database from xmet.spc import SPCOutlookParser, \ SPCOutlook, \ @@ -25,7 +26,8 @@ ASSETS = { def render_categorical(conus: SPCOutlookMap, outlook: SPCOutlook, args): - db = Database.connect(args.db) + config = Config.load() + db = Database.from_config(config) assets = ASSETS['dark'] if args.dark else ASSETS['light'] @@ -48,7 +50,8 @@ def render_probabilistic(conus: SPCOutlookMap, hazard: str, path: str, args): - db = Database.connect(args.db) + config = Config.load() + db = Database.from_config(config) assets = ASSETS['dark'] if args.dark else ASSETS['light'] @@ -69,7 +72,6 @@ def render_probabilistic(conus: SPCOutlookMap, conus.draw_annotation(cr, outlook, SPCOutlookType.PROBABILISTIC, hazard) argparser = argparse.ArgumentParser(description='Render graphical SPC outlooks from text file') -argparser.add_argument('db', help='Spatialite database file') argparser.add_argument('--dark', action='store_true', help='Output dark mode graphics') argparser.add_argument('--categorical', type=str, help='Output categorical risk graphic file') argparser.add_argument('--any-severe', type=str, help='Output probabilistic severe risk graphic file') diff --git a/lib/xmet/config.py b/lib/xmet/config.py new file mode 100644 index 0000000..dde6fe2 --- /dev/null +++ b/lib/xmet/config.py @@ -0,0 +1,36 @@ +import os +import configparser + +from typing import Optional, Self + +class ConfigException(Exception): + ... + +class ConfigFileException(ConfigException): + ... + +class Config(configparser.ConfigParser): + SEARCH_PATHS = ( + './xmet.conf', + os.environ['HOME'] + '/.xmet.conf', + '/etc/xmet/xmet.conf', + ) + + @staticmethod + def find() -> str: + for path in Config.SEARCH_PATHS: + if os.path.isfile(path): + return path + + @staticmethod + def load(path: Optional[str]=None) -> Self: + if path is None: + path = Config.find() + + if path is None: + raise ConfigFileException("Could not locate xmet configuration file") + + config = Config() + config.read(path) + + return config diff --git a/lib/xmet/db.py b/lib/xmet/db.py index 90b33fc..d90ac82 100644 --- a/lib/xmet/db.py +++ b/lib/xmet/db.py @@ -1,6 +1,10 @@ import enum import sqlite3 +from typing import Self + +from xmet.config import Config + class DatabaseOrder(enum.Enum): DEFAULT = 0 ASC = 1 @@ -112,6 +116,10 @@ class Database(): return Database(db) + @staticmethod + def from_config(config: Config) -> Self: + return Database.connect(config['database']['path']) + def column_placeholders(self, table, obj) -> list: ret = list()