Package: biodome
biodomeControlled environmentsReading environment variables withos.environis pretty easy, but after
a while one gets pretty tired of having to cast variables to the right types
and handling fallback to defaults.This library provides a clean way read environment variables and fall back
to defaults in a sane way.How you were doing it:importostry:TIMEOUT=int(os.environ.get('TIMEOUT',10))exceptValueError:TIMEOUT=10Wordy, boilerplate, DRY violation, etc.How you will be doing it:importbiodomeTIMEOUT=biodome.environ.get('TIMEOUT',10)That’s right, it becomes a single line. But there’s a magic trick here: how
doesbiodomeknow thatTIMEOUTshould be set to anint? It knows
because it looks at the type of the default arguments. This works for a bunch
of different things:# Listsos.environ['IGNORE_KEYS']='[1, 2, 3]'biodome.environ.get('TIMEOUT',[])==[1,2,3]# Dictsos.environ['SETTINGS']='{"a": 1, "b": 2}'biodome.environ.get('SETTINGS',{})==dict(a=1,b=2)If you look carefully at the above, you can see that wesetthe data via
the stdlibos.environdictionary; that’s right,biodome.environis adrop-in replacementforos.environ. You don’t even have to switch out
your entire codebase, you can do it piece by piece.And while we’re on the subject ofsettingenv vars, withbiodomeyou
don’t have to cast them first, it does string casting internally automatically,
unlikeos.environ:# Dictsbiodome.environ['SETTINGS']=dict(b=2,a=1)# No cast requiredbiodome.environ.get('SETTINGS',{})==dict(a=1,b=2)Loading env filesbiodomealso provides a function to load a file which specifies the
values of environment variables. An example of such anenvfile:# myconfig.env
# This sets the log level for all the loggers in the program
LOGGER_LEVEL=info

# Hourly backups are stored at this path and named with a timestamp.
BACKUP_PATH=/data/backups/

# The number of times to retry outgoing HTTP requests if the status
# code is > 500
RETRY_TIME=5The name of the environment variable must be on the left and the value
on the right. Each variable must be on its own line. Lines starting with
a#are considered comments and are ignored.Thisenvfile can be loaded like this:>>>importbiodome>>>biodome.load_env_file('myconfig.env')>>>print(biodome.environ['RETRY_TIME'])5True and FalseI don’t know about you, but I use bool settings a LOT in environment variables,
so handling this properly is really important to me. When callingbiodome.environ.get('SETTING',default=<value>), the default value
can also be a bool, i.e.,TrueorFalse. In this case,any of the
following values,andtheir upper- or mixed-case equivalents will be
recognized asTrue:['1','y','yes','on','active','activated','enabled','true','t','ok','yeah']Anything not in this list will be considered asFalse.  Do you have ideas
for more things that should be considered asTrue? I take PRs!CallablesFor explictness it is often convenient to declare and load environment
variables at the top of the module in which they’re used:""" My new module """importbiodomeENABLE_SETTING_XYZ=biodome.environ.get('ENABLE_SETTING_XYZ',True)defblah():print(ENABLE_SETTING_XYZ)Youcouldcallenviron.get()inside the functions and methods where it
is used, but then you would lose the convenience of documenting all the
available environment variables at the top of the module.  As a solution to
this problem,biodomeprovides a way to produce a callable for a particular
setting.  An extra advantage of doing this is that it becomes quite easy to
make use of changes in environment variables on the fly.  Here’s the
modified example:""" My new module """importbiodomeENABLE_SETTING_XYZ=biodome.environ.get_callable(# Same as before'ENABLE_SETTING_XYZ',True)defblah():print(ENABLE_SETTING_XYZ())# Now a callable!How it works internallyThe key theme here is that thetypeof the default value is used to determine
how to cast the input value.  This works for the following types:intfloatstrlistdictset(NOTE: only supported in Python 3+ due toast.literal_eval())tupleFor the containers, we useast.literal_eval()which is much safer than
usingeval()because code is not evaluated. Safety first! (thanks to
@nickdirienzo for the tip)