Package: env_config
Declare and load configuration from environment variables.Supported features:declare different sets of variables for production, test and other environmentsload variables from file if necessaryparse configuration into different datatypesstrintfloatbool (‘True’, ‘False’, 1, 0, ‘yes’, ‘no’)str[]int[]float[]bool[]nested typeseasy to work with reports about missing variables and declaration issuesMissingenvironmentvariables:exportERR_KEY_1=[yourvaluehere]exportERR_KEY_1_DICT2_DICT3_KEY4=[yourvaluehere]exportERR_KEY_1_DICT2_KEY3=[yourvaluehere]Missingdeclarations:declare("undeclared",[yourdefinitionhere])Parseerrors:ERR_KEY_1_VALUE1:invalidliteralforint()withbase10:'some int value'InstallpipinstallconfigExamplesCreate a new Config instanceConfigure log levelsDeclare and load scalar valuesDeclare and load list valuesDeclare and load nested valuesNamespace your variablesAdd validationReloading configuration at runtimeDeclaring optional variablesLoading variables from a fileCreate a new Config instancefromenv_configimport# control error reporting.# If deferred, config errors are raised the first time Config.get() is called.# The error message contains a detailed report about all errors encountered while parsing config variables.# If not deferred, Config raises on the first error encountered. This most likely happens while calling Config.declare().# Default is defer_raise=Truecfg=Config(defer_raise=False)# load config from a file. See a more detailed example further down.cfg=Config(filename_variable='CONFIG_FILE')Configure log levelsenv_config has a simple mechanism to set log levels for python logging, including all configured loggers.
The library searches for environment variables that are build following this schema:LOG_LEVEL_{logger_name.upper}If found the values of each variable is applied as the log_level for the respective logger.
The environment variableLOG_LEVEL(without a logger name) set the log level for the root logger.the available values are:
- debug
- info
- warning
- error
- criticalimportosfromenv_configimportos.environ['LOG_LEVEL']='info'# set the root logger to logging.INFOos.environ['LOG_LEVEL_URLLIB3']='critical'# set the urllib3 logger to logging.CRITICALos.environ['LOG_LEVEL_PARAMIKO.TRANSPORT']='debug'# set paramiko.transport to logging.DEBUGcfg=Config()cfg.apply_log_levels()# read the environment variables and apply to the respective log levelsDeclare and load scalar valuesfromenv_configimportConfig,parse_int,parse_float,parse_str,parse_boolcfg=Config()# declare variables with the appropriate parsercfg.declare('my_int_variable',parse_int())cfg.declare('my_float_variable',parse_float())cfg.declare('my_str_variable',parse_str())cfg.declare('my_bool_variable',parse_bool())# load the values# will load the value of MY_INT_VARIABLE as an intint_result=cfg.get('my_int_variable')# will load the value of MY_FLOAT_VARIABLE as a floatfloat_result=cfg.get('my_float_variable')# will load the value of MY_STR_VARIABLE as a strstr_result=cfg.get('my_str_variable')Declare and load list valuesfromenv_configimportConfig,parse_int_listcfg=Config()# declare variables with the appropriate parsercfg.declare('my_int_list_variable',parse_int_list())# load the values# will load the value of MY_INT_LIST_VARIABLE as a list of ints.# By default it assumes the elements to be comma separatedint_list_result=cfg.get('my_int_list_variable')Declare and load nested valuesMost libraries need multiple variables to be correctly configured.
Nested values help reduce boilerplate necessary to wire configuration with the library.fromenv_configimportConfig,parse_strimportpsycopg2cfg=Config()cfg.declare('database',{'dbname':parse_str(),'user':parse_str(),'password':parse_str()},)# this will load values from these environment variables and parse them into a dict:#  - DATABASE_DBNAME#  - DATABASE_USER#  - DATABASE_PASSWORDpsyco_config=cfg.get('database')# the dict will look like this: {'dbname': 'some value', 'user': 'username', 'password': 'vsjkfl'}psyco_connection=psycopg2.connect(**psyco_config)Namespace your variablesfromenv_configimportConfig,parse_strimportpsycopg2cfg=Config(namespace='my_prefix')cfg.declare('database')# the value will be loaded from the environment variable: MY_PREFIX_DATABASEvalue=cfg.get('database')Add validationfromenv_configimportConfig,parse_str,parse_str_listfromvalidatorsimportemail# config expects validators to raise an Error on failure.# Since the validators package returns Failures instead of raising, we create a small adapter.defemail_validator(value):result=email(value)ifisinstance(result,ValidationFailure):raiseValueError('"{}" is not a valid email address'.format(value))cfg=Config()cfg.declare('valid_email',parse_str(validator=email_validator))# this also works with lists. The validator function is applied to each value separatelycfg.declare('valid_list_of_emails, parse_str_list(validator=email_validator))valid_email=cfg.get('valid_email')valid_list_of_emails=cfg.get('valid_list_of_emails')Reloading configuration at runtimefromenv_configimportConfig,parse_str,reloadcfg=Config()cfg.declare('some_value',parse_str())value=cfg.get('some_value')# Values are actually loaded during declare().# Changes to the environment at runtime are not picked up automatically.# Relaoding has to be triggered explicitly.cfg.reload()new_value=cfg.get('some_value')Declaring optional variablesSometimes you just want to load a subset of all variables. For example most applications nowadays get executed
in a live environment and in a testing environment.
Another example is different processes, for example a web endpoint and a background worker, sharing configuration setup.# config.pyfromenv_configimportConfig,parse_strdefdeclare_config(tag):required=('live','test')test_optional=('live',)cfg=Config()# this variable is available both in live and testcfg.declare('some_value',parse_str(),required,tag)# this variable is only available in live. In test it won't be loaded and only raises an error when accessed.cfg.declare('some_other_value',parse_str(),test_optional,tag)returncfg# live-app.pyfromconfigimportdeclare_config# the active tag is 'live', so all variables tagged with 'live' are required and raise errors when missing.cfg=declare_config('live')# access variablesval=cfg.get('some_value')# something_test.pyfromconfigimportdeclare_config# the active tag is 'test', so all variables tagged with 'test' are required and raise errors when missing.# All other variables become optional and only raise errors when accessed withcfg.declare_config('test')# access variablesval=cfg.get('some_value')# raise an error, because the variable is not available in 'test'val2=cfg.get('some_other_value')Loading variables from a fileSometimes it’s rather cumbersome to declare all the variables explicitly.
For example the PyCharm variable declaration is rather awkward to use.To elegantly deal with these kinds of situations, it’s possible to load variables declared to a tag from a bash file.
So only one variable (the file name) has to be declared. The rest is loaded from that file.
The file is not evaluated, though. Onlyexportdeclarations are extracted and parsed into variables.define the variable holding the file nameexportCONFIG_FILE=test.shCreate a file test.sh with the variable declarations.#!/usr/bin/env bash# comment is ignoredHIDDEN_VARIABLE="value not parsed"exportVISIBLE_VARIABLE_1="this value will be available"function{# if the line does not start with export it's ignored}# variables inside strings are not expanded. The value will contain the literal :code:`$OTHER_VARIABLE`.exportVARIABLE_CONTAINING_REFERENCE="$OTHER_VARIABLE"Then setup the CONFIG_FILE variable to load the file.fromenv_configimportConfig,parse_str# uses the value of CONFIG_FILE as the file name to load variables fromconfig=Config(filename_variable='CONFIG_FILE',defer_raise=False)# visible_variable_1 is declared in test and the current tag is test. variable1 will be loaded from test.shconfig.declare('visible_variable_1',parse_int(),('test',),'test'))# visible_variable_2 is declared in the 'default' tag and not available in the config file.# visible_variable_2 will be ignored because the current tag is 'test'config.declare('visible_variable_1',parse_int(),('default',),'test')