Usage

Parameter

Each parameter has certain attributes associated with it, which can simplify creation of self-explanatory parameters. For instance, in a GUI application a widget (e.g. number input) would require a tooltip, which if defined as a Parameter could be included in the doc attribute.

You can initilize individual Parameter using simple synthax:

import simpleparam as param

number = param.Number(42, doc="I am the answer")
number
>>> 42

number.doc
"I am the answer"

Value, range and type checking

Before any value is set, it is validated against its class characteristics (e.g. Integer -> all values must be integers).

number = param.Number("42")
[ ... ]
>>> ValueError: Parameter 'param' only takes numeric values

integer = param.Integer(1.0)
[ ... ]
>>> ValueError: Parameter 'param' must be an integer not '<class 'float'>'

value = param.Range([1], hardbounds=[0, 100])
[ ... ]
>>> ValueError: Range parameter 'param' must have two values

color = param.Color("#FFFF")
[ ... ]
>>> ValueError: Color 'param' only accepts valid RGB hex codes.

value = param.Boolean(2)
[ ... ]
ValueError: Boolean 'param' only takes a Boolean value or None.


value = param.List(1)
[ ... ]
ValueError: List 'param' must be a list.

Built-in range-checking

number = param.Number(42, hardbounds=[0, 41])
[ ... ]
>>> ValueError: Parameter 'param' must be at most 41

Which can be relaxed to allow value correction if its set outside of the hard boundary (in the case of Integer or Number)

number = param.Number(42, hardbounds=[0, 41], auto_bound=True)
number
>>> 41
number = param.Number(42, hardbounds=[0, None], auto_bound=True)
number
>>> 42

Range checking is also performed in the Range and List parametesr where each value is checked against the hardbounds

number = param.Range([0, 100], hardbounds=[0, 100])
number.value = [-1, 100]
[ ... ]
>>> ValueError: Parameter 'param' must be at least 0

value = param.List(["Hello", "World"], hardbounds=[None, 1])
[ ... ]
>>> ValueError: Parameter 'param' list length must be at most 1.

When creating Choice, any value to be set after it has been initilized will be checked against set choices

choice = param.Choice("Hello", choices=["Hello", "World"])
choice.value = "Goodbye"
[ ... ]
>>> ValueError: Value `Goodbye` not in the provided choices: ['Hello', 'World']

Choices can be updated of course

choice = param.Choice("Hello", choices=["Hello", "World"])
choice.choices = ["Hello", "World", "Goodbye"]
choice.value = "Goodbye"
choice
>>> "Goodbye"

Operations

You can do the typical numerical operations on each parameter:

number.value += 6
number
>>> 48

number.value /= 4
number
>>>  12.0

Important

When using Parameter outside of the ParameterStore, operations such as addition, subtraction, etc must be performed on the value attribute of the variable, otherwise it will be overwritten by the new value.

number = param.Number(42, doc="I am the answer")
print(type(number))
number.value += 1
print(type(number))
>>> <class 'simpleparam.Number'>
>>> <class 'simpleparam.Number'>

and not

number = param.Number(42, doc="I am the answer")
print(type(number))
number += 1
print(type(number))
>>> <class 'simpleparam.Number'>
>>> <class 'int'>

This is not necessarily the case for all parameters:

item = param.List(["Hello", "World"])
item[0] = "Goodbye"
item
>>> ['Goodbye', 'World']

item = param.List(["Hello", "World"])
del item[0]
item
>>>
['World']

ParameterStore

However, it is probably better to use parameters inside of a ParameterStore, where you can store multiple parameters together and take advantage of additioncal functionality (e.g. locking of parameters by setting them as constant, marking parameters as saveable or quickly generating self-explenatory JSON configuration files by using the export_as_json file).

Creation

import simpleparam as param

class Config(param.ParameterStore):
    def __init__(self):
        super(Config, self).__init__()

        # # you can add parameter docstrings by setting `doc`
        self.integer = param.Integer(42,
                                     doc="A not very important value")
        # `auto_bound` forces hard bounds on values that are outside the specification
        self.number = param.Number(42.,
                                   softbounds=[0, 100],
                                   hardbounds=[1, 100],
                                   auto_bound=True)
        # setting `allow_any` to False, will force values to be of `str` instance
        self.string = param.String("string",
                                   allow_any=False)
        # you can set internal parameter name by setting the value of `name`
        self.choice = param.Choice("foo",
                                   name="foo_bar_choice",
                                   choices=["foo", "bar"],
                                   )
        # parameters can be prevented from being changed by setting value of `constant
        self.color = param.Color("#FFFFFF",
                                 constant=True)
        self.bool = param.Boolean(True)
        self.range = param.Range(value=[0, 100], hardbounds=[0, 100])
        self.list = param.List([0, 1])

config = Config()
config

>>> ParameterStore(count=7)

Exporting to dictionary

ParameterStore can be exported as JSON dictionary by simply calling config.export_as_dict() to give:

export_dict = config.export_as_dict()
export_dict
>>> {
{'integer': {'name': 'param',
  'value': 42,
  'doc': 'A not very important value',
  'kind': 'Integer',
  'allow_None': True,
  'auto_bound': False,
  'softbounds': None,
  'hardbounds': None,
  'inclusive_bounds': [True, True],
  'step': None},
 'number': {'name': 'param',
  'value': 42.0,
  'doc': '',
  'kind': 'Number',
  'allow_None': True,
  'auto_bound': True,
  'softbounds': [0, 100],
  'hardbounds': [1, 100],
  'inclusive_bounds': [True, True],
  'step': None},
 'string': {'name': 'param',
  'value': 'string',
  'doc': '',
  'kind': 'String',
  'allow_None': True,
  'allow_any': False,
  'regex': None},
 'choice': {'name': 'foo_bar_choice',
  'value': 'foo',
  'doc': '',
  'kind': 'Choice',
  'allow_None': True,
  'choices': ['foo', 'bar']},
 'color': {'name': 'param',
  'value': '#FFFFFF',
  'doc': '',
  'kind': 'Color'},
 'bool': {'name': 'param',
  'value': True,
  'doc': '',
  'kind': 'Boolean',
  'allow_None': True},
 'range': {'name': 'param',
  'value': [0, 100],
  'doc': '',
  'kind': 'Range',
  'allow_None': True,
  'auto_bound': False,
  'softbounds': None,
  'hardbounds': [0, 100],
  'inclusive_bounds': [True, True],
  'step': None}
  'list': {'name': 'param',
  'value': [0, 1],
  'doc': '',
  'kind': 'List',
  'allow_None': True,
  'hardbounds': None}}

Importing from dictionary

which can be later read-in by using the config.load_from_dict(export_dict). Of course, if any value was changed in the export_dict object, this will be reflected in the instance of Config

class Config(param.ParameterStore):
    def __init__(self):
        super(Config, self).__init__()
        self.integer = param.Integer(42)

config = Config()
config.integer = 103
output = config.export_as_dict()
output
>>>
{'integer': {'name': 'param',
  'value': 199,  # lets change the value
  'doc': 'Lets change the documentation also',  # and add some documentation
  'kind': 'Integer',
  'allow_None': True,
  'auto_bound': False,
  'softbounds': None,
  'hardbounds': None,
  'inclusive_bounds': [True, True],
  'step': None}}

and now lets load it to see the changes

config = Config()
config.load_from_dict(output)
print(config.integer, config.integer.doc)
>>> 199 'Lets change the documentation also'

If you want to preserve certain values, for instance doc, you can set the value of ignored_attributes=['doc'] which will ensure that the value of doc is not overwritten when loading data from a dictionary.

config = Config()
config.load_from_dict(output, ignored_attributes=['doc'])
print(config.integer, config.integer.doc)
>>> 199 ''

You can also specify which value you want to load, for instance you only want to update the value of doc while keeping everything else unchanged. To do this, you can specify the allowed_attributes=['doc'].

config = Config()
config.load_from_dict(output, allowed_attributes=['doc'])
print(config.integer, config.integer.doc)
>>> 42 Lets change the documentation also

Important

You cannot specify both allowed_attributes and ignored_attributes. This will result in an error.

Pickling

You can pickle the ParameterStore object as you would do any other configuration object. When unpickling, values will be restored to their original state.