Allow substitutions to be valid names (#4726)

This commit is contained in:
Joel Goguen 2023-05-17 00:33:08 -04:00 committed by GitHub
parent 77695aa55b
commit b3ed988119
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 14 deletions

View file

@ -1,19 +1,14 @@
import logging import logging
import re
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import core from esphome import core
from esphome.const import CONF_SUBSTITUTIONS from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS
from esphome.yaml_util import ESPHomeDataBase, make_data_base from esphome.yaml_util import ESPHomeDataBase, make_data_base
from esphome.config_helpers import merge_config from esphome.config_helpers import merge_config
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
VALID_SUBSTITUTIONS_CHARACTERS = (
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
)
def validate_substitution_key(value): def validate_substitution_key(value):
value = cv.string(value) value = cv.string(value)
@ -42,12 +37,6 @@ async def to_code(config):
pass pass
# pylint: disable=consider-using-f-string
VARIABLE_PROG = re.compile(
"\\$([{0}]+|\\{{[{0}]*\\}})".format(VALID_SUBSTITUTIONS_CHARACTERS)
)
def _expand_substitutions(substitutions, value, path, ignore_missing): def _expand_substitutions(substitutions, value, path, ignore_missing):
if "$" not in value: if "$" not in value:
return value return value
@ -56,7 +45,7 @@ def _expand_substitutions(substitutions, value, path, ignore_missing):
i = 0 i = 0
while True: while True:
m = VARIABLE_PROG.search(value, i) m = cv.VARIABLE_PROG.search(value, i)
if not m: if not m:
# Nothing more to match. Done # Nothing more to match. Done
break break

View file

@ -53,6 +53,7 @@ from esphome.const import (
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
TYPE_GIT, TYPE_GIT,
TYPE_LOCAL, TYPE_LOCAL,
VALID_SUBSTITUTIONS_CHARACTERS,
) )
from esphome.core import ( from esphome.core import (
CORE, CORE,
@ -79,6 +80,11 @@ from esphome.yaml_util import make_data_base
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=consider-using-f-string
VARIABLE_PROG = re.compile(
"\\$([{0}]+|\\{{[{0}]*\\}})".format(VALID_SUBSTITUTIONS_CHARACTERS)
)
# pylint: disable=invalid-name # pylint: disable=invalid-name
Schema = _Schema Schema = _Schema
@ -265,6 +271,14 @@ def alphanumeric(value):
def valid_name(value): def valid_name(value):
value = string_strict(value) value = string_strict(value)
if CORE.vscode:
# If the value is a substitution, it can't be validated until the substitution
# is actually made.
sub_match = VARIABLE_PROG.search(value)
if sub_match:
return value
for c in value: for c in value:
if c not in ALLOWED_NAME_CHARS: if c not in ALLOWED_NAME_CHARS:
raise Invalid( raise Invalid(
@ -447,6 +461,14 @@ def validate_id_name(value):
raise Invalid( raise Invalid(
"Dashes are not supported in IDs, please use underscores instead." "Dashes are not supported in IDs, please use underscores instead."
) )
if CORE.vscode:
# If the value is a substitution, it can't be validated until the substitution
# is actually made
sub_match = VARIABLE_PROG.match(value)
if sub_match:
return value
valid_chars = f"{ascii_letters + digits}_" valid_chars = f"{ascii_letters + digits}_"
for char in value: for char in value:
if char not in valid_chars: if char not in valid_chars:

View file

@ -3,6 +3,9 @@
__version__ = "2023.6.0-dev" __version__ = "2023.6.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
)
PLATFORM_ESP32 = "esp32" PLATFORM_ESP32 = "esp32"
PLATFORM_ESP8266 = "esp8266" PLATFORM_ESP8266 = "esp8266"

View file

@ -6,7 +6,7 @@ from hypothesis.strategies import one_of, text, integers, builds
from esphome import config_validation from esphome import config_validation
from esphome.config_validation import Invalid from esphome.config_validation import Invalid
from esphome.core import Lambda, HexInt from esphome.core import CORE, Lambda, HexInt
def test_check_not_templatable__invalid(): def test_check_not_templatable__invalid():
@ -40,6 +40,47 @@ def test_valid_name__invalid(value):
config_validation.valid_name(value) config_validation.valid_name(value)
@pytest.mark.parametrize("value", ("${name}", "${NAME}", "$NAME", "${name}_name"))
def test_valid_name__substitution_valid(value):
CORE.vscode = True
actual = config_validation.valid_name(value)
assert actual == value
CORE.vscode = False
with pytest.raises(Invalid):
actual = config_validation.valid_name(value)
@pytest.mark.parametrize("value", ("{NAME}", "${A NAME}"))
def test_valid_name__substitution_like_invalid(value):
with pytest.raises(Invalid):
config_validation.valid_name(value)
@pytest.mark.parametrize("value", ("myid", "anID", "SOME_ID_test", "MYID_99"))
def test_validate_id_name__valid(value):
actual = config_validation.validate_id_name(value)
assert actual == value
@pytest.mark.parametrize("value", ("id of mine", "id-4", "{name_id}", "id::name"))
def test_validate_id_name__invalid(value):
with pytest.raises(Invalid):
config_validation.validate_id_name(value)
@pytest.mark.parametrize("value", ("${id}", "${ID}", "${ID}_test_1", "$MYID"))
def test_validate_id_name__substitution_valid(value):
CORE.vscode = True
actual = config_validation.validate_id_name(value)
assert actual == value
CORE.vscode = False
with pytest.raises(Invalid):
config_validation.validate_id_name(value)
@given(one_of(integers(), text())) @given(one_of(integers(), text()))
def test_string__valid(value): def test_string__valid(value):
actual = config_validation.string(value) actual = config_validation.string(value)