[packages] Allow list instead of dict for packages (#8688)

This commit is contained in:
Clyde Stubbs 2025-05-05 10:26:59 +10:00 committed by GitHub
parent 3ed03edfec
commit a31d8ec309
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 63 additions and 33 deletions

View File

@ -24,22 +24,13 @@ DOMAIN = CONF_PACKAGES
def validate_git_package(config: dict): def validate_git_package(config: dict):
if CONF_URL not in config:
return config
config = BASE_SCHEMA(config)
new_config = config new_config = config
for key, conf in config.items(): if CONF_FILE in config:
if CONF_URL in conf: new_config[CONF_FILES] = [config[CONF_FILE]]
try: del new_config[CONF_FILE]
conf = BASE_SCHEMA(conf)
if CONF_FILE in conf:
new_config[key][CONF_FILES] = [conf[CONF_FILE]]
del new_config[key][CONF_FILE]
except cv.MultipleInvalid as e:
with cv.prepend_path([key]):
raise e
except cv.Invalid as e:
raise cv.Invalid(
"Extra keys not allowed in git based package",
path=[key] + e.path,
) from e
return new_config return new_config
@ -74,8 +65,8 @@ BASE_SCHEMA = cv.All(
cv.Required(CONF_URL): cv.url, cv.Required(CONF_URL): cv.url,
cv.Optional(CONF_USERNAME): cv.string, cv.Optional(CONF_USERNAME): cv.string,
cv.Optional(CONF_PASSWORD): cv.string, cv.Optional(CONF_PASSWORD): cv.string,
cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, cv.Exclusive(CONF_FILE, CONF_FILES): validate_yaml_filename,
cv.Exclusive(CONF_FILES, "files"): cv.All( cv.Exclusive(CONF_FILES, CONF_FILES): cv.All(
cv.ensure_list( cv.ensure_list(
cv.Any( cv.Any(
validate_yaml_filename, validate_yaml_filename,
@ -100,14 +91,17 @@ BASE_SCHEMA = cv.All(
cv.has_at_least_one_key(CONF_FILE, CONF_FILES), cv.has_at_least_one_key(CONF_FILE, CONF_FILES),
) )
PACKAGE_SCHEMA = cv.All(
cv.Any(validate_source_shorthand, BASE_SCHEMA, dict), validate_git_package
)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.Any(
cv.Schema( cv.Schema(
{ {
str: cv.Any(validate_source_shorthand, BASE_SCHEMA, dict), str: PACKAGE_SCHEMA,
} }
), ),
validate_git_package, cv.ensure_list(PACKAGE_SCHEMA),
) )
@ -183,25 +177,33 @@ def _process_base_package(config: dict) -> dict:
return {"packages": packages} return {"packages": packages}
def _process_package(package_config, config):
recursive_package = package_config
if CONF_URL in package_config:
package_config = _process_base_package(package_config)
if isinstance(package_config, dict):
recursive_package = do_packages_pass(package_config)
config = merge_config(recursive_package, config)
return config
def do_packages_pass(config: dict): def do_packages_pass(config: dict):
if CONF_PACKAGES not in config: if CONF_PACKAGES not in config:
return config return config
packages = config[CONF_PACKAGES] packages = config[CONF_PACKAGES]
with cv.prepend_path(CONF_PACKAGES): with cv.prepend_path(CONF_PACKAGES):
packages = CONFIG_SCHEMA(packages) packages = CONFIG_SCHEMA(packages)
if not isinstance(packages, dict): if isinstance(packages, dict):
for package_name, package_config in reversed(packages.items()):
with cv.prepend_path(package_name):
config = _process_package(package_config, config)
elif isinstance(packages, list):
for package_config in reversed(packages):
config = _process_package(package_config, config)
else:
raise cv.Invalid( raise cv.Invalid(
f"Packages must be a key to value mapping, got {type(packages)} instead" f"Packages must be a key to value mapping or list, got {type(packages)} instead"
) )
for package_name, package_config in reversed(packages.items()):
with cv.prepend_path(package_name):
recursive_package = package_config
if CONF_URL in package_config:
package_config = _process_base_package(package_config)
if isinstance(package_config, dict):
recursive_package = do_packages_pass(package_config)
config = merge_config(recursive_package, config)
del config[CONF_PACKAGES] del config[CONF_PACKAGES]
return config return config

View File

@ -76,10 +76,11 @@ def test_package_unused(basic_esphome, basic_wifi):
def test_package_invalid_dict(basic_esphome, basic_wifi): def test_package_invalid_dict(basic_esphome, basic_wifi):
""" """
Ensures an error is raised if packages is not valid. If a url: key is present, it's expected to be well-formed remote package spec. Ensure an error is raised if not.
Any other simple dict passed as a package will be merged as usual but may fail later validation.
""" """
config = {CONF_ESPHOME: basic_esphome, CONF_PACKAGES: basic_wifi} config = {CONF_ESPHOME: basic_esphome, CONF_PACKAGES: basic_wifi | {CONF_URL: ""}}
with pytest.raises(cv.Invalid): with pytest.raises(cv.Invalid):
do_packages_pass(config) do_packages_pass(config)

View File

@ -0,0 +1,3 @@
sensor:
- platform: template
id: package_sensor

View File

@ -0,0 +1,11 @@
packages:
- sensor:
- platform: template
id: inline_sensor
- !include package.yaml
- github://esphome/esphome/tests/components/template/common.yaml@dev
- url: https://github.com/esphome/esphome
file: tests/components/binary_sensor_map/common.yaml
ref: dev
refresh: 1d

View File

@ -0,0 +1,13 @@
packages:
sensor:
sensor:
- platform: template
id: inline_sensor
local: !include package.yaml
shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev
github:
url: https://github.com/esphome/esphome
file: tests/components/binary_sensor_map/common.yaml
ref: dev
refresh: 1d