|
1 | 1 | # ConfigurationManager Class
|
2 | 2 |
|
3 |
| -The `ConfigurationManager` class is a key component of the JSON-Py-Craft library. It provides a high-level interface for managing configuration data in a JSON-based format. This class is designed to simplify the process of handling configuration settings, including loading, saving, backing up, and accessing values within the configuration data. |
| 3 | +The `ConfigurationManager` class is a key component of the **JSONPyCraft** library. It provides a high-level interface for managing configuration data in a JSON-based format. This class is designed to simplify the process of handling configuration settings, including loading, saving, backing up, and accessing values within the configuration data. |
4 | 4 |
|
5 | 5 | ## Class Hierarchy
|
6 | 6 |
|
7 | 7 | - `Singleton` (Inherits from)
|
8 | 8 | - `ConfigurationManager`
|
9 | 9 |
|
10 |
| -## Constructor |
| 10 | +### `ConfigurationManager(file_path: str, initial_data: Optional[JSONMap] = None, indent: int = 2)` |
11 | 11 |
|
12 |
| -### `ConfigurationManager(file_path: str, initial_data: Optional[JSONMap] = None, logger: Optional[Logger] = None)` |
| 12 | +- **Purpose**: The constructor initializes a new `ConfigurationManager` instance, setting up the essential links to a JSON configuration file. It is designed to manage configuration settings, whether creating a new file or handling an existing one. |
13 | 13 |
|
14 |
| -- Initializes a new `ConfigurationManager` instance. |
15 |
| -- Parameters: |
16 |
| - - `file_path` (str): The path to the configuration JSON file. |
17 |
| - - `initial_data` (Optional[JSONMap]): Optional initial configuration data. |
18 |
| - - `logger` (Optional[Logger]): Optional logger for error-handling. |
| 14 | +- **Parameters**: |
| 15 | + - `file_path` (str): The path to the JSON configuration file. This parameter is crucial as it determines which file the manager will interact with for all operations. The path can be relative or absolute. |
| 16 | + - `initial_data` (Optional[JSONMap]): Optional initial data to populate a new configuration file. This parameter is particularly useful for establishing default settings in a configuration. It is important to note that if the specified JSON file already exists, this initial data does not merge with the existing data; instead, it's used only if the file does not exist or when explicitly saving this data. |
| 17 | + - `indent` (int, optional): Sets the JSON indentation level for the output format, affecting the readability of the saved configuration file. The default is 2, which is a standard practice for JSON formatting. |
| 18 | + |
| 19 | +- **Functionality**: |
| 20 | + - Upon instantiation, `ConfigurationManager` prepares to manage the specified JSON file. The `initial_data` is held in readiness to be used if needed (e.g., creating a new file or explicitly saving this initial data). No automatic merging of `initial_data` with existing data occurs. |
| 21 | + - The `indent` parameter influences the aesthetics of the JSON file, making it more readable when opened in a text editor. |
| 22 | + |
| 23 | +- **Error Handling**: |
| 24 | + - The constructor itself does not handle errors related to file operations or data handling. These are managed within their respective methods (`load`, `save`, and `backup`). This design choice keeps the constructor simple and delegates error handling to the specific operations where errors are more likely to occur. |
| 25 | + |
| 26 | +- **Example Usage**: |
| 27 | + - Showcasing the initialization of the `ConfigurationManager` with and without the `initial_data` parameter. |
| 28 | + |
| 29 | +```python |
| 30 | +# Initializing ConfigurationManager for an existing configuration file |
| 31 | +config_manager = ConfigurationManager("existing_config.json") |
| 32 | + |
| 33 | +# Initializing ConfigurationManager with initial data for a new configuration file |
| 34 | +initial_config = {"app_name": "MyApp", "version": "1.0"} |
| 35 | +config_manager = ConfigurationManager("new_config.json", initial_data=initial_config) |
| 36 | +``` |
19 | 37 |
|
20 | 38 | ## Methods
|
21 | 39 |
|
22 |
| -### `load() -> bool` |
| 40 | +### `load() -> None` |
23 | 41 |
|
24 | 42 | - Loads configuration data from the file.
|
25 |
| -- Returns: |
26 |
| - - `True` if the data was loaded successfully, `False` otherwise. |
| 43 | +- Raises: |
| 44 | + - `JSONFileErrorHandler`: If there is a file-related error accessing the JSON file. |
| 45 | + - `JSONDecodeErrorHandler`: If there is an error loading JSON data from the file. |
27 | 46 |
|
28 |
| -### `save() -> bool` |
| 47 | +### `save() -> None` |
29 | 48 |
|
30 | 49 | - Saves configuration data to the file.
|
31 |
| -- Returns: |
32 |
| - - `True` if the data was saved successfully, `False` otherwise. |
| 50 | +- Raises: |
| 51 | + - `JSONFileErrorHandler`: If there is a file-related error accessing the JSON file. |
| 52 | + - `JSONEncodeErrorHandler`: If there is an error saving JSON data to the file. |
33 | 53 |
|
34 |
| -### `backup() -> bool` |
| 54 | +### `backup() -> None` |
35 | 55 |
|
36 | 56 | - Creates a backup of the configuration file.
|
37 |
| -- Returns: |
38 |
| - - `True` if the backup was created successfully, `False` otherwise. |
| 57 | +- Raises: |
| 58 | + - `JSONFileErrorHandler`: If there is an error creating a backup of the JSON file. |
| 59 | + - `JSONDecodeErrorHandler`: If there is an error loading JSON data from the file during backup. |
| 60 | + - `JSONEncodeErrorHandler`: If there is an error saving JSON data to the file during backup. |
39 | 61 |
|
40 | 62 | ### `get_value(key: str, default: Optional[Any] = None) -> Any`
|
41 | 63 |
|
42 |
| -- Gets a configuration value based on the provided key. |
| 64 | +- Retrieves a configuration value based on the provided key. Supports accessing nested values in the configuration data. |
43 | 65 | - Parameters:
|
44 |
| - - `key` (str): The key to retrieve the value for. |
45 |
| - - `default` (Optional[Any]): The default value to return if the key is not found. |
| 66 | + - `key` (str): The key for the desired value. Supports dot notation for nested keys (e.g., "section.subsection.key"). |
| 67 | + - `default` (Optional[Any]): The default value to return if the key is not found. Ensures type consistency with the expected return value. |
46 | 68 | - Returns:
|
47 |
| - - The configuration value corresponding to the key, or the default value if not found. |
| 69 | + - The value corresponding to the key or the default value if the key is not found. If a non-existent key is provided and no default is specified, returns `None`. |
| 70 | + |
| 71 | +Examples: |
| 72 | + |
| 73 | +```python |
| 74 | +# Retrieving a simple configuration value with a default |
| 75 | +app_name = config_manager.get_value("app.name", "MyApp") |
| 76 | +print(f"Application Name: {app_name}") |
| 77 | + |
| 78 | +# Accessing a nested configuration value |
| 79 | +db_port = config_manager.get_value("database.settings.port", 3306) |
| 80 | +print(f"Database Port: {db_port}") |
| 81 | + |
| 82 | +# Handling non-existent keys |
| 83 | +unknown_key_value = config_manager.get_value("nonexistent.key") |
| 84 | +if unknown_key_value is None: |
| 85 | + print("The key 'nonexistent.key' was not found in the configuration.") |
| 86 | +``` |
48 | 87 |
|
49 | 88 | ### `set_value(key: str, value: Any) -> bool`
|
50 | 89 |
|
51 |
| -- Sets a configuration value for the provided key. |
52 |
| -- Parameters: |
53 |
| - - `key` (str): The key to set the value for. |
54 |
| - - `value` (Any): The value to set. |
55 |
| -- Returns: |
56 |
| - - `True` if the value was set successfully, `False` otherwise. |
| 90 | +- **Purpose**: Assigns a new value to a specified configuration key. This method is versatile and can handle both top-level and nested keys within the configuration structure. |
57 | 91 |
|
58 |
| -### `evaluate_path(key: str, default: Optional[Any] = None) -> Optional[str]` |
| 92 | +- **Parameters**: |
| 93 | + - `key` (str): The key under which the value will be set. Supports dot notation for nested keys (e.g., "section.subsection.key"), allowing for deep updates within the configuration structure. |
| 94 | + - `value` (Any): The new value to be assigned. This can be of any data type supported by JSON (e.g., string, number, object). |
59 | 95 |
|
60 |
| -- Evaluates a configuration path based on the provided key. |
61 |
| -- Parameters: |
62 |
| - - `key` (str): The key to retrieve the path for. |
63 |
| - - `default` (Optional[Any]): The default value to return if the path is not found. |
64 |
| -- Returns: |
65 |
| - - The evaluated path, or the default value if not found. |
| 96 | +- **Returns**: |
| 97 | + - `bool`: Always returns `True` if the update is successful. |
| 98 | + |
| 99 | +- **Behavior and Error Handling**: |
| 100 | + - The method internally uses `JSONMapTemplate.update_nested` to handle nested keys. In this process, keys are expanded and coerced into strings, facilitating the handling of deeply nested structures. |
| 101 | + - Exceptions are raised in rare edge cases, such as when an invalid key structure is passed. The method will raise a `ValueError` for invalid keys or a `TypeError` for incompatible value types. |
| 102 | + - It's important to note that while `set_value` is designed to be robust and handle most typical use cases smoothly, the underlying complexity of `update_nested` should be considered when dealing with highly nested or complex configurations. |
| 103 | + |
| 104 | +- **Examples**: |
| 105 | + - Demonstrating both simple and nested key updates, as well as error handling: |
| 106 | + |
| 107 | +```python |
| 108 | +# Setting a simple configuration value |
| 109 | +try: |
| 110 | + config_manager.set_value("app.version", "1.0") |
| 111 | + print("Configuration value 'app.version' set to '1.0'") |
| 112 | +except (ValueError, TypeError) as e: |
| 113 | + print(f"Error setting 'app.version': {str(e)}") |
| 114 | + |
| 115 | +# Updating a nested configuration value |
| 116 | +try: |
| 117 | + config_manager.set_value("database.settings.hostname", "db.example.com") |
| 118 | + print("Database hostname updated successfully.") |
| 119 | +except (ValueError, TypeError) as e: |
| 120 | + print(f"Error updating database hostname: {str(e)}") |
| 121 | +``` |
| 122 | + |
| 123 | +### `evaluate_path(key: str, default_path: Optional[Any] = None, default_type: str = "dir") -> Optional[str]` |
| 124 | + |
| 125 | +- **Purpose**: This method is designed to retrieve and validate paths from the configuration data. It is particularly useful for dynamically determining file or directory paths based on the configuration settings. |
| 126 | + |
| 127 | +- **Parameters**: |
| 128 | + - `key` (str): The key corresponding to the path in the configuration data. This key can be a simple string or a dot-notated string for nested keys. |
| 129 | + - `default_path` (Optional[str], optional): A fallback path to return when the specified key is not found in the configuration. This parameter allows for a default behavior when a configuration entry is missing. |
| 130 | + - `default_type` (str, optional): Specifies the expected type of the path – either "file" or "dir". This is used to determine the nature of the path validation and creation process. Defaults to "dir", implying that in the absence of explicit specification, the path is treated as a directory. |
| 131 | + |
| 132 | +- **Returns**: |
| 133 | + - `Optional[str]`: The method returns the evaluated path as a string. If the key is not found, and a `default_path` is provided, that path is returned. If no `default_path` is specified, it returns `None`. |
| 134 | + |
| 135 | +- **Functionality and Behavior**: |
| 136 | + - The method first attempts to retrieve the path specified by the `key`. If the path is not found in the configuration, and `default_path` is provided, the method returns this default path. |
| 137 | + - If the path does not exist in the file system, the method will attempt to create it based on the `default_type` parameter. For example, if `default_type` is "dir", it will try to create the directory structure. If it is "file", it will create an empty file at that location. |
| 138 | + - This functionality is particularly useful for ensuring that required directories or files are available at runtime, especially in scenarios where the application dynamically generates or modifies paths. |
| 139 | + |
| 140 | +- **Examples**: |
| 141 | + - Demonstrating how to retrieve and automatically create paths: |
| 142 | + |
| 143 | +```python |
| 144 | +# Retrieve a configuration path for logging |
| 145 | +log_path = config_manager.evaluate_path("logging.directory", default_path="/var/logs/myapp", default_type="dir") |
| 146 | +print(f"Log Path: {log_path}") |
| 147 | + |
| 148 | +# Handle a configuration path for a data file |
| 149 | +data_file_path = config_manager.evaluate_path("data.filepath", default_path="data/default_data.json", default_type="file") |
| 150 | +print(f"Data File Path: {data_file_path}") |
| 151 | +``` |
66 | 152 |
|
67 | 153 | ### `get_environment(variable: str, key: Optional[str] = None) -> str`
|
68 | 154 |
|
69 |
| -- Gets the value of an environment variable. |
70 |
| -- Parameters: |
71 |
| - - `variable` (str): The name of the environment variable. |
72 |
| - - `key` (Optional[str]): The key in the configuration data where the environment variable is stored. |
73 |
| -- Returns: |
74 |
| - - The value of the environment variable. |
| 155 | +- **Purpose**: Retrieves the value of an environment variable, optionally mapping it to a configuration key. This method is designed to bridge the gap between the application's runtime environment and its configuration settings. |
75 | 156 |
|
76 |
| -### `get_logger(key: str, logger_name: str, level: str = "DEBUG") -> Logger` |
| 157 | +- **Parameters**: |
| 158 | + - `variable` (str): Specifies the name of the environment variable to retrieve. This should match the variable's name as it is set in the system's environment. |
| 159 | + - `key` (Optional[str]): If provided, this parameter points to a key within the configuration data where the environment variable value is expected to be mirrored or overridden. This allows for dynamic configuration changes based on environmental conditions. |
77 | 160 |
|
78 |
| -- Gets a logger instance with specified configuration. |
79 |
| -- Parameters: |
80 |
| - - `key` (str): A unique key identifying the logger configuration. |
81 |
| - - `logger_name` (str): The name of the logger. |
82 |
| - - `level` (str, optional): The log level for the logger (default is "DEBUG"). |
83 |
| -- Returns: |
84 |
| - - A configured logger instance. |
| 161 | +- **Returns**: |
| 162 | + - `str`: The value of the specified environment variable. If the variable is not found in the system's environment and a `key` is provided, the method will attempt to retrieve the value from the configuration data associated with that key. |
| 163 | + |
| 164 | +- **Behavior and Use Cases**: |
| 165 | + - The method first checks the system's environment for the specified `variable`. If found, its value is returned. |
| 166 | + - If the environment variable is not set, and a `key` is provided, the method looks up this key in the configuration data. This feature is useful for scenarios where certain settings might be overridden or specified within the application configuration instead of the environment. |
| 167 | + - This dual approach (environment variable first, then configuration data) provides flexibility and a fallback mechanism, allowing applications to adapt to different deployment environments and configurations seamlessly. |
| 168 | + |
| 169 | +- **Examples**: |
| 170 | + - Demonstrating how to retrieve an environment variable and fallback to configuration data: |
| 171 | + |
| 172 | +```python |
| 173 | +# Retrieving a database URL from an environment variable or configuration file |
| 174 | +db_url = config_manager.get_environment("DATABASE_URL", key="database.url") |
| 175 | +print(f"Database URL: {db_url}") |
| 176 | + |
| 177 | +# Getting an API key with fallback to configuration if not set in the environment |
| 178 | +api_key = config_manager.get_environment("API_KEY", key="api.credentials.key") |
| 179 | +print(f"API Key: {api_key}") |
| 180 | +``` |
| 181 | + |
| 182 | +### `get_logger(key: str, logger_name: str, level: str = "DEBUG", logger_format: Optional[str] = None) -> Logger` |
| 183 | + |
| 184 | +- **Purpose**: Retrieves a logger instance that is configured based on provided settings. This method offers customization for the logger's name, log level, and format, allowing for tailored logging setups according to different parts of an application. |
| 185 | + |
| 186 | +- **Parameters**: |
| 187 | + - `key` (str): A unique key identifying the logger's configuration. This key is used to retrieve log settings such as file path and level from the configuration data. |
| 188 | + - `logger_name` (str): The name of the logger, which can be used to retrieve the same logger instance across different parts of the application. |
| 189 | + - `level` (str, optional): Specifies the log level (e.g., "DEBUG", "INFO", "ERROR"). The default level is "DEBUG". |
| 190 | + - `logger_format` (str, optional): Defines the format of the log messages. If not specified, a default format or the format specified in the configuration is used. |
| 191 | + |
| 192 | +- **Returns**: |
| 193 | + - `Logger`: A configured logger instance tailored for logging messages as per the specified settings. |
| 194 | + |
| 195 | +- **Behavior and Error Handling**: |
| 196 | + - The method uses the `key` to fetch specific configuration settings related to logging, such as file paths and log levels. |
| 197 | + - If a logger with the given `logger_name` already exists, it returns that instance to ensure consistent logging throughout the application. |
| 198 | + - Log messages are written to a file as specified in the configuration, and if `logger_format` is provided, it customizes the appearance of log messages. |
| 199 | + - A `ValueError` is raised if the logger configuration for the specified `key` is not found. |
| 200 | + |
| 201 | +- **Examples**: |
| 202 | + - Illustrating the creation of a custom logger: |
| 203 | + |
| 204 | +```python |
| 205 | +# Creating a logger with a specific level and format |
| 206 | +logger = config_manager.get_logger("logger_config", "my_logger", "INFO", "%(asctime)s - %(levelname)s - %(message)s") |
| 207 | +logger.info("This is an info message.") |
| 208 | +``` |
| 209 | + |
| 210 | +- **Notes**: |
| 211 | + - The method's flexibility in specifying logger configuration makes it ideal for applications that require different logging behaviors in various components or modules. |
| 212 | + - The addition of `logger_format` provides greater control over log message formatting, enhancing readability and debugging. |
85 | 213 |
|
86 | 214 | # Usage Example
|
87 | 215 |
|
88 |
| -Here's a simple example of how to use the `ConfigurationManager` class to manage configuration data in your Python application: |
| 216 | +Here's a comprehensive example of how to use the `ConfigurationManager` class to manage configuration data within a Python application: |
89 | 217 |
|
90 | 218 | ```python
|
| 219 | +from jsonpycraft.core.errors import JSONEncodeErrorHandler, JSONDecodeErrorHandler |
91 | 220 | from jsonpycraft.manager.configuration import ConfigurationManager
|
92 | 221 |
|
93 | 222 | # Initialize the ConfigurationManager with a file path
|
94 | 223 | config_file_path = "config.json"
|
95 | 224 | config_manager = ConfigurationManager(config_file_path)
|
96 | 225 |
|
97 | 226 | # Load configuration data from the file
|
98 |
| -if not config_manager.load(): |
99 |
| - # Handle the case where loading failed |
100 |
| - print("Failed to load configuration data.") |
101 |
| -else: |
102 |
| - # Get a configuration value |
103 |
| - app_name = config_manager.get_value("app.name", "MyApp") |
104 |
| - print(f"Application Name: {app_name}") |
105 |
| - |
106 |
| - # Set a new configuration value |
107 |
| - config_manager.set_value("app.version", "1.0") |
108 |
| - |
109 |
| - # Save the updated configuration data |
110 |
| - if config_manager.save(): |
111 |
| - print("Configuration data saved successfully.") |
112 |
| - |
113 |
| - # Create a backup of the configuration file |
114 |
| - if config_manager.backup(): |
115 |
| - print("Backup created successfully.") |
| 227 | +try: |
| 228 | + config_manager.load() |
| 229 | + print("Configuration data loaded successfully.") |
| 230 | +except JSONDecodeErrorHandler: |
| 231 | + raise JSONDecodeErrorHandler("Failed to load configuration data.") |
| 232 | + |
| 233 | +# Get a configuration value |
| 234 | +app_name = config_manager.get_value("app.name", "MyApp") |
| 235 | +print(f"Application Name: {app_name}") |
| 236 | + |
| 237 | +# Set a new configuration value |
| 238 | +config_manager.set_value("app.version", "1.0") |
| 239 | +print("Configuration value 'app.version' set to '1.0'") |
| 240 | + |
| 241 | +# Evaluate a path for log files |
| 242 | +log_path = config_manager.evaluate_path("logging.directory", default_path="/var/logs/myapp", default_type="dir") |
| 243 | +print(f"Log Path: {log_path}") |
| 244 | + |
| 245 | +# Get an environment variable or fallback to a default |
| 246 | +db_url = config_manager.get_environment("DATABASE_URL", key="database.url") |
| 247 | +print(f"Database URL: {db_url}") |
| 248 | + |
| 249 | +# Create and configure a logger |
| 250 | +logger = config_manager.get_logger("logger_config", "my_logger", "INFO") |
| 251 | +logger.info("Configuration manager initialized successfully.") |
| 252 | + |
| 253 | +# Save the updated configuration data |
| 254 | +try: |
| 255 | + config_manager.save() |
| 256 | + print("Configuration data saved successfully.") |
| 257 | +except JSONEncodeErrorHandler: |
| 258 | + raise JSONEncodeErrorHandler("Failed to save configuration data.") |
| 259 | + |
| 260 | +# Create a backup of the configuration file |
| 261 | +try: |
| 262 | + config_manager.backup() |
| 263 | + print("Backup created successfully.") |
| 264 | +except JSONEncodeErrorHandler: |
| 265 | + raise JSONEncodeErrorHandler("Failed to backup configuration data.") |
116 | 266 | ```
|
117 | 267 |
|
118 |
| -In this example, we first initialize the `ConfigurationManager` with the path to the configuration file (`config.json`). We then load the configuration data, retrieve and print a configuration value, set a new configuration value, save the updated data, and create a backup of the configuration file. |
| 268 | +This example demonstrates several key operations: |
| 269 | +- Loading configuration data from a file. |
| 270 | +- Retrieving and setting configuration values. |
| 271 | +- Evaluating paths for specific configurations, such as log directories. |
| 272 | +- Integrating environment variables into the configuration. |
| 273 | +- Setting up and using a custom logger. |
| 274 | +- Saving updates to the configuration and creating backups. |
119 | 275 |
|
120 |
| -This demonstrates the basic usage of the `ConfigurationManager` class for managing configuration settings in your Python application. |
| 276 | +This example provides a practical illustration of how `ConfigurationManager` can be utilized in real-world scenarios, covering a wide range of its capabilities. This holistic approach helps with understanding how different methods and features can work together effectively. |
0 commit comments