smartenv

Intelligent environment variable handling


Keywords
environment, variable, parsing
License
MIT
Install
pip install smartenv==1.0.0

Documentation

Smartenv

PyPI PyPI PyPI PyPI Build Status codecov

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 to str
se.Str('val')} == 'val'
  • Int: alias to int
se.Int('42')} == 42
  • Float: alias to float
se.Float('3.1415')} == 3.1415
  • Json: alias to json.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