diff --git a/python-naive/examples/rich-and-config/rich-and-config.py b/python-naive/examples/rich-and-config/rich-and-config.py index 724e811..ab5aca7 100644 --- a/python-naive/examples/rich-and-config/rich-and-config.py +++ b/python-naive/examples/rich-and-config/rich-and-config.py @@ -17,17 +17,20 @@ class CONST(object): CFG_THIS_FILE_DIRNAME = os.path.dirname(__file__) CFG_DEFAULT_FILENAME = "config.ini" CFG_DEFAULT_ABS_PATH = os.path.join(CFG_THIS_FILE_DIRNAME, CFG_DEFAULT_FILENAME) - # Values you don't have to set, these are their internal defaults + # Values you don't have to set, these are their internal defaults. You may optionally add a key 'is_global' equal + # to either True or False. By default if left off it'll be assumed False. Script will treat values where + # 'is_global' equals True as not being overridable in a '[section]'. It's a setting that only makes sense in a + # global context for the entire script. CFG_KNOWN_DEFAULTS = [ {"key": "self_name", "value": "rich-and-config"}, {"key": "tmp_base_dir", "value": os.path.join(CFG_THIS_FILE_DIRNAME, "data/tmp/%(self_name)s")}, {"key": "state_base_dir", "value": os.path.join(CFG_THIS_FILE_DIRNAME, "data/var/lib/%(self_name)s")}, - {"key": "state_files_dir", "value": "%(state_base_dir)s/state"}, - {"key": "state_file_retention", "value": "50"}, - {"key": "state_file_name_prefix", "value": "state-"}, - {"key": "state_file_name_suffix", "value": ".log"}, - {"key": "rich_and_config_some_option", "value": "http://localhost:8000/api/query"}, - {"key": "another_option", "value": "first"} + {"key": "state_files_dir", "value": "%(state_base_dir)s/state", "is_global": False}, + {"key": "state_file_retention", "value": "50", "is_global": False}, + {"key": "state_file_name_prefix", "value": "state-", "is_global": False}, + {"key": "state_file_name_suffix", "value": ".log", "is_global": False}, + {"key": "rich_and_config_some_option", "value": "http://localhost:8000/api/query", "is_global": True}, + {"key": "another_option", "value": "first", "is_global": True} ] # In all sections other than 'default' the following settings are known and accepted. We silently ignore other # settings. We use 'is_mandatory' to determine if we have to raise errors on missing settings. @@ -76,6 +79,7 @@ class ConfigParser( ini_defaults = [] internal_defaults = {default["key"]: default["value"] for default in CONST.CFG_KNOWN_DEFAULTS} +internal_globals = [default["key"] for default in CONST.CFG_KNOWN_DEFAULTS if default["is_global"]] config = ConfigParser(defaults=internal_defaults) config.read(CONST.CFG_DEFAULT_ABS_PATH) @@ -93,14 +97,19 @@ def validate_default_section( sys.exit(1) if config.defaults(): log.debug(f"Symbol legend:\n" - f"* Global default from section '[{config_obj.default_section}]'\n" + f"* Default from section '[{config_obj.default_section}]'\n" + f": Global option from '[{config_obj.default_section}]', can not be overridden in local sections\n" f"~ Local option, doesn't exist in '[{config_obj.default_section}]'\n" f"+ Local override of a value from '[{config_obj.default_section}]'\n" - f"= Local override, same value as in '[{config_obj.default_section}]'") + f"= Local override, same value as in '[{config_obj.default_section}]'\n" + f"# Local attempt at overriding a global, will be ignored") log.debug(print_section_header(config_obj.default_section)) for default in config_obj.defaults(): ini_defaults.append({default: config_obj[config_obj.default_section][default]}) - log.debug(f"* {default} = {config_obj[config_obj.default_section][default]}") + if default in internal_globals: + log.debug(f": {default} = {config_obj[config_obj.default_section][default]}") + else: + log.debug(f"* {default} = {config_obj[config_obj.default_section][default]}") else: log.debug(f"No defaults defined") @@ -120,6 +129,11 @@ def is_default( return any(config_key in ini_default for ini_default in ini_defaults) +def is_global( + config_key: str) -> bool: + return config_key in internal_globals + + def is_same_as_default( config_kv_pair: dict) -> bool: return config_kv_pair in ini_defaults @@ -136,11 +150,17 @@ def validate_config_sections( else: for key in config_obj.options(this_section, no_defaults=True): kv_prefix = "~" - if is_default(key): + remove_from_section = False + if is_global(key): + kv_prefix = "#" + remove_from_section = True + elif is_default(key): kv_prefix = "+" if is_same_as_default({key: config_obj[this_section][key]}): kv_prefix = "=" log.debug(f"{kv_prefix} {key} = {config_obj[this_section][key]}") + if remove_from_section: + config_obj.remove_option(this_section, key) def an_important_function( @@ -164,4 +184,4 @@ if __name__ == '__main__': log.debug(f"Iterating over config sections ...") for section in config.sections(): log.debug(f"Processing section '[{section}]' ...") - # ...config.ini.example + # ... diff --git a/python-naive/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__project_slug }}.py b/python-naive/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__project_slug }}.py index d1f368b..2e11309 100644 --- a/python-naive/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__project_slug }}.py +++ b/python-naive/{{ cookiecutter.__project_slug }}/{{ cookiecutter.__project_slug }}.py @@ -26,17 +26,20 @@ class CONST(object): CFG_THIS_FILE_DIRNAME = os.path.dirname(__file__) CFG_DEFAULT_FILENAME = "config.ini" CFG_DEFAULT_ABS_PATH = os.path.join(CFG_THIS_FILE_DIRNAME, CFG_DEFAULT_FILENAME) - # Values you don't have to set, these are their internal defaults + # Values you don't have to set, these are their internal defaults. You may optionally add a key 'is_global' equal + # to either True or False. By default if left off it'll be assumed False. Script will treat values where + # 'is_global' equals True as not being overridable in a '[section]'. It's a setting that only makes sense in a + # global context for the entire script. CFG_KNOWN_DEFAULTS = [ {"key": "self_name", "value": "{{ cookiecutter.__project_slug }}"}, {"key": "tmp_base_dir", "value": os.path.join(CFG_THIS_FILE_DIRNAME, "data/tmp/%(self_name)s")}, {"key": "state_base_dir", "value": os.path.join(CFG_THIS_FILE_DIRNAME, "data/var/lib/%(self_name)s")}, - {"key": "state_files_dir", "value": "%(state_base_dir)s/state"}, - {"key": "state_file_retention", "value": "50"}, - {"key": "state_file_name_prefix", "value": "state-"}, - {"key": "state_file_name_suffix", "value": ".log"}, - {"key": "{{ cookiecutter.__project_slug_under }}_some_option", "value": "http://localhost:8000/api/query"}, - {"key": "another_option", "value": "first"} + {"key": "state_files_dir", "value": "%(state_base_dir)s/state", "is_global": False}, + {"key": "state_file_retention", "value": "50", "is_global": False}, + {"key": "state_file_name_prefix", "value": "state-", "is_global": False}, + {"key": "state_file_name_suffix", "value": ".log", "is_global": False}, + {"key": "{{ cookiecutter.__project_slug_under }}_some_option", "value": "http://localhost:8000/api/query", "is_global": True}, + {"key": "another_option", "value": "first", "is_global": True} ] # In all sections other than 'default' the following settings are known and accepted. We silently ignore other # settings. We use 'is_mandatory' to determine if we have to raise errors on missing settings. @@ -89,6 +92,7 @@ class ConfigParser( ini_defaults = [] internal_defaults = {default["key"]: default["value"] for default in CONST.CFG_KNOWN_DEFAULTS} +internal_globals = [default["key"] for default in CONST.CFG_KNOWN_DEFAULTS if default["is_global"]] config = ConfigParser(defaults=internal_defaults) config.read(CONST.CFG_DEFAULT_ABS_PATH) @@ -106,14 +110,19 @@ def validate_default_section( sys.exit(1) if config.defaults(): {% if cookiecutter.rich_logging == "yes" -%}log.debug{%- else -%}print{%- endif %}(f"Symbol legend:\n" - {% if cookiecutter.rich_logging == "yes" %} {% endif %}f"* Global default from section '[{config_obj.default_section}]'\n" + {% if cookiecutter.rich_logging == "yes" %} {% endif %}f"* Default from section '[{config_obj.default_section}]'\n" + {% if cookiecutter.rich_logging == "yes" %} {% endif %}f": Global option from '[{config_obj.default_section}]', can not be overridden in local sections\n" {% if cookiecutter.rich_logging == "yes" %} {% endif %}f"~ Local option, doesn't exist in '[{config_obj.default_section}]'\n" {% if cookiecutter.rich_logging == "yes" %} {% endif %}f"+ Local override of a value from '[{config_obj.default_section}]'\n" - {% if cookiecutter.rich_logging == "yes" %} {% endif %}f"= Local override, same value as in '[{config_obj.default_section}]'") + {% if cookiecutter.rich_logging == "yes" %} {% endif %}f"= Local override, same value as in '[{config_obj.default_section}]'\n" + {% if cookiecutter.rich_logging == "yes" %} {% endif %}f"# Local attempt at overriding a global, will be ignored") {% if cookiecutter.rich_logging == "yes" -%}log.debug{%- else -%}print{%- endif %}(print_section_header(config_obj.default_section)) for default in config_obj.defaults(): ini_defaults.append({default: config_obj[config_obj.default_section][default]}) - {% if cookiecutter.rich_logging == "yes" -%}log.debug{%- else -%}print{%- endif %}(f"* {default} = {config_obj[config_obj.default_section][default]}") + if default in internal_globals: + {% if cookiecutter.rich_logging == "yes" -%}log.debug{%- else -%}print{%- endif %}(f": {default} = {config_obj[config_obj.default_section][default]}") + else: + {% if cookiecutter.rich_logging == "yes" -%}log.debug{%- else -%}print{%- endif %}(f"* {default} = {config_obj[config_obj.default_section][default]}") else: {% if cookiecutter.rich_logging == "yes" -%}log.debug{%- else -%}print{%- endif %}(f"No defaults defined") @@ -133,6 +142,11 @@ def is_default( return any(config_key in ini_default for ini_default in ini_defaults) +def is_global( + config_key: str) -> bool: + return config_key in internal_globals + + def is_same_as_default( config_kv_pair: dict) -> bool: return config_kv_pair in ini_defaults @@ -149,11 +163,17 @@ def validate_config_sections( else: for key in config_obj.options(this_section, no_defaults=True): kv_prefix = "~" - if is_default(key): + remove_from_section = False + if is_global(key): + kv_prefix = "#" + remove_from_section = True + elif is_default(key): kv_prefix = "+" if is_same_as_default({key: config_obj[this_section][key]}): kv_prefix = "=" {% if cookiecutter.rich_logging == "yes" -%}log.debug{%- else -%}print{%- endif %}(f"{kv_prefix} {key} = {config_obj[this_section][key]}") + if remove_from_section: + config_obj.remove_option(this_section, key) {%- endif %}