Load yaml using validator and include consider_home (#3743)

* Load yaml using validator, consider_home

* timedelta, track_if_away

* improve voluptuous

* Add default back

* Change time_period validation order
This commit is contained in:
Johann Kellerman 2016-10-08 03:08:33 +02:00 committed by Paulus Schoutsen
parent b09b13f552
commit fb94aaa5a1
4 changed files with 53 additions and 31 deletions

View file

@ -46,7 +46,6 @@ CONF_TRACK_NEW = 'track_new_devices'
DEFAULT_TRACK_NEW = True DEFAULT_TRACK_NEW = True
CONF_CONSIDER_HOME = 'consider_home' CONF_CONSIDER_HOME = 'consider_home'
DEFAULT_CONSIDER_HOME = 180 # seconds
CONF_SCAN_INTERVAL = 'interval_seconds' CONF_SCAN_INTERVAL = 'interval_seconds'
DEFAULT_SCAN_INTERVAL = 12 DEFAULT_SCAN_INTERVAL = 12
@ -70,8 +69,10 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
_CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.All(cv.ensure_list, [ _CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.All(cv.ensure_list, [
vol.Schema({ vol.Schema({
vol.Optional(CONF_TRACK_NEW): cv.boolean, vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean,
vol.Optional(CONF_CONSIDER_HOME): cv.positive_int # seconds vol.Optional(
CONF_CONSIDER_HOME, default=timedelta(seconds=180)): vol.All(
cv.time_period, cv.positive_timedelta)
}, extra=vol.ALLOW_EXTRA)])}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)])}, extra=vol.ALLOW_EXTRA)
DISCOVERY_PLATFORMS = { DISCOVERY_PLATFORMS = {
@ -118,9 +119,8 @@ def setup(hass: HomeAssistantType, config: ConfigType):
return False return False
else: else:
conf = conf[0] if len(conf) > 0 else {} conf = conf[0] if len(conf) > 0 else {}
consider_home = timedelta( consider_home = conf[CONF_CONSIDER_HOME]
seconds=conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME)) track_new = conf[CONF_TRACK_NEW]
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
devices = load_config(yaml_path, hass, consider_home) devices = load_config(yaml_path, hass, consider_home)
@ -282,7 +282,7 @@ class Device(Entity):
def __init__(self, hass: HomeAssistantType, consider_home: timedelta, def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
track: bool, dev_id: str, mac: str, name: str=None, track: bool, dev_id: str, mac: str, name: str=None,
picture: str=None, gravatar: str=None, picture: str=None, gravatar: str=None,
away_hide: bool=False) -> None: hide_if_away: bool=False) -> None:
"""Initialize a device.""" """Initialize a device."""
self.hass = hass self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id) self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
@ -307,7 +307,7 @@ class Device(Entity):
else: else:
self.config_picture = picture self.config_picture = picture
self.away_hide = away_hide self.away_hide = hide_if_away
@property @property
def name(self): def name(self):
@ -398,15 +398,29 @@ class Device(Entity):
def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
"""Load devices from YAML configuration file.""" """Load devices from YAML configuration file."""
dev_schema = vol.Schema({
vol.Required('name'): cv.string,
vol.Optional('track', default=False): cv.boolean,
vol.Optional('mac', default=None): vol.Any(None, vol.All(cv.string,
vol.Upper)),
vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean,
vol.Optional('gravatar', default=None): vol.Any(None, cv.string),
vol.Optional('picture', default=None): vol.Any(None, cv.string),
vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All(
cv.time_period, cv.positive_timedelta)
})
try: try:
return [ result = []
Device(hass, consider_home, device.get('track', False), devices = load_yaml_config_file(path)
str(dev_id).lower(), None if device.get('mac') is None for dev_id, device in devices.items():
else str(device.get('mac')).upper(), try:
device.get('name'), device.get('picture'), device = dev_schema(device)
device.get('gravatar'), device['dev_id'] = cv.slug(dev_id)
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE)) except vol.Invalid as exp:
for dev_id, device in load_yaml_config_file(path).items()] log_exception(exp, dev_id, devices)
else:
result.append(Device(hass, **device))
return result
except (HomeAssistantError, FileNotFoundError): except (HomeAssistantError, FileNotFoundError):
# When YAML file could not be loaded/did not contain a dict # When YAML file could not be loaded/did not contain a dict
return [] return []

View file

@ -167,7 +167,16 @@ def time_period_str(value: str) -> timedelta:
return offset return offset
time_period = vol.Any(time_period_str, timedelta, time_period_dict) def time_period_seconds(value: Union[int, str]) -> timedelta:
"""Validate and transform seconds to a time offset."""
try:
return timedelta(seconds=int(value))
except (ValueError, TypeError):
raise vol.Invalid('Expected seconds, got {}'.format(value))
time_period = vol.Any(time_period_str, time_period_seconds, timedelta,
time_period_dict)
def match_all(value): def match_all(value):

View file

@ -59,15 +59,13 @@ class TestComponentsDeviceTracker(unittest.TestCase):
def test_reading_broken_yaml_config(self): # pylint: disable=no-self-use def test_reading_broken_yaml_config(self): # pylint: disable=no-self-use
"""Test when known devices contains invalid data.""" """Test when known devices contains invalid data."""
files = {'empty.yaml': '', files = {'empty.yaml': '',
'bad.yaml': '100', 'nodict.yaml': '100',
'ok.yaml': 'my_device:\n name: Device'} 'allok.yaml': 'my_device:\n name: Device'}
args = {'hass': self.hass, 'consider_home': timedelta(seconds=60)}
with patch_yaml_files(files): with patch_yaml_files(files):
# File is empty assert device_tracker.load_config('empty.yaml', **args) == []
assert device_tracker.load_config('empty.yaml', None, False) == [] assert device_tracker.load_config('nodict.yaml', **args) == []
# File contains a non-dict format assert len(device_tracker.load_config('allok.yaml', **args)) == 1
assert device_tracker.load_config('bad.yaml', None, False) == []
# A file that works fine
assert len(device_tracker.load_config('ok.yaml', None, False)) == 1
def test_reading_yaml_config(self): def test_reading_yaml_config(self):
"""Test the rendering of the YAML configuration.""" """Test the rendering of the YAML configuration."""
@ -75,7 +73,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
device = device_tracker.Device( device = device_tracker.Device(
self.hass, timedelta(seconds=180), True, dev_id, self.hass, timedelta(seconds=180), True, dev_id,
'AB:CD:EF:GH:IJ', 'Test name', picture='http://test.picture', 'AB:CD:EF:GH:IJ', 'Test name', picture='http://test.picture',
away_hide=True) hide_if_away=True)
device_tracker.update_config(self.yaml_devices, dev_id, device) device_tracker.update_config(self.yaml_devices, dev_id, device)
self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN,
TEST_PLATFORM)) TEST_PLATFORM))
@ -211,7 +209,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
device = device_tracker.Device( device = device_tracker.Device(
self.hass, timedelta(seconds=180), True, dev_id, None, self.hass, timedelta(seconds=180), True, dev_id, None,
friendly_name, picture, away_hide=True) friendly_name, picture, hide_if_away=True)
device_tracker.update_config(self.yaml_devices, dev_id, device) device_tracker.update_config(self.yaml_devices, dev_id, device)
self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN,
@ -228,7 +226,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id) entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id)
device = device_tracker.Device( device = device_tracker.Device(
self.hass, timedelta(seconds=180), True, dev_id, None, self.hass, timedelta(seconds=180), True, dev_id, None,
away_hide=True) hide_if_away=True)
device_tracker.update_config(self.yaml_devices, dev_id, device) device_tracker.update_config(self.yaml_devices, dev_id, device)
scanner = get_component('device_tracker.test').SCANNER scanner = get_component('device_tracker.test').SCANNER
@ -246,7 +244,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id) entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id)
device = device_tracker.Device( device = device_tracker.Device(
self.hass, timedelta(seconds=180), True, dev_id, None, self.hass, timedelta(seconds=180), True, dev_id, None,
away_hide=True) hide_if_away=True)
device_tracker.update_config(self.yaml_devices, dev_id, device) device_tracker.update_config(self.yaml_devices, dev_id, device)
scanner = get_component('device_tracker.test').SCANNER scanner = get_component('device_tracker.test').SCANNER

View file

@ -200,17 +200,18 @@ def test_time_period():
schema = vol.Schema(cv.time_period) schema = vol.Schema(cv.time_period)
for value in ( for value in (
None, '', 1234, 'hello:world', '12:', '12:34:56:78', None, '', 'hello:world', '12:', '12:34:56:78',
{}, {'wrong_key': -10} {}, {'wrong_key': -10}
): ):
with pytest.raises(vol.MultipleInvalid): with pytest.raises(vol.MultipleInvalid):
schema(value) schema(value)
for value in ( for value in (
'8:20', '23:59', '-8:20', '-23:59:59', '-48:00', {'minutes': 5} '8:20', '23:59', '-8:20', '-23:59:59', '-48:00', {'minutes': 5}, 1, '5'
): ):
schema(value) schema(value)
assert timedelta(seconds=180) == schema('180')
assert timedelta(hours=23, minutes=59) == schema('23:59') assert timedelta(hours=23, minutes=59) == schema('23:59')
assert -1 * timedelta(hours=1, minutes=15) == schema('-1:15') assert -1 * timedelta(hours=1, minutes=15) == schema('-1:15')