Home >Backend Development >Python Tutorial >Towards Effortless Python Configuration Files Version 1

Towards Effortless Python Configuration Files Version 1

Susan Sarandon
Susan SarandonOriginal
2024-12-02 14:55:19768browse

Introduction

As noted in the previous article the simplistic version is rife with problems including scalability, maintainability and extensibility.

A simple extension from Version Ø is to try and hide the Python configuration details behind a property class. That is implement a pseudo data class that exposes a set of properties that allow a developer to simply do property set and get calls to retrieve and persist property values.

From the maintainers point of view this implementation should support the following capabilities.

  1. Allow auto creation of configuration sections if they are missing
  2. Allow auto creation of property values if they are missing
  3. The properties should be implemented as both read-through and write-through.
  4. In order to avoid the startup costs for the above as an application instantiates this class throughout the application this class should be a Singleton.

Class Representation

The following UML Class diagram describes a class that would meet the requirements in the introduction. The ConfiguratonProperties class meets requirements 1 & 2 with the protected methods .createMissingSections and .createMissingKeys

Towards Effortless Python Configuration Files Version 1

Create Implementations

Create Missing Sections

The following code shows the implementation. Notice that additional sections require code updates to this method

SECTION_GENERAL:  str = 'General'
SECTION_DATABASE: str = 'Database'

def _createMissingSections(self):
    """
    Create missing sections.  Add additional calls for
    each defined section
    """
    self._createMissingSection(SECTION_GENERAL)
    self._createMissingSection(SECTION_DATABASE)

The missing section code is as follows.

def _createMissingSection(self, sectionName: str):
    """
    Only gets created if it is missing
    Args:
        sectionName: The potential section to create
    """
    hasSection: bool = self._configParser.has_section(sectionName)
    self.logger.info(f'hasSection: {hasSection} - {sectionName}')
    if hasSection is False:
        self._configParser.add_section(sectionName)

Create Missing Keys

The following code shows the implementation. Again note if we add an additional section the developer must add an additional loop for the new section.

GENERAL_PREFERENCES: Dict[str, str] = {
    'debug':    'False',
    'logLevel': 'Info'
}

DATABASE_PREFERENCES: Dict[str, str] = {
    'dbName': 'example_db',
    'dbHost': 'localhost',
    'dbPort': '5432'
}

def _createMissingKeys(self):
    """
    Create missing keys and their values.  Add additional calls for
    each defined section.
    """
    for keyName, keyValue in GENERAL_PREFERENCES.items():
        self._createMissingKey(sectionName=SECTION_GENERAL, keyName=keyName, defaultValue=keyValue)
    for keyName, keyValue in DATABASE_PREFERENCES.items():
        self._createMissingKey(sectionName=SECTION_DATABASE, keyName=keyName, defaultValue=keyValue)

The missing key code is as follows. Notice any missing keys are immediately persisted.

def _createMissingKey(self, sectionName: str, keyName: str, defaultValue: str):
    """
    Only gets created if it is missing.  The configuration file is updated
    immediately for each missing key and its value

    Args:
        sectionName:   The section name where the key resides
        keyName:       The key name
        defaultValue:  Itsß value
    """
    if self._configParser.has_option(sectionName, keyName) is False:
        self._configParser.set(sectionName, keyName, defaultValue)
        self._saveConfiguration()

Class Properties

Sample implementations for requirement 3 follow.

String Properties

Notice that setting a property writes-through to the configuration file by setting the property and immediately persisting it. Reading properties are effectively read-through because of how we immediately write set properties.

@property
def dbName(self) -> str:
    return self._configParser.get(SECTION_DATABASE, 'dbName')

@dbName.setter
def dbName(self, newValue: str):
    self._configParser.set(SECTION_DATABASE, 'dbName', newValue)
    self._saveConfiguration()

Integer Properties

Integer properties use the .getint method to retrieve the value. When setting the property the developer must manually convert it to a string.

@property
def dbPort(self) -> int:
    return self._configParser.getint(SECTION_DATABASE, 'dbPort')

@dbPort.setter
def dbPort(self, newValue: int):
    self._configParser.set(SECTION_DATABASE, 'dbPort', str(newValue))
    self._saveConfiguration()

Boolean Properties

Boolean properties use the .getboolean method to retrieve their value. When setting the property the developer must manually convert it to a string.

SECTION_GENERAL:  str = 'General'
SECTION_DATABASE: str = 'Database'

def _createMissingSections(self):
    """
    Create missing sections.  Add additional calls for
    each defined section
    """
    self._createMissingSection(SECTION_GENERAL)
    self._createMissingSection(SECTION_DATABASE)

Enumeration Properties

I will not cover enumeration properties in this article. There are two ways to persist them, by their name or by their value. Each mechanism requires a slightly different way to deserialize the values back to an enumeration type.

Accessing and Modifying Properties

The following code snippet demonstrates how to access and modify the properties.

def _createMissingSection(self, sectionName: str):
    """
    Only gets created if it is missing
    Args:
        sectionName: The potential section to create
    """
    hasSection: bool = self._configParser.has_section(sectionName)
    self.logger.info(f'hasSection: {hasSection} - {sectionName}')
    if hasSection is False:
        self._configParser.add_section(sectionName)

The above snippet produces the following output

GENERAL_PREFERENCES: Dict[str, str] = {
    'debug':    'False',
    'logLevel': 'Info'
}

DATABASE_PREFERENCES: Dict[str, str] = {
    'dbName': 'example_db',
    'dbHost': 'localhost',
    'dbPort': '5432'
}

def _createMissingKeys(self):
    """
    Create missing keys and their values.  Add additional calls for
    each defined section.
    """
    for keyName, keyValue in GENERAL_PREFERENCES.items():
        self._createMissingKey(sectionName=SECTION_GENERAL, keyName=keyName, defaultValue=keyValue)
    for keyName, keyValue in DATABASE_PREFERENCES.items():
        self._createMissingKey(sectionName=SECTION_DATABASE, keyName=keyName, defaultValue=keyValue)

Conclusion

The source code for this article is here. The support class SingletonV3 is here

The result of the implementation initially left me satisfied as a consumer of the code. I was able to get and set typed properties. However, as the maintainer of the code I had to manually update code data structures and code loops whenever I added new sections and new properties. Additionally, all I really got from this is a mechanism/pattern to use whenever I needed new configuration properties in different applications.

Advantages

  • Easy type safe access to application properties
  • Invoking the singleton in different parts of my application provided consistent and reliable access to properties regardless which part of the application modified values

Disadvantages

  • Updates to add new properties was tedious
  • Lots of boiler plate code
  • No reusability across various applications. Essentially, I just had a template

See my next post that documents an alternate implementation to address the disadvantages I listed, while keeping the advantages.

The above is the detailed content of Towards Effortless Python Configuration Files Version 1. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn