mirror of https://github.com/home-assistant/core
Align valid_entity_id with new slugify (#20231)
* slug * ensure a dot * fix * schema_with_slug_keys * lint * test
This commit is contained in:
parent
6ca0da5c52
commit
c36c708068
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue