Skip to content

Commit

Permalink
Implement configuration loading for commands / plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
led02 committed Feb 28, 2024
1 parent 01aa7be commit 966f64a
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 18 deletions.
54 changes: 37 additions & 17 deletions src/hermes/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import logging
import pathlib
from importlib import metadata
from typing import Type
from typing import Dict, Optional, Type

from pydantic import BaseModel
import toml

from hermes.settings import HermesSettings


class HermesCommand(abc.ABC):
Expand All @@ -21,7 +23,7 @@ class HermesCommand(abc.ABC):
"""

command_name: str = ""
settings_class: Type = BaseModel
settings_class: Type = HermesSettings

def __init__(self, parser: argparse.ArgumentParser):
"""Initialize a new instance of any HERMES command.
Expand All @@ -30,6 +32,7 @@ def __init__(self, parser: argparse.ArgumentParser):
"""
self.parser = parser
self.plugins = self.init_plugins()
self.settings = None

self.log = logging.getLogger(f"hermes.{self.command_name}")

Expand All @@ -44,28 +47,30 @@ def init_plugins(self):
}

# Collect the plug-in specific configurations
plugin_settings = {
plugin_name: getattr(plugin_class, "settings_class", None)
self.derive_settings_class({
plugin_name: plugin_class.settings_class
for plugin_name, plugin_class in group_plugins.items()
}
if hasattr(plugin_class, "settings_class") and plugin_class.settings_class is not None
})

return group_plugins

@classmethod
def derive_settings_class(cls, setting_types: Dict[str, Type]) -> None:
# Derive a new settings model class that contains all the plug-in extensions
self.settings_class = type(
f"{self.__class__.__name__}Settings",
(self.settings_class,),
cls.settings_class = type(
f"{cls.__name__}Settings",
(cls.settings_class, ),
{
**{
plugin_name: plugin_settings()
for plugin_name, plugin_settings in plugin_settings.items()
for plugin_name, plugin_settings in setting_types.items()
if plugin_settings is not None
},

"__annotations__": plugin_settings,
"__annotations__": setting_types,
},
)

return group_plugins

def init_common_parser(self, parser: argparse.ArgumentParser) -> None:
"""Initialize the common command line arguments available for all HERMES sub-commands.
Expand All @@ -82,15 +87,17 @@ def init_common_parser(self, parser: argparse.ArgumentParser) -> None:
help="Configuration file in TOML format",
)

plugin_args = parser.add_argument_group("Extra plug-in options")
plugin_args = parser.add_argument_group("Extra options")
plugin_args.add_argument(
"-O",
nargs=2,
action="append",
default=[],
metavar=("NAME", "VALUE"),
dest="options",
help="Configuration values to override hermes.toml options. "
"NAME is the dotted name / path to the option in the TOML file."
"VALUE the actual value.",
"NAME is the dotted name / path to the option in the TOML file, "
"VALUE is the actual value.",
)

def init_command_parser(self, command_parser: argparse.ArgumentParser) -> None:
Expand All @@ -104,6 +111,19 @@ def init_command_parser(self, command_parser: argparse.ArgumentParser) -> None:

pass

def load_settings(self, args: argparse.Namespace):
toml_data = toml.load(args.path / args.config)
root_settings = HermesCommand.settings_class.model_validate(toml_data)
self.settings = getattr(root_settings, self.command_name)

def patch_settings(self, args: argparse.Namespace):
for key, value in args.options:
target = self.settings
sub_keys = key.split()
for sub_key in sub_keys[:-1]:
target = getattr(target, sub_key)
setattr(target, sub_keys[-1], value)

@abc.abstractmethod
def __call__(self, args: argparse.Namespace):
"""Execute the HERMES sub-command.
Expand Down
5 changes: 4 additions & 1 deletion src/hermes/commands/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ def main() -> None:
command.init_common_parser(command_parser)
command.init_command_parser(command_parser)

# Actually parse the commands and execute the selected sub-command.
# Construct the Pydantic Settings root model
HermesCommand.derive_settings_class(setting_types)

# Actually parse the command line, configure it and execute the selected sub-command.
args = parser.parse_args()
args.command(args)

0 comments on commit 966f64a

Please sign in to comment.