Implement config file loader

Changes:

* Implement config file loader

* Add config-based database connector
This commit is contained in:
XANTRONIX 2025-04-03 21:47:45 -04:00
parent 86ca31b445
commit f40c1d14c6
9 changed files with 79 additions and 25 deletions

View file

@ -2,22 +2,24 @@
import argparse import argparse
from xmet.config import Config
from xmet.db import Database from xmet.db import Database
from xmet.afos import AFOSMessageParser from xmet.afos import AFOSMessageParser
from xmet.util import each_chunk from xmet.util import each_chunk
parser = argparse.ArgumentParser(
description = 'Ingest National Weather Service text bulletin products' description = 'Ingest National Weather Service text bulletin products'
) )
parser.add_argument('--quiet', action='store_true', help='Suppress output') 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('--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') parser.add_argument('afos-text-file', help='AFOS text bulletin product file')
args = parser.parse_args() args = parser.parse_args()
db = Database.connect(args.db) config = Config.load()
db = Database.from_config(config)
db.execute('begin transaction') db.execute('begin transaction')
parser = AFOSMessageParser() parser = AFOSMessageParser()

View file

@ -2,6 +2,7 @@
import argparse import argparse
from xmet.config import Config
from xmet.db import Database from xmet.db import Database
from xmet.storm import StormEvent from xmet.storm import StormEvent
@ -11,12 +12,12 @@ parser = argparse.ArgumentParser(
parser.add_argument('--quiet', action='store_true', help='Suppress output') 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('--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') parser.add_argument('csv-event-details', nargs='+', help='Compressed storm event details CSV file')
args = parser.parse_args() args = parser.parse_args()
db = Database.connect(args.db) config = Config.load()
db = Database.from_config(config)
if not args.dry_run: if not args.dry_run:
db.execute('begin transaction') db.execute('begin transaction')

View file

@ -2,6 +2,7 @@
import argparse import argparse
from xmet.config import Config
from xmet.db import Database from xmet.db import Database
from xmet.igra import IGRAReader from xmet.igra import IGRAReader
@ -17,7 +18,8 @@ parser.add_argument('igra-sounding-file', nargs='+', help='IGRA sounding file')
args = parser.parse_args() args = parser.parse_args()
db = Database.connect(args.db) config = Config.load()
db = Database.from_config(config)
if not args.dry_run: if not args.dry_run:
db.execute('begin transaction') db.execute('begin transaction')

View file

@ -2,6 +2,7 @@
import argparse import argparse
from xmet.config import Config
from xmet.db import Database from xmet.db import Database
from xmet.s3 import S3Bucket from xmet.s3 import S3Bucket
from xmet.storm import StormEvent 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('--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') 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('csv-event-details', nargs='+', help='Compressed storm event details CSV file')
parser.add_argument('archive-dir', help='Target archive directory') parser.add_argument('archive-dir', help='Target archive directory')
args = parser.parse_args() args = parser.parse_args()
db = Database.connect(args.db) config = Config.load()
db = Database.from_config(config)
bucket = S3Bucket() bucket = S3Bucket()
archive = Archive(getattr(args, 'archive-dir'), bucket) archive = Archive(getattr(args, 'archive-dir'), bucket)
exclude = None exclude = None

View file

@ -2,6 +2,7 @@
import argparse import argparse
from xmet.config import Config
from xmet.db import Database from xmet.db import Database
from xmet.raob import RAOBReader from xmet.raob import RAOBReader
from xmet.igra import IGRAStation from xmet.igra import IGRAStation
@ -13,12 +14,12 @@ parser = argparse.ArgumentParser(
parser.add_argument('--quiet', action='store_true', help='Suppress output') 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('--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') parser.add_argument('raob-sounding-file', nargs='+', help='RAOB sounding file')
args = parser.parse_args() args = parser.parse_args()
db = Database.connect(args.db) config = Config.load()
db = Database.from_config(config)
if not args.dry_run: if not args.dry_run:
db.execute('begin transaction') db.execute('begin transaction')

View file

@ -4,6 +4,7 @@ import argparse
import cairo import cairo
import shapely import shapely
from xmet.config import Config
from xmet.db import Database from xmet.db import Database
from xmet.sounding import Sounding from xmet.sounding import Sounding
from xmet.skew_t import SkewTGraph, SkewTLegend 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('--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('--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('lat', help='Latitude')
parser.add_argument('lon', help='Longitude') parser.add_argument('lon', help='Longitude')
parser.add_argument('timestamp', help='Timestamp in YYYY-MM-DD HH:MM:SS (UTC)') parser.add_argument('timestamp', help='Timestamp in YYYY-MM-DD HH:MM:SS (UTC)')
args = parser.parse_args() 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)) location = shapely.Point(float(args.lon), float(args.lat))
sounding = Sounding.valid_by_location(db, location, args.timestamp) sounding = Sounding.valid_by_location(db, location, args.timestamp)

View file

@ -3,6 +3,7 @@
import argparse import argparse
import cairo import cairo
from xmet.config import Config
from xmet.db import Database from xmet.db import Database
from xmet.spc import SPCOutlookParser, \ from xmet.spc import SPCOutlookParser, \
@ -25,7 +26,8 @@ ASSETS = {
def render_categorical(conus: SPCOutlookMap, def render_categorical(conus: SPCOutlookMap,
outlook: SPCOutlook, outlook: SPCOutlook,
args): args):
db = Database.connect(args.db) config = Config.load()
db = Database.from_config(config)
assets = ASSETS['dark'] if args.dark else ASSETS['light'] assets = ASSETS['dark'] if args.dark else ASSETS['light']
@ -48,7 +50,8 @@ def render_probabilistic(conus: SPCOutlookMap,
hazard: str, hazard: str,
path: str, path: str,
args): args):
db = Database.connect(args.db) config = Config.load()
db = Database.from_config(config)
assets = ASSETS['dark'] if args.dark else ASSETS['light'] 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) conus.draw_annotation(cr, outlook, SPCOutlookType.PROBABILISTIC, hazard)
argparser = argparse.ArgumentParser(description='Render graphical SPC outlooks from text file') 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('--dark', action='store_true', help='Output dark mode graphics')
argparser.add_argument('--categorical', type=str, help='Output categorical risk graphic file') 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') argparser.add_argument('--any-severe', type=str, help='Output probabilistic severe risk graphic file')

36
lib/xmet/config.py Normal file
View file

@ -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

View file

@ -1,6 +1,10 @@
import enum import enum
import sqlite3 import sqlite3
from typing import Self
from xmet.config import Config
class DatabaseOrder(enum.Enum): class DatabaseOrder(enum.Enum):
DEFAULT = 0 DEFAULT = 0
ASC = 1 ASC = 1
@ -112,6 +116,10 @@ class Database():
return Database(db) return Database(db)
@staticmethod
def from_config(config: Config) -> Self:
return Database.connect(config['database']['path'])
def column_placeholders(self, table, obj) -> list: def column_placeholders(self, table, obj) -> list:
ret = list() ret = list()