2

I am currently trying to learn a new method of programming. I am creating a Singleton class to help manage my config file, which is used across multiple areas of my code. The code that I'm using is below, but my main goal is to create a single instance of this class, and it's called within my code and pull information through when it is needed. Instead of attaching the config parser to a variable that would create an instance named config, I wanted to inherit the class and implement it myself inside my class. I understand typically, I would pull this information in and use a __init__, but in this case, I am using a __new__ and controlling the creation of this class. When i change the _initialize to __init__ i can get the code to work but from my understanding, I would create a new instance every time i call my class, instead of controlling __init__ with __new__. I guess the questions are: am I approaching this the right way, and am I missing something?

Error
  File "...\Documents\GitHub\Strontium-Carbon-Version2\SrC\test_functions.py", line 89, in <module>
    config = ConfigManager(config_path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\Programs\Python\Python312\Lib\configparser.py", line 625, in __init__
    self._read_defaults(defaults)
  File "...\AppData\Local\Programs\Python\Python312\Lib\configparser.py", line 1200, in _read_defaults
    self.read_dict({self.default_section: defaults})
  File "...\AppData\Local\Programs\Python\Python312\Lib\configparser.py", line 734, in read_dict
    for key, value in keys.items():
                      ^^^^^^^^^^
AttributeError: 'str' object has no attribute 'items'
import os
import configparser

class ConfigManager(configparser.ConfigParser):
    """
    Creating a singleton class to manage the inherited use of the ConfigParser as a global class without the need to
    pull this class information through multiple classes. Once the class has been called (within the main
    program function), the information can be pulled when needed.
    """

    _instance = None  # Singleton instance

    def __new__(cls, config_path: str = None) -> 'ConfigManager':
        """
        Using the special method __new__ to control the creation of a new instance of the ConfigManager Class.
        Granting more control of the creation of an instance (when you call the class). Using the Singleton
        pattern design with the __new__ method allows me to create one instance of this class for the program
        and if one is already created, return that instance.
        """
        if cls._instance is None:
            cls._instance = super(ConfigManager, cls).__new__(cls)
            cls._instance._initialize(config_path)  # Initialize config on first creation
        return cls._instance

    def _initialize(self, config_path: str = None):
        """
        This method is designed to set up the configuration by loading the config file when the class is 
        instantiated for the first time. If the config file doesn't exist, it creates a default configuration 
        file.
        """
        super().__init__()  # Call the ConfigParser __init__ method
        configparser.ConfigParser.__init__(self)

        if config_path is None:
            raise ValueError('No path inputted')

        if not os.path.exists(config_path):
            print(f"Config file not found at {config_path}.")
        else:
            self.config_path = config_path
            self.read(config_path)

    def get_path(self, category: str, path: str) -> str:
        """
        Retrieves a specific path from the configuration file.

        :param category: The section or category in the config file (e.g., 'Path Location').
        :type category: str
        :param path: The option or path name under the category (e.g., 'main_folder').
        :type path: str
        :return: The value (path) associated with the category and path name.
        :rtype: str

        :Example:
        >>> config_manager.get_path('Path Location', 'main_folder')
        '~/Documents/test folder'
        """
        try:
            return self.get(category, path)
        except (configparser.NoSectionError, configparser.NoOptionError) as e:
            print(f"Error: {e}")
            return None

    def path_options(self) -> dict:
        """
        Retrieves all categories (sections) and their associated paths (options) as a dictionary.
        """
        all_paths = {}
        for section in self.sections():
            all_paths[section] = dict(self.items(section))  # Convert each section to a dict
        return all_paths

    def _create_default_config(self, file_locations: dict):
        """Creates a default configuration file if one does not exist."""
        self['Path Location'] = file_locations
        with open(self.config_path, 'w') as configfile:
            self.write(configfile)

    def save(self):
        """Saves the current configuration to the config file."""
        with open(self.config_path, 'w') as configfile:
            self.write(configfile)


# Main program to test the class
if __name__ == "__main__":
    config_path = os.path.expanduser(r"~\Documents\test folder\Program files\config.ini")
    config = ConfigManager(config_path)
    print(config.get_path('Path Location', 'main_file'))

My expectation is is that this would access the config file and return a string that is associated with that specific config location.

3
  • You're calling configparser.ConfigParser.__init__(self) twice: Once using super().__init__() and then with the explicit call. Commented Oct 30, 2024 at 21:38
  • 1
    Beware: if ConfigManager.__new__ returns an instance of ConfigManager, type.__call__ is going to invoke ConfigManager.__init__, even if it's not a newly created instance. If you really want a singleton, use a separate class method (or even a standalone function that manages a "hidden" global variable) to enforce the uniqueness. Commented Oct 30, 2024 at 22:56
  • @Barmar I seem to be using Super().__init__() so often I didn't notice that. Thank you. Unfortynetly, fixing this still results in the same error, from my understanding is that this error is saying i am not init ing. Commented Nov 1, 2024 at 21:04

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.