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:
parent
b09b13f552
commit
fb94aaa5a1
4 changed files with 53 additions and 31 deletions
|
@ -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 []
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue