Smartenv
Yet another package for parsing and validating environment variables. Inspired by envparse.
This package provides a dict-like Smartenv
class, which is used to enforce a
schema on environment variable object or an arbitrary dict
(useful for testing purposes), as well as a number of default "cast functions",
which cast/transform the string value into a certain Python object.
Installation
Through PyPI:
$ pip install smartenv
Manually:
$ pip install git+https://github.com/mikeroll/smartenv.git
or
$ git clone https://github.com/mikeroll/smartenv && cd smartenv
$ python setup.py install
Usage
The most common use is to define a schema for the environment. A schema is a variable->cast mapping, where cast is a function (class) used to convert the raw value. By default, only the variables defined in schema will be included in the resulting dict, but this can be overriden.
# INT=42, BOOL=yes, S=rawstring
import smartenv as se
env = se.Smartenv({'INT': se.Int, 'BOOL': se.Bool})
assert env == {'INT': 42, 'BOOL': True}
env = se.Smartenv({'INT': se.Int, 'BOOL': se.Bool}, include_all=True)
assert env == {'INT': 42, 'BOOL': True, 'S': 'rawstring'}
Schema entries can also be defined in extended format, e.g. for supplying a default value:
# NOTFOUND is not set
env = se.Smartenv({'NOTFOUND', dict(cast=int, default=404)})
assert env['NOTFOUND'] == 404
Data sources
Smartenv will work with any dict object. Default is os.environ
, a custom data can be supplied via the data
argument:
vardata = {'INT': 10, 'BOOL': 'no'}
env = se.Smartenv({'INT': se.Int, 'BOOL': se.Bool}, data=vardata)
assert env == {'INT': 10, 'BOOL': False}
Bundled cast functions
The package includes cast functions for most commonly used variable types.
Simple functions:
-
Str
: alias tostr
se.Str('val')} == 'val'
-
Int
: alias toint
se.Int('42')} == 42
-
Float
: alias tofloat
se.Float('3.1415')} == 3.1415
-
Json
: alias tojson.loads
se.Json('{"var": "val"}')} == {'var': 'val'}
-
List
: treat the value as a comma-delimited list
se.List('a, b, c, d')} == ['a', 'b', 'c', 'd']
-
Dict
: treat the value as comma-delimited key=value pairs
se.Str('a=1, b=2')} == {'a': '1', 'b': '2'}
Complex functions - these are factories for simple types:
-
Listx(delimiter, strip)
:
customlist = se.Listx(delimiter='|', strip=False)
customlist(' a | b | c | d ') == [' a ', ' b ', ' c ', ' d ']
-
Dictx(delimiter, assoc_char, strip)
:
customdict = se.Dictx(delimiter='|', assoc_char='->', strip=False)
customdict('a-> 2 |b-> 3 |c -> 4') == {'a': ' 2 ', 'b': ' 3 ', 'c ': ' 4'}
Nested structures
smartenv can also handle nested structures like "list of dicts" or "list if list of ints". The cast argument can be defined as a list/tuple, that way each subsequent function will be aplied to each of the outputs of the previous one.
# LIST_OF_INTS=1, 2, 3, 4
env = Smartenv({'LIST_OF_INTS': (se.List, # ["1", "2", "3", "4"]
se.Int)})
assert env['LIST_OF_INTS'] == [1, 2, 3, 4]
# LIST_OF_DICTS=a => 2, c => 3| d => 4
env = Smartenv({
'LIST_OF_DICTS': (se.Listx(delimiter='|'), # ["a => 2, c => 3", "d => 4"]
se.Dictx(delimiter=',', assoc_char='=>'), # [{'a': '2', 'c': '3'}, {'d' => '4'}]
se.Int)
})
assert env['LIST_OF_DICTS'] == [{'a': 2, 'c': 3}, {'d': 4}]
Variable subsets
smartenv supports grouping variables matching a certain regex. This behaviour is enabled by specifying a regex instead of a full name and passing a special collect_as
argument to schema entry:
# BOOL0=1
# BOOL1=yeS
# BOOL2=FaLsE
# BOOL3=42
env = Smartenv({r'BOOL\d': dict(cast=se.Bool,
collect_as='BOOLEANS')})
assert env['BOOLEANS'] == {'BOOL0': True,
'BOOL1': True,
'BOOL2': False,
'BOOL3': False}
Manual parsing
You can actually do without a schema and manually parse a couple of variables with the parse
method. Or temporarily override schema options for whatever reasons:
# INT=42, FLOAT=9.75, LOL is not set
env = Smartenv({'INT': se.Int,
'LOL': dict(cast=se.Int, default=1)})
assert env.parse('INT', se.Float) == 42.0
assert env.parse('LOL', se.Str) == '1'
assert env.parse('LOL', default=2) == 2
# FLOAT was not previously defined in schema, so it is not present in env.
# However you can parse it manually and save it.
assert env.parse('FLOAT', cast=se.Float, save=True) == 9.75
assert env['FLOAT'] == 9.75
Developing
Nothing special here. Tests are written with pytest
, everything automated via tox
. This includes unit tests, pep8 checks and code coverage via pytest
plugins.
$ python setup.py test
$ tox