2024-06-27 20:08:41 +02:00

144 lines
5.4 KiB
Python

import json
import click
from pathlib import Path
import logging
from logging.handlers import RotatingFileHandler
import sys
from iottb.contexts import IottbConfig
APP_NAME = 'iottb'
DB_NAME = 'iottb.db'
CFG_FILE_PATH = str(Path(click.get_app_dir(APP_NAME)).joinpath('iottb.cfg'))
CONSOLE_LOG_FORMATS = {
0: '%(levelname)s - %(message)s',
1: '%(levelname)s - %(module)s - %(message)s',
2: '%(levelname)s - %(asctime)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
}
LOGFILE_LOG_FORMAT = {
0: '%(levelname)s - %(asctime)s - %(module)s - %(message)s',
1: '%(levelname)s - %(asctime)s - %(module)s - %(funcName)s - %(message)s',
2: '%(levelname)s - %(asctime)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
}
MAX_VERBOSITY = len(CONSOLE_LOG_FORMATS) - 1
assert len(LOGFILE_LOG_FORMAT) == len(CONSOLE_LOG_FORMATS), 'Log formats must be same size'
logger = logging.getLogger(__name__)
def setup_logging(verbosity, debug):
""" Setup root logger for iottb """
log_level = logging.ERROR
handlers = []
date_format = '%Y-%m-%d %H:%M:%S'
if verbosity > 0:
log_level = logging.WARNING
if verbosity > MAX_VERBOSITY:
verbosity = MAX_VERBOSITY
log_level = logging.INFO
assert verbosity <= MAX_VERBOSITY, f'Verbosity must be <= {MAX_VERBOSITY}'
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter(CONSOLE_LOG_FORMATS[verbosity], datefmt=date_format))
console_handler.setLevel(logging.DEBUG) # can keep at debug since it depends on global level?
handlers.append(console_handler)
if debug:
log_level = logging.DEBUG
# Logfile logs INFO+, no debugs though
file_handler = RotatingFileHandler(f'{APP_NAME}.log', maxBytes=10240, backupCount=5)
file_handler.setFormatter(logging.Formatter(LOGFILE_LOG_FORMAT[verbosity], datefmt=date_format))
file_handler.setLevel(logging.INFO)
# finnish root logger setup
handlers.append(file_handler)
# Force this config to be applied to root logger
logging.basicConfig(level=log_level, handlers=handlers, force=True)
def create_default_config(cfg_file):
"""Create default iottb config file."""
logger.info(f'Creating default config file at {cfg_file}')
defaults = {
'DefaultDatabase': DB_NAME,
'DefaultDatabasePath': str(Path.home())
}
try:
cfg_file.parent.mkdir(exist_ok=True)
with cfg_file.open('w') as config_file:
defaults_dict = json.dumps(defaults, indent=4)
config_file.write(defaults_dict)
except IOError as e:
logger.error(f"Failed to create default configuration file at {cfg_file}: {e}")
raise RuntimeError(f"Failed to create configuration file: {e}") from e
return defaults
def load_config(cfg_file):
"""Loads or creates default configuration from given file path."""
logger.info("Loading iottb config file")
if not cfg_file.is_file():
return create_default_config(cfg_file)
with open(cfg_file, 'r') as config_file:
return json.load(config_file)
@click.group()
@click.option('-v', '--verbosity', count=True, type=click.IntRange(0, 3), default=0,
help='Set verbosity')
@click.option('-d', '--debug', is_flag=True, default=False,
help='Enable debug mode')
@click.option('--cfg-file', type=click.Path(),
default=Path(click.get_app_dir(APP_NAME)).joinpath('iottb.cfg'),
envvar='IOTTB_CONF_HOME', help='Path to iottb config file')
@click.pass_context
def cli(ctx, verbosity, debug, cfg_file):
ctx.ensure_object(dict) # Make sure context is ready for use
logger.info("Starting execution.")
ctx.obj['CONFIG'] = IottbConfig(cfg_file) # Load configuration directly
setup_logging(verbosity, debug) # Setup logging based on the loaded configuration and other options
@click.command()
@click.option('-d', '--dest', default=str(Path.home()), type=str, help='Location to put (new) iottb database')
@click.option('--name', default=DB_NAME, type=str, help='Name of new database.')
@click.option('--update-default/ --no-update-default', default=True,
help='If new db should be set as the new default')
@click.pass_context
def init_db(ctx, dest, name, update_default):
logger.debug('TOP init_db')
@click.command
@click.option('--obj', '--object', type=click.Choice(['cfg', 'db', 'dev', 'cap']),
help='Type of file to edit.')
@click.option('--file', type=click.Choice(['iottb.cfg', '']))
def edit(ctx, file, table, key, value):
"""Edit config or metadata files. TODO: Implement"""
pass
@click.command
@click.confirmation_option(prompt="Are you certain that you want to delete the cfg file?")
def rm_cfg():
""" Removes the cfg file from the filesystem.
This is mostly a utility during development. Once non-standard database locations are implemented,
deleting this would lead to iottb not being able to find them anymore.
"""
Path(CFG_FILE_PATH).unlink()
click.echo(f'Iottb configuration removed at {CFG_FILE_PATH}')
##################################################################################
# Add all subcommands to group here
#################################################################################
cli.add_command(init_db)
cli.add_command(rm_cfg)
cli.add_command(edit)
if __name__ == '__main__':
cli(auto_envvar_prefix='IOTTB')