Align valid_entity_id with new slugify (#20231)

* slug

* ensure a dot

* fix

* schema_with_slug_keys

* lint

* test
This commit is contained in:
Johann Kellerman 2019-01-21 19:45:11 +02:00 committed by Paulus Schoutsen
parent 6ca0da5c52
commit c36c708068
5 changed files with 32 additions and 17 deletions

View File

@ -45,7 +45,7 @@ SCRIPT_ENTRY_SCHEMA = vol.Schema({
}) })
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({cv.slug: SCRIPT_ENTRY_SCHEMA}) DOMAIN: cv.schema_with_slug_keys(SCRIPT_ENTRY_SCHEMA)
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
SCRIPT_SERVICE_SCHEMA = vol.Schema(dict) SCRIPT_SERVICE_SCHEMA = vol.Schema(dict)

View File

@ -170,10 +170,9 @@ def _no_duplicate_auth_mfa_module(configs: Sequence[Dict[str, Any]]) \
return configs return configs
PACKAGES_CONFIG_SCHEMA = vol.Schema({ PACKAGES_CONFIG_SCHEMA = cv.schema_with_slug_keys( # Package names are slugs
cv.slug: vol.Schema( # Package names are slugs vol.Schema({cv.string: vol.Any(dict, list, None)}) # Component config
{cv.string: vol.Any(dict, list, None)}) # Component configuration )
})
CUSTOMIZE_DICT_SCHEMA = vol.Schema({ CUSTOMIZE_DICT_SCHEMA = vol.Schema({
vol.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
@ -627,7 +626,7 @@ def _identify_config_schema(module: ModuleType) -> \
except (AttributeError, KeyError): except (AttributeError, KeyError):
return None, None return None, None
t_schema = str(schema) t_schema = str(schema)
if t_schema.startswith('{'): if t_schema.startswith('{') or 'schema_with_slug_keys' in t_schema:
return ('dict', schema) return ('dict', schema)
if t_schema.startswith(('[', 'All(<function ensure_list')): if t_schema.startswith(('[', 'All(<function ensure_list')):
return ('list', schema) return ('list', schema)

View File

@ -12,7 +12,6 @@ import functools
import logging import logging
import os import os
import pathlib import pathlib
import re
import sys import sys
import threading import threading
from time import monotonic from time import monotonic
@ -43,7 +42,7 @@ from homeassistant.util.async_ import (
fire_coroutine_threadsafe) fire_coroutine_threadsafe)
from homeassistant import util from homeassistant import util
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.util import location from homeassistant.util import location, slugify
from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA
# Typing imports that create a circular dependency # Typing imports that create a circular dependency
@ -62,9 +61,6 @@ DOMAIN = 'homeassistant'
# How long we wait for the result of a service call # How long we wait for the result of a service call
SERVICE_CALL_LIMIT = 10 # seconds SERVICE_CALL_LIMIT = 10 # seconds
# Pattern for validating entity IDs (format: <domain>.<entity>)
ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$")
# How long to wait till things that run on startup have to finish. # How long to wait till things that run on startup have to finish.
TIMEOUT_EVENT_START = 15 TIMEOUT_EVENT_START = 15
@ -77,8 +73,12 @@ def split_entity_id(entity_id: str) -> List[str]:
def valid_entity_id(entity_id: str) -> bool: def valid_entity_id(entity_id: str) -> bool:
"""Test if an entity ID is a valid format.""" """Test if an entity ID is a valid format.
return ENTITY_ID_PATTERN.match(entity_id) is not None
Format: <domain>.<entity> where both are slugs.
"""
return ('.' in entity_id and
slugify(entity_id) == entity_id.replace('.', '_', 1))
def valid_state(state: str) -> bool: def valid_state(state: str) -> bool:

View File

@ -319,7 +319,23 @@ def service(value):
.format(value)) .format(value))
def slug(value): def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable:
"""Ensure dicts have slugs as keys.
Replacement of vol.Schema({cv.slug: value_schema}) to prevent misleading
"Extra keys" errors from voluptuous.
"""
schema = vol.Schema({str: value_schema})
def verify(value: Dict) -> Dict:
"""Validate all keys are slugs and then the value_schema."""
for key in value.keys():
slug(key)
return schema(value)
return verify
def slug(value: Any) -> str:
"""Validate value is a valid slug.""" """Validate value is a valid slug."""
if value is None: if value is None:
raise vol.Invalid('Slug should not be None') raise vol.Invalid('Slug should not be None')
@ -330,7 +346,7 @@ def slug(value):
raise vol.Invalid('invalid slug {} (try {})'.format(value, slg)) raise vol.Invalid('invalid slug {} (try {})'.format(value, slg))
def slugify(value): def slugify(value: Any) -> str:
"""Coerce a value to a slug.""" """Coerce a value to a slug."""
if value is None: if value is None:
raise vol.Invalid('Slug should not be None') raise vol.Invalid('Slug should not be None')

View File

@ -139,11 +139,11 @@ class TestWOLSwitch(unittest.TestCase):
'mac_address': '00-01-02-03-04-05', 'mac_address': '00-01-02-03-04-05',
'host': 'validhostname', 'host': 'validhostname',
'turn_off': { 'turn_off': {
'service': 'shell_command.turn_off_TARGET', 'service': 'shell_command.turn_off_target',
}, },
} }
}) })
calls = mock_service(self.hass, 'shell_command', 'turn_off_TARGET') calls = mock_service(self.hass, 'shell_command', 'turn_off_target')
state = self.hass.states.get('switch.wake_on_lan') state = self.hass.states.get('switch.wake_on_lan')
assert STATE_OFF == state.state assert STATE_OFF == state.state