Source code for masterpiece.argmaestro
"""
Automated class configuration through startup arguments.
Author: Juha Meskanen
Date: 2024-10-26
"""
import argparse
from typing import Type, Union, get_type_hints
from .masterpiece import MasterPiece
[docs]
class ArgMaestro:
"""
Automated class configuration through startup arguments.
ArgMaestro manages startup argument creation and parsing for the class attribute initialization
of the registered classes. Each startup argument is of form '--[app]_[class]_[attr]'.
Note: currently supports only int, str, bool and float types.
"""
[docs]
def __init__(self) -> None:
"""Construct ArgMaestro with a single argument parser"""
self.parser = argparse.ArgumentParser(description="Application arguments")
self.args: dict[str, Type[MasterPiece]] = {}
[docs]
def add_class_arguments(self, clazz: Type[MasterPiece]) -> None:
"""Create startup arguments for a class's attributes.
Args:
clazz (class): The class to generate arguments for.
"""
type_hints = get_type_hints(clazz)
self.args[clazz.__name__] = clazz # Store the class for future use
# Filter attributes to ensure only class-specific ones are considered
for attr, attr_type in type_hints.items():
try:
if not attr.startswith("_") and not callable(
getattr(clazz, attr, None)
):
# Skip attributes that are not in the class's own __dict__
if attr not in clazz.__dict__:
continue
# Check attribute type and set default if necessary
default_value = getattr(clazz, attr, None)
if default_value is None:
# Assign logical defaults if None is found
if attr_type is float:
default_value = 0.0
elif attr_type is int:
default_value = 0
elif attr_type is str:
default_value = ""
# Argument type handling
arg_type: Type[Union[int, float, bool, str]]
if attr_type is int:
arg_type = int
elif attr_type is float:
arg_type = float
elif attr_type is bool:
self.parser.add_argument(
f"--{clazz.__name__.lower()}_{attr}",
action="store_true",
help=f"Enable or disable {attr} in {clazz.__name__}",
)
print(f"--{clazz.__name__.lower()}_{attr}")
continue # Boolean flag added without needing a default or type
else:
arg_type = str # Default to string for unspecified types
self.parser.add_argument(
f"--{clazz.__name__.lower()}_{attr}",
type=arg_type,
default=default_value,
help=f"Set {attr} in {clazz.__name__} (type: {attr_type.__name__})",
)
print(
f"--{clazz.__name__.lower()}_{attr}, type:{arg_type} default: {default_value}"
)
except Exception as e:
print(f"Error {e} in parsing argument {attr}")
[docs]
def parse_args(self) -> None:
"""Parse arguments and assign values to each class's attributes."""
# print(self.parser.format_help())
args = self.parser.parse_args()
args_dict = vars(args)
for class_name, clazz in self.args.items():
# Assign parsed values to class attributes
for arg, value in args_dict.items():
prefix = class_name.lower() + "_"
if arg.startswith(prefix):
attr = arg[len(prefix) :]
setattr(clazz, attr, value)