So quick introduction to voluptuous. It’s a library which can check incoming data, transform the data (if needed) and reject incorrect data. You can install it through pip.
In voluptuous you create a schema and the data is then checked against this schema. If the data doesn’t match the schema, it will throw an error. A schema can have required and optional arguments. You can check the input against some included functions like str (string) or int (integer) or you can build your own validation functions.
Now let’s get to a real life example from my appbase.py module modified for demo purpose.
first we import the library:
import voluptuous as vol
then we create the schema with vol.Schema():
APP_SCHEMA = vol.Schema({
vol.Required('module'): str,
vol.Required('class'): str,
vol.Optional('disabled_states'): vol.Schema({
vol.Optional('presence'): str,
vol.Optional('days'): str,
}),
}, extra=vol.ALLOW_EXTRA)
the above schema starts with ‘{’ for a dictionary, then a required key named ‘module’ with a value which needs to be a string. Then another required key named ‘class’, which also needs a string as value. Afterwards follows an optional key named ‘disabled_states’ for which we define another schema with the optional keys ‘presence’ and ‘days’. The ‘extra=vol.ALLOW_EXTRA’ at the end is there to allow additional key, value pairs in the dictionary.
So now our schema requires the yaml file to looke like this, where the disabled_states dictionary is optional:
vacuum_app:
module: a string
class: a string
disabled_states:
presence: a string
days: a string
Now that we created our schema, we need to add it to the app and check it.
class AppBase(Hass):
"""Define a base automation object."""
APP_SCHEMA = APP_SCHEMA
def initialize(self) -> None:
"""Initialize."""
# Check if the app configuration is correct:
try:
self.APP_SCHEMA(self.args)
except vol.Invalid as err:
self.error(f"Invalid format: {err}", level='ERROR')
return
Here we try if the config is valid against the schema with self.APP_SCHEMA(self.args)
and we except a vol.Invalid
error if the config is invalid. Then we define the error message and ‘return’ to exit the function.
Now we can take it a step further and define our own validation function to use in the voluptuous schema. For this we create a separate module and just import it when needed. Here is another modified example from my voluptuous_helper.py file, which checks if a given string is an home assistant entity-id:
from typing import Any, Sequence, TypeVar, Union
import voluptuous as vol
def entity_id(value: Any) -> str:
value = str(value).lower()
if '.' in value:
return value
raise vol.Invalid(f"Invalid entity-id: {value}")
this function just checks if there is a dot in the provided value, if there is a dot the function returns the value otherwise it raises a voluptuous error with the message we define. This error will later be shown in our error log if the config doesn’t match the schema.
Now we can use this function in our schema. First import the module we created import voluptuous_helper as vol_help
. The new schema:
APP_SCHEMA = vol.Schema({
vol.Required('module'): str,
vol.Required('class'): str,
vol.Optional('motion_sensor'): vol_help.entity_id,
vol.Optional('disabled_states'): vol.Schema({
vol.Optional('presence'): str,
vol.Optional('days'): str,
}),
}, extra=vol.ALLOW_EXTRA)
with the line vol.Optional('motion_sensor'): vol_help.entity_id
we added an optional key named ‘motion_sensor’ which needs to be an entity_id.
I hope this helps you get the concept of voluptuous. I think it’s awesome and saves me a lot of code like:
if 'motion_sensor' in self.args:
do something
else:
self.log("No motion sensor specified")
and because I included it in my base app, every app will automatically be checked against the schema defined there and the schema can then be extended for each app individually. As I said, I’m no programmer and there might be better ways to do this but for me this works out pretty well.