From 23a0d409788d7a201a581a96ebdea99e119e255e Mon Sep 17 00:00:00 2001 From: Olof Harrysson Date: Wed, 23 Dec 2020 22:33:52 +0100 Subject: [PATCH] Move cli-help to config class, default is in print utils. Checks the return type of allowed_cli_args --- anyfig/anyfig_setup.py | 11 +++-------- anyfig/config_functions.py | 5 ++--- anyfig/fields.py | 4 ++-- anyfig/figutils.py | 26 ++++++++++++++++++-------- anyfig/print_utils.py | 21 +++++++++++++++++---- hacking/required_cli.py | 33 ++++++++++++++++++--------------- notes.txt | 10 ++++++---- 7 files changed, 66 insertions(+), 44 deletions(-) diff --git a/anyfig/anyfig_setup.py b/anyfig/anyfig_setup.py index 59f80dd..664fc22 100644 --- a/anyfig/anyfig_setup.py +++ b/anyfig/anyfig_setup.py @@ -36,13 +36,8 @@ def init_config(default_config, cli_args=None): fields.validate_fields(config) # Print config help - if 'help' in cli_args: - config_classes = list(figutils.get_config_classes()) - print( - f"Available config classes {config_classes}. Set config with --config=OtherConfigClass\n", - f"\nCurrent config is '{config_str}'. The available input arguments are") - - print(config.comments_string()) + if 'help' in cli_args or 'h' in cli_args: + print(config.cli_help()) sys.exit(0) # Overwrite parameters via optional input flags @@ -118,7 +113,7 @@ def overwrite(main_config_obj, args): assert hasattr(config_obj, key_part), err_msg # Check if the config allows the argument - figutils.allowed_input_argument(config_obj, key_part, argument_key) + figutils.check_allowed_input_argument(config_obj, key_part, argument_key) # Check if the outer attributes are config classes if key_idx < len(outer_keys): diff --git a/anyfig/config_functions.py b/anyfig/config_functions.py index 7794c09..f5a0c3e 100644 --- a/anyfig/config_functions.py +++ b/anyfig/config_functions.py @@ -22,9 +22,8 @@ def post_init(self): ''' A function that is called after overwriting from command line input ''' pass - def comments_string(self): - ''' Returns string for config class's attributes and comments ''' - return print_utils.comments_string(self) + def cli_help(self): + return print_utils.cli_help(self) def frozen(self, freeze=True): ''' Freeze/unfreeze config ''' diff --git a/anyfig/fields.py b/anyfig/fields.py index 93982b6..498914d 100644 --- a/anyfig/fields.py +++ b/anyfig/fields.py @@ -28,8 +28,8 @@ def validate_fields(config): for key, val in vars(config).items(): if type(val) is InterfaceField: # Don't check InputField or ConstantField err_msg = ( - f"Missing value for the 'anyfig.field' named '{key}' in config '{type(config).__name__}'. " - "Set a value or use 'anyfig.cli_input' for input arguments without default values" + f"Missing value for '{key}' in config '{type(config).__name__}'. " + "Set a value or change the type to 'anyfig.cli_input' to allow input arguments without default values" ) assert hasattr(val, 'value'), err_msg diff --git a/anyfig/figutils.py b/anyfig/figutils.py index 157d75a..0d11bd8 100644 --- a/anyfig/figutils.py +++ b/anyfig/figutils.py @@ -1,7 +1,8 @@ import inspect -import dill - from pathlib import Path +from collections.abc import Iterable + +import dill registered_config_classes = {} global_configs = {} @@ -119,21 +120,30 @@ def find_arguments(callable_): return list(parameters), required_args -def allowed_input_argument(config_obj, name, deep_name): +def check_allowed_input_argument(config_obj, name, deep_name): ''' Raises error if the input argument isn't marked as "allowed" ''' - allowed_args = check_allowed_cli_args(config_obj) + allowed_args = get_allowed_cli_args(config_obj) if name not in allowed_args: err_msg = f"Input argument '{deep_name}' is not allowed to be overwritten. See --help for more info" raise ValueError(err_msg) -def check_allowed_cli_args(config_obj): +def get_allowed_cli_args(config_obj): ''' Returns the attribute names that can be be overwritten from command line input. - Raises AttributeError if an attribute doesn't exist ''' + Raises AttributeError if an attribute doesn't exist ''' allowed_items = config_obj.allowed_cli_args() - attr = config_obj.get_parameters() + if allowed_items is None: + allowed_items = [] + if isinstance(allowed_items, str): + allowed_items = [allowed_items] + err_msg = ( + f"Expected return type 'String, None or Iterable' for {type(config_obj).__name__}'s allowed_cli_args method, " + f"was {allowed_items} with type {type(allowed_items)}") + assert isinstance(allowed_items, Iterable), err_msg + + attributes = config_obj.get_parameters() for item in allowed_items: - if item not in attr: + if item not in attributes: err_msg = ( f"'{type(config_obj).__name__}' has no attribute '{item}' and should not be marked as an allowed command line " "input argument") diff --git a/anyfig/print_utils.py b/anyfig/print_utils.py index 74b649c..4fff178 100644 --- a/anyfig/print_utils.py +++ b/anyfig/print_utils.py @@ -5,8 +5,8 @@ from .fields import InputField, InterfaceField -def comments_string(config_obj): - ''' Returns a "help" string for the config object that contain attributes and any matching comments ''' +def cli_help(config_obj): + ''' Returns string for config's cli-arguments with corresponding comments ''' comments = extract_config_obj_comments(config_obj) indent_width = 4 # In spaces @@ -47,7 +47,20 @@ def comments_string(config_obj): help_string = f"{attr_string}{' ' * (n_spaces - len(attr_string))}{comment}" help_strings.append(help_string) - return '\n'.join(help_strings) + # Add header info + cli_help_header = [] + config_classes = list(figutils.get_config_classes()) + if len(config_classes) > 1: + header = ( + f"Current config is '{type(config_obj).__name__}'. Available config classes {config_classes}. " + "Set config with --config=OtherConfigClass") + cli_help_header.append(header) + + if help_strings: + cli_help_header.append("{}The available input arguments are".format( + '\n' if cli_help_header else '')) + + return '\n'.join(cli_help_header + help_strings) def extract_config_obj_comments(config_obj): @@ -56,7 +69,7 @@ def extract_config_obj_comments(config_obj): comments = _extract_comments(type(config_obj)) # Remove the keys that aren't allowed from command line input - allowed_cli_args = figutils.check_allowed_cli_args(config_obj) + allowed_cli_args = figutils.get_allowed_cli_args(config_obj) comments = {k: v for k, v in comments.items() if k in allowed_cli_args} flat_comments = {} diff --git a/hacking/required_cli.py b/hacking/required_cli.py index 744eb05..f1001a0 100644 --- a/hacking/required_cli.py +++ b/hacking/required_cli.py @@ -3,6 +3,7 @@ from pathlib import Path import time import typing +from anyfig import print_utils @anyfig.config_class # Registers the class with anyfig @@ -16,8 +17,20 @@ def __init__(self): # The inner config obj self.innerfig = InnerConfig() - def post_init(self): - print(type(self).__name__) + # self.help = 'heeelp' + + # def allowed_cli_args(self): + # pass + + # def cli_help(self): + # hej = "YOOOLLOOF" + # cmt = print_utils.cli_help(self) + # return hej + cmt + # return "YOOOLLOOF" + + # return ['start_time1'] + # return 'start_time', 'innerfig' + # return 'start_time' @anyfig.config_class @@ -26,9 +39,10 @@ def __init__(self): # An integer between the values of 1 and 10 because the world has never seen such apples self.inner = 'innner' + self.inner2 = 'innner2' - def post_init(self): - print(type(self).__name__) + def allowed_cli_args(self): + return 11 class InnerConfig2: @@ -37,16 +51,5 @@ def __init__(self): self.inner = 'innner2' -# tt = typing.Union[Path, str] -# tt = str -# print(tt) -# print(type(tt)) config = anyfig.init_config(default_config=MyConfig) print(config) - -# config.frozen(False) -# config.start_time = 123 -# print(config) - -# config.innerfig.inner = 'HEEJ' -# print(config) diff --git a/notes.txt b/notes.txt index f2735eb..67818fe 100644 --- a/notes.txt +++ b/notes.txt @@ -4,6 +4,7 @@ Versioning support - I'd like to create a way to keep track of / load old config Can I use the "with" statement to maybe say with Anyfig.OnlyOverwrite(self) or something to make sure that people don't add new attributes in configs instead of overwritting existing attributes? +"with" statement to set config attributes without having to set frozen(False) then back to frozen(True) Test frozen for nested obejcts @@ -11,15 +12,15 @@ Move _frozen and other default attributes to a function so it's defined at one p license as a button in github -when printing available classes in setup, only print the class, not the name. - Make target classes have a special attribute that holds the dict arguments? Good is that you can put other stiff in there, but bad is that it becomes another thing you need to remember about anyfig - type for target class for isinstance check +Assume unchanged instead of that ignore thing in git for user configs? It had some problems when changing branches as git couldn't handle. --help doesn't work with type hints +Don't allow --help (or -h in config) + ~~~~~~ WEBSITE ~~~~~~ Style external links @@ -32,4 +33,5 @@ Some sort of info on the main page. dict cli-arguments readme cli_input readme readme save/load. Also write why its difficult -post_init readme. Is this feature done? +post_init readme. Is this feature done? post init can be done to validate config or somethink +allowed_cli_args readme