w3resource

Python Dynamic Configuration Manager with Runtime Reload

Write a Python program to create a dynamic configuration manager that reloads settings at runtime.

The problem involves creating a dynamic configuration manager that can reload settings from a configuration file at runtime without restart. This system should monitor the configuration file for any changes and automatically update the in-memory settings when modifications are detected. It ensures that applications can adapt to new configurations immediately, enhancing flexibility and reducing downtime. Implementing this involves using file system monitoring tools, thread-safe operations, and handling runtime updates seamlessly.

Sample Solution:

config_manager.py

Python Code :

# Import necessary modules
import json
import threading
import time
import signal
import os
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

# Define the ConfigManager class to manage configurations
class ConfigManager:
    def __init__(self, config_file):
        # Initialize with the configuration file path
        self.config_file = config_file
        # Initialize the configuration data dictionary
        self.config_data = {}
        # Create a lock for thread-safe operations
        self.lock = threading.Lock()
        # Load the initial configuration
        self.load_config()

    def load_config(self):
        # Load the configuration file in a thread-safe manner
        with self.lock:
            with open(self.config_file, 'r') as f:
                self.config_data = json.load(f)
        # Print the loaded configuration for debugging
        print("Configuration reloaded:", self.config_data)

    def get_config(self):
        # Get the current configuration in a thread-safe manner
        with self.lock:
            return self.config_data

    def start_watching(self):
        # Create a file system event handler for the configuration file
        event_handler = ConfigFileHandler(self, self.config_file)
        # Create an observer to monitor file system changes
        observer = Observer()
        # Schedule the observer to watch the current directory for changes
        observer.schedule(event_handler, path='.', recursive=False)
        # Start the observer
        observer.start()
        print("Started watching for configuration changes...")
        
        try:
            # Continuously check if the stop event is set
            while not stop_event.is_set():
                time.sleep(1)
        except KeyboardInterrupt:
            pass
        finally:
            # Stop the observer when shutting down
            print("Stopping observer...")
            observer.stop()
            observer.join()
            print("Observer stopped.")

# Define the file system event handler class for configuration file changes
class ConfigFileHandler(FileSystemEventHandler):
    def __init__(self, config_manager, config_file):
        # Initialize with the config manager and the configuration file path
        self.config_manager = config_manager
        self.config_file = os.path.abspath(config_file)

    def on_modified(self, event):
        # Check if the modified file is the configuration file
        if os.path.abspath(event.src_path) == self.config_file:
            # Print the modification event for debugging
            print(f"Configuration file {event.src_path} changed, reloading...")
            # Reload the configuration
            self.config_manager.load_config()

# Define a signal handler for SIGINT to handle graceful shutdown
def handle_sigint(signal, frame):
    global stop_event
    print("Shutting down...")
    # Set the stop event to terminate the program
    stop_event.set()

# Example usage
if __name__ == "__main__":
    # Define the path to the configuration file
    config_file = 'config.json'
    # Create an instance of ConfigManager with the configuration file
    config_manager = ConfigManager(config_file)

    # Create an event to signal stopping
    stop_event = threading.Event()
    # Register the signal handler for SIGINT
    signal.signal(signal.SIGINT, handle_sigint)
    
    # Start watching for configuration changes in a separate thread
    watcher_thread = threading.Thread(target=config_manager.start_watching)
    watcher_thread.start()

    # Simulate application usage
    try:
        while not stop_event.is_set():
            # Get and print the current configuration periodically
            config = config_manager.get_config()
            print("Current configuration:", config)
            time.sleep(5)
    except KeyboardInterrupt:
        pass

    # Signal the watcher thread to stop
    stop_event.set()
    # Wait for the watcher thread to finish
    watcher_thread.join()
    print("Program terminated.")

config.json

Python Code :

{
    "setting100": "value100",
    "setting200": "value200"
}

Output:

(base) C:\Users\ME>python config_manager.py
Configuration reloaded: {'setting1': 'value100', 'setting2': 'value200'}
Current configuration: {'setting1': 'value100', 'setting2': 'value200'}
Started watching for configuration changes...
Current configuration: {'setting1': 'value100', 'setting2': 'value200'}
Current configuration: {'setting1': 'value100', 'setting2': 'value200'}
Current configuration: {'setting1': 'value100', 'setting2': 'value200'}
Current configuration: {'setting1': 'value100', 'setting2': 'value200'}
Configuration file .\config.json changed, reloading...
Configuration reloaded: {'setting1': 'value1', 'setting2': 'value2'}
Current configuration: {'setting1': 'value1', 'setting2': 'value2'}
Current configuration: {'setting1': 'value1', 'setting2': 'value2'}
Current configuration: {'setting1': 'value1', 'setting2': 'value2'}
Configuration file .\config.json changed, reloading...
Configuration reloaded: {'setting100': 'value1', 'setting200': 'value2'}
Current configuration: {'setting100': 'value1', 'setting200': 'value2'}
Current configuration: {'setting100': 'value1', 'setting200': 'value2'}
Shutting down...
Stopping observer...
Observer stopped.
Program terminated.

(base) C:\Users\ME>

Explanation:

  • Imports: Necessary modules are imported, including JSON for parsing, threading for concurrent execution, signal for handling interrupts, and watchdog for monitoring file changes.
  • ConfigManager class: Manages the configuration file, loading and providing access to configuration settings in a thread-safe manner.
  • ConfigFileHandler class: Handles file modification events to reload the configuration file when it changes.
  • handle_sigint function: Gracefully handles shutdown when a SIGINT (Ctrl+C) is detected.
  • Main execution block: Initializes the configuration manager, sets up a signal handler for graceful shutdown, starts a thread to watch for file changes, and periodically prints the current configuration until interrupted.

Python Code Editor :

Have another way to solve this solution? Contribute your code (and comments) through Disqus.

Previous: Python Mathematical expression Parsing and Evaluation Library.
Next: Python Genetic Algorithm for Optimization.

What is the difficulty level of this exercise?

Test your Programming skills with w3resource's quiz.



Become a Patron!

Follow us on Facebook and Twitter for latest update.

It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.

https://198.211.115.131/python-exercises/advanced/dynamic-configuration-manager-in-python.php