Package: cklass
cklassHierarchical config loader from files and env variables to class.This module takes care of loading configuration files, secret files
and environment variables to single configuration class without a hassle!Features:easy to use (just one function!) and configurecomes with sane defaultslogical hierarchical order of importancesupports infinite number of file formats via custom format loadersautomatic lookup of config and secret files in specified directoriesConstraints:all keys are case-insensitiveInstallationpipinstallcklassUsageAll you have to do is to create a class and call functioncklass.load_config()with it as an argument.Case SensitivityClass name (and nested classes which represent dictionaries)
have to be upper-cased or title-cased, eg.ConfigorCONFIGwill work, butconfigwon't.All variables that you want to search and match have to be upper-cased.
This also means that all keys arenotcase sensitive.For example:# config.yamllogin:'thiswillbeignored'password:'thiswillbeignored'uSER:logIN:'pi'paSSwoRD:'raspberry'# secret.yamlUser:# this will overwrite `logIN` and `pasSSwoRD` from abovelogin:'root'password:'yrrebpsar'# env variableEXPORTUSER__PASSWORD='mytopsecretpassword'# pythonimportcklassclassUser:LOGIN='this string will be overwritten'password='this will NOT be overwritten'Password='this will NOT be overwritten'PASSWORD='default password, will be overwritten'other_variable_kinda_like_private=42cklass.load_config(User)HierarchyEvery class-level keys have to be nested in top-level dictionary named
same as the class. Only class attributes will be overwritten,
there isnopossibility to add new attributes only by defining
them in configuration files.Each class loaded bycklass.load_config()will have it's attributes
overwritten according to order specified below:All values defined in class code are considered as defaultFunction will look for first config file with filename defined in_config_filenameand found in_config_filepathlist of dirs
will be taken into account and overwrite the values set in (1).Function will look for first secret file with filename defined in_secret_filenameand found in_secret_filepathlist of dirs
will be taken into account and overwrite the values set in (1) and (2).Function will look for matching environment variables of typestrwith optional prefix defined in_environ_prefixand overwrite the values
set in (1), (2) and (3).# pythonimportcklass# any name will workclassConfig:PATH_TO_SOMETHING='/etc/default/path/'classUser:NAME=''EMAIL=''PASS=''DEBUG=FalseclassSERVER:OPEN_PORTS=['80']SECRET_KEY=''_type_safe=True_environ_prefix='MYAPP'_config_filename='config.yaml'_secret_filename='secret.json'_config_filepath=['/etc/myapp/conf/']_secret_filepath=['.']_format_loaders={'json':['jsonlib','read'],'yaml':['metayaml','read'],}cklass.load_config(Config)# config.yamlconfig:path-to-something:/etc/app/debug:Trueserver:open-ports:-'22'-'80'-'443'# secret.yamlconfig:user:name:Your Secret Nameemail:example@example.com# envvars.shexportMYAPP__CONFIG__SECRET_KEY="supersecretkey"exportMYAPP__CONFIG__USER__PASS="secretpassword"Default ValuesEvery class passed tocklass.load_config()can define below variables with appropriate
type for some manipulating it's behaviour.All values specified below are considered as default. Any of these variables can be omitted.Type Safety_type_safe=TrueThis will compare and ensure that all keys overwritten in config/secret file have the same
type as the variables defined in class exceptNone. If set toFalsethis check
will be skipped.
Example:Config.VALIDATE_ME = Truewill match onlybooltype from config file.Config.DOESNT_MATTER = Nonewill match any type from config file and environment variableCONFIG__DOESNT_MATTER.Environment Prefix_environ_prefix=''This allows you to define custom environment variable prefix to act as a namespace.
You could for example set this to'MYAPP'which would cause to look up only
environment variables starting with such prefix, likeMYAPP__CONFIG__HOME_DIR.
Class and nesting is defined with two underscores__, hence key names may contain
only single underscores - eg.CORRECT_NAME,INCORRECT___NAME.Environment variables support three types: bool converted from stringTRUE/FALSE,
numbers converted from string with numbers and string if two previous conversions failed.Config / Secret File Names_config_filename='config.yaml'_secret_filename='secret.yaml'File name of the config. Extension has to match the defined one in_format_loaders.Config / Secret File Paths_config_filepath=['.']_secret_filepath=['.']List of paths where function will look for_config__filenameand_secret_filename.
For example, You could change it to['~', '.']which would cause the function to
search forconfig.yamlin$HOME/config.yamland then in$PWD/config.yaml.
Only the first file which will be successfully opened will be taken into account.Format Loaders_format_loaders={'ini':['ini','load'],'json':['json','load'],'toml':['toml','load'],'yaml':['yaml','safe_load'],}Format loaders consists of a nested dictionary with key matching file extension
and value defined as a two-element list. Default format loader enables the usage
ofini,json,tomlandyamlfile types.
In order for this to work you need to have installed appropriate python packages
specified in the list.Example:yaml: ['metayaml', 'read']says that for any config/secret file
withyamlextension will be loaded viareadfunction defined inmetayamlmodule.Real-Live ExampleSeeexamplesdirectory.# config.pyimportcklassclassRoot:_environ_prefix='SIMPLEWEBAPP'_config_filepath=['./conf']_secret_filepath=['./conf']classCommon(Root):NAME='Simple Web App'DEBUG=TrueDATE=''BASE_DIR='/app'SRC_DIR='./src'ALLOWED_HOSTS=[]classSecret:KEY=''_config_filename='common.yaml'_secret_filename='common.json'classDatabase(Root):ENGINE='sqlite3'HOST='localhost'NAME='simplewebapp_db'PORT=1111classCredentials:USER='readonly'PASS='readonly'_config_filename='database.yaml'_secret_filename='database.json'cklass.load_config(Common)cklass.load_config(Database)print(f'''Common:NAME = '{Common.NAME}'DEBUG ={Common.DEBUG}DATE = '{Common.DATE}'BASE_DIR = '{Common.BASE_DIR}'SRC_DIR = '.{Common.SRC_DIR}'ALLOWED_HOSTS ={Common.ALLOWED_HOSTS}Secret:KEY = '{Common.Secret.KEY}'Database:ENGINE = '{Database.ENGINE}'HOST = '{Database.HOST}'NAME = '{Database.NAME}'PORT ={Database.PORT}Credentials:USER ={Database.Credentials.USER}'PASS = '{Database.Credentials.PASS}'''')# conf/common.yamlCommon:debug:noallowed-hosts:-'localhost'-'127.0.0.1'-'mydomain.local'#conf/common.json{"Common":{"Secret":{"key":"AAAAAAAA"}}}# conf/database.yamlDatabase:engine:postgresqlhost:psql://localhost#conf/database.json{"database":{"credentials":{"user":"dbuser","pass":"pbpass"}}}# conf/environment.sh#!/bin/bashexportSIMPLEWEBAPP__COMMON__DEBUG=TRUEexportSIMPLEWEBAPP__COMMON__DATE="$(date)"exportSIMPLEWEBAPP__COMMON__SECRET__KEY="seckey"exportSIMPLEWEBAPP__DATABASE__PORT=6621exportSIMPLEWEBAPP__DATABASE__CREDENTIALS__PASS="supersecretdbpass"Resultsourceconf/environment.sh
python3config.pyCommon:NAME='Simple Web App'# overwritten in conf/common.yaml# then overwritten by environment variableDEBUG=True# overwritten by environment variableDATE='Mon Apr 15 12:35:39 CEST 2019'BASE_DIR='/app'SRC_DIR='./src'# overwritten in conf/common.yamlALLOWED_HOSTS=['localhost','127.0.0.1','mydomain.local']classSecret:# overwritten in conf/common.json# then overwritten by environment variableKEY='seckey'Database:# overwritten in conf/database.yamlENGINE='postgresql'HOST='psql://localhost'NAME='simplewebapp_db'# overwritten by environment variablePORT=6621classCredentials:# overwritten in conf/database.jsonUSER='dbuser'# overwritten in conf/database.json# then overwritten by environment variablePASS='supersecretdbpass'