Skip to content

Instantly share code, notes, and snippets.

@yatharthb97
Created February 26, 2022 11:08
Show Gist options
  • Save yatharthb97/086f24c9a01bb5a508821fae80e190c9 to your computer and use it in GitHub Desktop.
Save yatharthb97/086f24c9a01bb5a508821fae80e190c9 to your computer and use it in GitHub Desktop.
Tool to import, verify, and generate config files {json and yaml} for python.
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
#!/usr/bin/env python
"""
Configuration file manager for yaml and json.
Tool to import, verify, and generate config files {json and yaml} for python.
Author : *Yatharth Bhasin* (Github → yatharthb97)
License: *MIT open-source license* (https://opensource.org/licenses/mit-license.php)
This piece of software was released on GistHub : TODO.
Please give credit whenever possible.
"""
import os
import json
import yaml
class Configr:
"""
Tools to import, verify, and generate config files for python.
"""
def __init__(self, filename=None, ext=".json", ref=None):
# Configuration
self.config = None
# Declare defaults
self.default_ext = ext
self.default_config_name = f"autogenerated_config{self.default_ext}" #Default configuration option
self.ref_config = None # Reference config object.
self.ref_set = False # Reference config is set flag.
# Filename
self.file_path = None
if filename != None:
self.file_path = filename
# Reference Configuration
if ref != None:
self.set_ref(ref)
def find_config(self, dir_path=".", ext="", file_key="config", alphabatical=True):
"""
Checks for configuration files within the current working
directory, does a simple search with the given `file_key`.
dir_path : The directory path for search - converts to absolute file path.
ext : Extension of file to be searched.
file_key : [optional] key word that indicates a config file.
args:
---
"alphabatical" : sorts and returns the first file in case multiple files are detected.
"""
# Extract all files within the folder
file_list = [file for file in os.listdir(os.path.abspath(dir_path))]
if ext != "":
file_list = [file for file in file_list if ext in file]
file_list = [file for file in file_list if file_key in file]
if "alphabatical":
file_list = sorted(file_list)
if not file_list:
return None
else:
return file_list[0]
if len(file_list) > 1:
print(f"[ERROR] Multiple config files options found - {file_list}.")
return None
elif len(file_list) == 0:
print(f"[ERROR] No config files found!")
return None
else:
return file_list[0]
def load(self, config_file):
"""
Loads the file into `self.config` field as dictionary.
Parses both json and yaml.
"""
if not os.path.exists(config_file):
raise Exception(f"File does not exist: {config_file}")
if config_file.endswith("yml") or config_file.endswith("yaml") or config_file.endswith("json"):
self.file_path = config_file
with open(config_file, 'r') as file:
self.config = yaml.safe_load(file)
else:
raise Exception(f"Can only load json and yaml files.")
return self.config
def set_ref(self, config_dict):
"""
Sets the default configuration.
"""
self.ref_config = config_dict
self.ref_set = True
def export(self, filename, config_dict):
"""
Exports a given
Returns path of exported file.
"""
# file_type selection
if filename.endswith(".yaml") or filename.endswith(".yml"):
file_type = ".yaml"
elif filename.endswith(".json"):
file_type = ".json"
else:
filename += self.default_ext
file_type = self.default_ext
serializer = None
if file_type == ".json":
import json as serializer
elif file_type == ".yaml" or file_type == ".yml":
import yaml as serializer
export_config = serializer.dumps(config_dict, indent=4)
with open(filename, 'w') as file:
file.write(export_config)
return filename
def export_def_config(self, filename=None):
"""
Generates a configuration file with default configuration options.
Returns path of exported file.
"""
# Filename selection
if filename == None:
filename = os.path.abspath(self.default_config_name)
self.export(filename, config_dict=self.ref_config)
print(f"[Configr] Configuration file generated → `{filename}`.")
return filename
def verify_syntax(self, filename):
"""
Verifies synatax and catches any exceptions during the parsing of files.
"""
with open(filename, 'r') as file_obj:
if filename.endswith(".json"):
try:
json.load(file_obj)
except ValueError as e:
return False
return True
if filename.endswith(".yml") or filename.endswith(".yaml"):
try:
yaml.safe_load(file_obj)
except yaml.YAMLError as e:
return False
return True
else:
return False
def verify_and_patch(self, patch_file=True):
"""
Fill missing fields and generate.
patch_file : True value applies the patch to the file as well.
Returns `True` when verification passed without any patching, `False` otherwise.
"""
# Check if the default configuration is set.
if not self.ref_set:
raise Exception("[Configr] Default configuration is not set. Verification not possible.")
update_state = False
# Unknown keys -> Only generates warning, does not remove the extra fields.
for key in self.config:
if not key in self.ref_config.keys():
print(f"[Warning] Unrecognised configuration field -> {key} : {str(self.config[key])}")
# Missing keys
for key in self.ref_config:
if not key in self.config:
self.config[key] = self.ref_config[key] #Add
print(f"[Warning] Configuration : Missing field added -> `{key}: {str(self.config[key])}` .")
update_state = True
if update_state == True and patch_file == True:
self.export(self.file_path, self.config)
return (not update_state)
# Higher level APIs ↓
def load_seq(self, directory): #TODO
"""
All configuration operations clubbed into one function.
* In case multiple config possibilities exist, choose alphabetically.
* Generate json file if config not found.
* Patches applied to file as well.
Returns the config dictionary at the end.
"""
conf_path = self.find_config(directory, ext=self.default_ext, alphabatical=True)
if conf_path == None:
print("[ERROR] No configuration file was found in the current directory.")
conf_path = self.export_def_config() #To file
# Load, verify and patch
conf_path = os.path.abspath(conf_path)
self.load(conf_path)
self.verify_and_patch(patch_file=True)
return self.config
#-- END of file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment