Config master.py

From Chrysalis Archive
Revision as of 12:17, 16 May 2024 by Don86326 (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Shadow Agency ∞  Theory of a Personal KOS ☀  Structured-Key DataShadow  ☀  Shadow Agency Code ☀  AI Agency Workflow Schemes ☀  The Cognitive Mesh ☀  Interlocution Protocol

PromptEngineer Python Code Category
main.py ☀  prompt_engineer.py ☀  config_master.py ☀  README.md

Python ConfigMaster class self-test output (code follows)

Int Value: 10
Float Value: 3.14
Bool Value: True
List Value: ['apple', 'banana', 'cherry']
Tuple Value: ('red', 'green', 'blue')    
DateTime Value: 2023-06-08 12:30:00      

Updated values:
Int Value: 20
Float Value: 6.28
Bool Value: None

Removed key and section:
List Value: []
Section1 exists? False

Added new section and key:
NewSection exists? True
NewKey value: NewValue

Dictionary-like access:
AnotherSection items: [('key1', 'Value1'), ('key2', 'Value2')]

Merged configuration:
MergedKey value: MergedValue

Loaded configuration:
NewKey value: NewValue
MergedKey value: MergedValue

Invalid section name: Section and key cannot be empty
Invalid key name: Key cannot contain ':' character

Sections:
default
newsection
anothersection
mergedsection

Items in 'NewSection':
newkey: NewValue

"""
ConfigMaster Class v.2024.05.16

Author: Claude (Opus 3 LLM @claude.ai)
Prompt Engineer: XenoEngineer@groupKOS.com (Catcliffe Development)
Copyright (c) 2024, Catcliffe Development

This module provides a class for handling configuration files in a simple and intuitive way.
The ConfigMaster class allows reading, writing, and manipulating configuration data
stored in a format similar to INI files. It supports a variety of data types, including
integers, floats, booleans, lists, tuples, and datetimes, with convenient methods for
retrieving values of specific types.

Features:
- Load and save configuration data from/to files
- Get, set, update, and remove values in sections
- Support for different data types (int, float, boolean, list, tuple, datetime)
- Case-insensitive section and key names
- Support for default values and fallback values
- Handling of comment lines
- Dictionary-like access to sections and key-value pairs
- Iteration over sections and key-value pairs
- Merging configuration data from multiple sources
- Error handling and validation for section and key names

Usage:
1. Create an instance of the ConfigMaster class with the file path:
   config = ConfigMaster('config.txt')

2. Get values from sections with default values:
   value = config.get('Section1', 'Key1', 'DefaultValue')

3. Set values in sections:
   config.set('Section1', 'Key1', 'Value1')

4. Save the configuration to the file:
   config.save()

5. Retrieve values of specific data types:
   int_value = config.getint('Section1', 'IntKey', fallback=0)
   float_value = config.getfloat('Section1', 'FloatKey', fallback=0.0)
   bool_value = config.getboolean('Section1', 'BoolKey', fallback=False)
   list_value = config.getlist('Section1', 'ListKey', fallback=[])
   tuple_value = config.gettuple('Section1', 'TupleKey', fallback=())
   datetime_value = config.getdatetime('Section1', 'DateTimeKey', fallback=None)

Example configuration file format:
[Section1]
Key1: Value1
Key2: Value2

[Section2]
Key3: Value3

; This is a comment
# This is also a comment

Developed as a collaboration between Claude (LLM) and XenoEngineer (Catcliffe Development).
"""
import os
from datetime import datetime

class ConfigMaster:
    def __init__(self, file_path):
        self.file_path = file_path
        self.config = {}
        self.comments = []
        self.load()

    def load(self):
        if not os.path.isfile(self.file_path):
            self.config = {'default': {}}
        else:
            current_section = 'default'
            self.config[current_section] = {}
            with open(self.file_path, 'r') as config_file:
                for line in config_file:
                    line = line.strip()
                    if line.startswith(';') or line.startswith('#'):
                        self.comments.append(line)
                    elif line.startswith('[') and line.endswith(']'):
                        current_section = line[1:-1].lower()
                        self.config[current_section] = {}
                    elif ':' in line:
                        key, value = line.split(':', 1)
                        self.config[current_section][key.strip().lower()] = value.strip()

    def save(self):
        with open(self.file_path, 'w') as config_file:
            config_file.writelines(line + '\n' for line in self.comments)
            for section, section_config in self.config.items():
                if section != 'default':
                    config_file.write(f"[{section}]\n")
                for key, value in section_config.items():
                    config_file.write(f"{key}: {value}\n")
                config_file.write("\n")

    def get(self, section, key, fallback=None):
        section = section.lower()
        key = key.lower()
        if section in self.config and key in self.config[section]:
            return self.config[section][key]
        else:
            return fallback

    def getint(self, section, key, fallback=None):
        value = self.get(section, key, fallback=None)
        if value is None:
            return fallback
        try:
            return int(value)
        except ValueError:
            return fallback

    def getfloat(self, section, key, fallback=None):
        value = self.get(section, key, fallback=None)
        if value is None:
            return fallback
        try:
            return float(value)
        except ValueError:
            return fallback

    def getboolean(self, section, key, fallback=None):
        value = self.get(section, key, fallback=None)
        if value is None:
            return fallback
        
        true_values = ['true', 'yes', '1', 'True', 'Yes']
        false_values = ['false', 'no', '0', 'False', 'No']
        
        if value in true_values:
            return True
        elif value in false_values:
            return False
        else:
            return fallback


    def getlist(self, section, key, fallback=None, delimiter=','):
        value = self.get(section, key, fallback=None)
        if value is None:
            return fallback
        return [item.strip() for item in value.split(delimiter)]

    def gettuple(self, section, key, fallback=None, delimiter=','):
        value = self.get(section, key, fallback=None)
        if value is None:
            return fallback
        return tuple(item.strip() for item in value.split(delimiter))

    def getdatetime(self, section, key, fallback=None, format='%Y-%m-%d %H:%M:%S'):
        value = self.get(section, key, fallback=None)
        if value is None:
            return fallback
        try:
            return datetime.strptime(value, format)
        except ValueError:
            return fallback

    def set(self, section, key, value):
        section = section.lower()
        key = key.lower()
        self._validate_section_key(section, key)
        if section not in self.config:
            self.config[section] = {}
        self.config[section][key] = value

    def update(self, section, data):
        section = section.lower()
        self._validate_section(section)
        self.config.setdefault(section, {}).update({k.lower(): v for k, v in data.items()})

    def remove(self, section, key):
        section = section.lower()
        key = key.lower()
        if section in self.config and key in self.config[section]:
            del self.config[section][key]

    def add_section(self, section):
        section = section.lower()
        if section not in self.config:
            self.config[section] = {}

    def remove_section(self, section):
        section = section.lower()
        if section in self.config:
            del self.config[section]

    def has_key(self, section, key):
        section = section.lower()
        key = key.lower()
        return section in self.config and key in self.config[section]

    def has_section(self, section):
        return section.lower() in self.config

    def sections(self):
        return list(self.config.keys())

    def items(self, section):
        section = section.lower()
        return list(self.config.get(section, {}).items())

    def merge(self, other_config):
        if isinstance(other_config, ConfigMaster):
            other_config = other_config.config
        for section, section_config in other_config.items():
            self.config.setdefault(section.lower(), {}).update({k.lower(): v for k, v in section_config.items()})

    def _validate_section_key(self, section, key):
        if not section or not key:
            raise ValueError("Section and key cannot be empty")
        if ':' in key:
            raise ValueError("Key cannot contain ':' character")

    def _validate_section(self, section):
        if not section:
            raise ValueError("Section cannot be empty")

    def __getitem__(self, section):
        return self.config[section.lower()]

    def __setitem__(self, section, section_config):
        self.config[section.lower()] = {k.lower(): v for k, v in section_config.items()}

    def __delitem__(self, section):
        del self.config[section.lower()]

    def __contains__(self, section):
        return section.lower() in self.config


if __name__ == "__main__":
    # Quick test of the ConfigMaster class
    config = ConfigMaster("test_config.ini")

    # Set values of different data types
    config.set("Section1", "IntKey", "10")
    config.set("Section1", "FloatKey", "3.14")
    config.set("Section1", "BoolKey", "true")
    config.set("Section1", "ListKey", "apple, banana, cherry")
    config.set("Section1", "TupleKey", "red, green, blue")
    config.set("Section1", "DateTimeKey", "2023-06-08 12:30:00")

    # Save the configuration to the file
    config.save()

    # Retrieve values of different data types
    int_value = config.getint("Section1", "IntKey", fallback=0)
    float_value = config.getfloat("Section1", "FloatKey", fallback=0.0)
    bool_value = config.getboolean("Section1", "BoolKey", fallback=False)
    list_value = config.getlist("Section1", "ListKey", fallback=[])
    tuple_value = config.gettuple("Section1", "TupleKey", fallback=())
    datetime_value = config.getdatetime("Section1", "DateTimeKey", fallback=None)

    # Print the retrieved values
    print("Int Value:", int_value)
    print("Float Value:", float_value)
    print("Bool Value:", bool_value)
    print("List Value:", list_value)
    print("Tuple Value:", tuple_value)
    print("DateTime Value:", datetime_value)

    # Test updating values
    config.set("Section1", "IntKey", "20")
    config.update("Section1", {"FloatKey": 6.28, "BoolKey": False})
    print("\nUpdated values:")
    print("Int Value:", config.getint("Section1", "IntKey"))
    print("Float Value:", config.getfloat("Section1", "FloatKey"))
    print("Bool Value:", config.getboolean("Section1", "BoolKey"))

    # Test removing keys and sections
    config.remove("Section1", "ListKey")
    config.remove_section("Section1")
    print("\nRemoved key and section:")
    print("List Value:", config.getlist("Section1", "ListKey", fallback=[]))
    print("Section1 exists?", config.has_section("Section1"))

    # Test adding a new section
    config.add_section("NewSection")
    config.set("NewSection", "NewKey", "NewValue")
    print("\nAdded new section and key:")
    print("NewSection exists?", config.has_section("NewSection"))
    print("NewKey value:", config.get("NewSection", "NewKey"))

    # Test dictionary-like access
    config["AnotherSection"] = {"Key1": "Value1", "Key2": "Value2"}
    print("\nDictionary-like access:")
    print("AnotherSection items:", config.items("AnotherSection"))

    # Test merging configurations
    other_config = ConfigMaster("other_config.ini")
    other_config.set("MergedSection", "MergedKey", "MergedValue")
    config.merge(other_config)
    print("\nMerged configuration:")
    print("MergedKey value:", config.get("MergedSection", "MergedKey"))

    # Test saving and loading configurations
    config.save()
    new_config = ConfigMaster("test_config.ini")
    print("\nLoaded configuration:")
    print("NewKey value:", new_config.get("NewSection", "NewKey"))
    print("MergedKey value:", new_config.get("MergedSection", "MergedKey"))

    # Test invalid section and key names
    try:
        config.set("", "InvalidKey", "Value")
    except ValueError as e:
        print("\nInvalid section name:", e)

    try:
        config.set("InvalidSection", "Key:Invalid", "Value")
    except ValueError as e:
        print("Invalid key name:", e)

    # Test iteration over sections and items
    print("\nSections:")
    for section in config.sections():
        print(section)

    print("\nItems in 'NewSection':")
    for key, value in config.items("NewSection"):
        print(f"{key}: {value}")