Refactor child validation (#23482)

* Try to make the process more readable and paritioned.
* Validate child values using set message.
* Only validate using relevant schemas.
* Extract node validation.
* Rework const types and schemas.
* Rework child validator.
* Enhance warning logging message.
This commit is contained in:
Martin Hjelmare 2019-05-08 17:26:40 +02:00 committed by Paulus Schoutsen
parent c384adeef4
commit c26af22edd
4 changed files with 214 additions and 179 deletions

View file

@ -8,11 +8,12 @@ from homeassistant.const import CONF_NAME
from homeassistant.core import callback
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.util.decorator import Registry
from .const import (
ATTR_DEVICES, DOMAIN, MYSENSORS_CONST_SCHEMA, PLATFORM, SCHEMA, TYPE)
from .const import ATTR_DEVICES, DOMAIN, FLAT_PLATFORM_TYPES, TYPE_TO_PLATFORMS
_LOGGER = logging.getLogger(__name__)
SCHEMAS = Registry()
@callback
@ -24,58 +25,116 @@ def discover_mysensors_platform(hass, hass_config, platform, new_devices):
return task
def validate_child(gateway, node_id, child):
"""Validate that a child has the correct values according to schema.
def default_schema(gateway, child, value_type_name):
"""Return a default validation schema for value types."""
schema = {value_type_name: cv.string}
return get_child_schema(gateway, child, value_type_name, schema)
Return a dict of platform with a list of device ids for validated devices.
"""
validated = defaultdict(list)
if not child.values:
_LOGGER.debug(
"No child values for node %s child %s", node_id, child.id)
return validated
if gateway.sensors[node_id].sketch_name is None:
_LOGGER.debug("Node %s is missing sketch name", node_id)
return validated
@SCHEMAS.register(('light', 'V_DIMMER'))
def light_dimmer_schema(gateway, child, value_type_name):
"""Return a validation schema for V_DIMMER."""
schema = {'V_DIMMER': cv.string, 'V_LIGHT': cv.string}
return get_child_schema(gateway, child, value_type_name, schema)
@SCHEMAS.register(('light', 'V_PERCENTAGE'))
def light_percentage_schema(gateway, child, value_type_name):
"""Return a validation schema for V_PERCENTAGE."""
schema = {'V_PERCENTAGE': cv.string, 'V_STATUS': cv.string}
return get_child_schema(gateway, child, value_type_name, schema)
@SCHEMAS.register(('light', 'V_RGB'))
def light_rgb_schema(gateway, child, value_type_name):
"""Return a validation schema for V_RGB."""
schema = {'V_RGB': cv.string, 'V_STATUS': cv.string}
return get_child_schema(gateway, child, value_type_name, schema)
@SCHEMAS.register(('light', 'V_RGBW'))
def light_rgbw_schema(gateway, child, value_type_name):
"""Return a validation schema for V_RGBW."""
schema = {'V_RGBW': cv.string, 'V_STATUS': cv.string}
return get_child_schema(gateway, child, value_type_name, schema)
@SCHEMAS.register(('switch', 'V_IR_SEND'))
def switch_ir_send_schema(gateway, child, value_type_name):
"""Return a validation schema for V_IR_SEND."""
schema = {'V_IR_SEND': cv.string, 'V_LIGHT': cv.string}
return get_child_schema(gateway, child, value_type_name, schema)
def get_child_schema(gateway, child, value_type_name, schema):
"""Return a child schema."""
set_req = gateway.const.SetReq
child_schema = child.get_schema(gateway.protocol_version)
schema = child_schema.extend(
{vol.Required(
set_req[name].value, msg=invalid_msg(gateway, child, name)):
child_schema.schema.get(set_req[name].value, valid)
for name, valid in schema.items()},
extra=vol.ALLOW_EXTRA)
return schema
def invalid_msg(gateway, child, value_type_name):
"""Return a message for an invalid child during schema validation."""
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
s_name = next(
return "{} requires value_type {}".format(
pres(child.type).name, set_req[value_type_name].name)
def validate_set_msg(msg):
"""Validate a set message."""
if not validate_node(msg.gateway, msg.node_id):
return {}
child = msg.gateway.sensors[msg.node_id].children[msg.child_id]
return validate_child(msg.gateway, msg.node_id, child, msg.sub_type)
def validate_node(gateway, node_id):
"""Validate a node."""
if gateway.sensors[node_id].sketch_name is None:
_LOGGER.debug("Node %s is missing sketch name", node_id)
return False
return True
def validate_child(gateway, node_id, child, value_type=None):
"""Validate a child."""
validated = defaultdict(list)
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
child_type_name = next(
(member.name for member in pres if member.value == child.type), None)
if s_name not in MYSENSORS_CONST_SCHEMA:
_LOGGER.warning("Child type %s is not supported", s_name)
value_types = [value_type] if value_type else [*child.values]
value_type_names = [
member.name for member in set_req if member.value in value_types]
platforms = TYPE_TO_PLATFORMS.get(child_type_name, [])
if not platforms:
_LOGGER.warning("Child type %s is not supported", child.type)
return validated
child_schemas = MYSENSORS_CONST_SCHEMA[s_name]
def msg(name):
"""Return a message for an invalid schema."""
return "{} requires value_type {}".format(
pres(child.type).name, set_req[name].name)
for platform in platforms:
v_names = FLAT_PLATFORM_TYPES[platform, child_type_name]
if not isinstance(v_names, list):
v_names = [v_names]
v_names = [v_name for v_name in v_names if v_name in value_type_names]
for v_name in v_names:
child_schema_gen = SCHEMAS.get((platform, v_name), default_schema)
child_schema = child_schema_gen(gateway, child, v_name)
try:
child_schema(child.values)
except vol.Invalid as exc:
_LOGGER.warning(
"Invalid %s on node %s, %s platform: %s",
child, node_id, platform, exc)
continue
dev_id = id(gateway), node_id, child.id, set_req[v_name].value
validated[platform].append(dev_id)
for schema in child_schemas:
platform = schema[PLATFORM]
v_name = schema[TYPE]
value_type = next(
(member.value for member in set_req if member.name == v_name),
None)
if value_type is None:
continue
_child_schema = child.get_schema(gateway.protocol_version)
vol_schema = _child_schema.extend(
{vol.Required(set_req[key].value, msg=msg(key)):
_child_schema.schema.get(set_req[key].value, val)
for key, val in schema.get(SCHEMA, {v_name: cv.string}).items()},
extra=vol.ALLOW_EXTRA)
try:
vol_schema(child.values)
except vol.Invalid as exc:
level = (logging.WARNING if value_type in child.values
else logging.DEBUG)
_LOGGER.log(
level,
"Invalid values: %s: %s platform: node %s child %s: %s",
child.values, platform, node_id, child.id, exc)
continue
dev_id = id(gateway), node_id, child.id, value_type
validated[platform].append(dev_id)
return validated