Packages feature (#1052)

* Started to work on packages feature

* Added some more validation to packages config

* Fixed some linter warnings

* Updated tests

* Reordered consts to avoid linter error

* Reordered consts to avoid linter error

* Refactored test yaml files to integrate into existing test pipeline

Co-authored-by: Dmitry Berezovsky <dmitry.berezovsky@logicify.com>
This commit is contained in:
Dmitry Berezovsky 2020-07-13 17:45:06 +03:00 committed by GitHub
parent 582ac4ac81
commit e6f42fa6f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 85 additions and 1 deletions

View File

@ -0,0 +1,51 @@
from deepmerge import conservative_merger as package_merger
import esphome.config_validation as cv
from esphome.const import CONF_PACKAGES
VALID_PACKAGE_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
def _merge_package(config, package_name, package_config):
config = config.copy()
package_merger.merge(config, package_config)
return config
def _is_valid_package_name(value: str) -> bool:
if not value:
return False
if value[0].isdigit():
return False
try:
cv.valid_name(value)
except cv.Invalid:
return False
return True
def do_packages_pass(config: dict):
if CONF_PACKAGES not in config:
return
packages = config[CONF_PACKAGES]
temp_config = config.copy()
with cv.prepend_path(CONF_PACKAGES):
if packages is not None and not isinstance(packages, dict):
raise cv.Invalid("Packages must be a key to value mapping, got {} instead"
"".format(type(packages)))
for package_name, package_config in packages.items():
with cv.prepend_path(package_name):
if not isinstance(package_config, dict):
raise cv.Invalid("Package definition must be a dictionary containing valid "
"esphome configuration to be merged with the main "
"config, got {} instead"
.format(type(package_config)))
if not _is_valid_package_name(package_name):
raise cv.Invalid("Package name is invalid. Valid name should consist of "
"letters, numbers and underscores. It shouldn't also "
"start with number")
temp_config = _merge_package(temp_config, package_name, package_config)
del temp_config[CONF_PACKAGES]
config.clear()
config.update(temp_config)

View File

@ -12,8 +12,9 @@ import voluptuous as vol
from esphome import core, core_config, yaml_util from esphome import core, core_config, yaml_util
from esphome.components import substitutions from esphome.components import substitutions
from esphome.components.packages import do_packages_pass
from esphome.components.substitutions import CONF_SUBSTITUTIONS from esphome.components.substitutions import CONF_SUBSTITUTIONS
from esphome.const import CONF_ESPHOME, CONF_PLATFORM, ESP_PLATFORMS from esphome.const import CONF_ESPHOME, CONF_PLATFORM, ESP_PLATFORMS, CONF_PACKAGES
from esphome.core import CORE, EsphomeError # noqa from esphome.core import CORE, EsphomeError # noqa
from esphome.helpers import color, indent from esphome.helpers import color, indent
from esphome.util import safe_print, OrderedDict from esphome.util import safe_print, OrderedDict
@ -390,6 +391,16 @@ def recursive_check_replaceme(value):
def validate_config(config, command_line_substitutions): def validate_config(config, command_line_substitutions):
result = Config() result = Config()
# 0. Load packages
if CONF_PACKAGES in config:
result.add_output_path([CONF_PACKAGES], CONF_PACKAGES)
try:
do_packages_pass(config)
except vol.Invalid as err:
result.update(config)
result.add_error(err)
return result
# 1. Load substitutions # 1. Load substitutions
if CONF_SUBSTITUTIONS in config: if CONF_SUBSTITUTIONS in config:
result[CONF_SUBSTITUTIONS] = {**config[CONF_SUBSTITUTIONS], **command_line_substitutions} result[CONF_SUBSTITUTIONS] = {**config[CONF_SUBSTITUTIONS], **command_line_substitutions}

View File

@ -342,6 +342,7 @@ CONF_OUTPUT = 'output'
CONF_OUTPUT_ID = 'output_id' CONF_OUTPUT_ID = 'output_id'
CONF_OUTPUTS = 'outputs' CONF_OUTPUTS = 'outputs'
CONF_OVERSAMPLING = 'oversampling' CONF_OVERSAMPLING = 'oversampling'
CONF_PACKAGES = 'packages'
CONF_PAGE_ID = 'page_id' CONF_PAGE_ID = 'page_id'
CONF_PAGES = 'pages' CONF_PAGES = 'pages'
CONF_PANASONIC = 'panasonic' CONF_PANASONIC = 'panasonic'

View File

@ -11,3 +11,4 @@ ifaddr==0.1.6
platformio==4.3.3 platformio==4.3.3
esptool==2.8 esptool==2.8
click==7.1.2 click==7.1.2
deepmerge==0.1.0

View File

@ -10,6 +10,7 @@ pyserial==3.4
ifaddr==0.1.6 ifaddr==0.1.6
platformio==4.3.3 platformio==4.3.3
esptool==2.8 esptool==2.8
deepmerge==0.1.0
pylint==2.5.0 pylint==2.5.0
flake8==3.7.9 flake8==3.7.9

View File

@ -33,6 +33,7 @@ REQUIRES = [
'pytz==2020.1', 'pytz==2020.1',
'pyserial==3.4', 'pyserial==3.4',
'ifaddr==0.1.6', 'ifaddr==0.1.6',
'deepmerge==0.1.0'
] ]
# If you have problems importing platformio and esptool as modules you can set # If you have problems importing platformio and esptool as modules you can set

View File

@ -1,3 +1,6 @@
substitutions:
devicename: test1
esphome: esphome:
name: test1 name: test1
platform: ESP32 platform: ESP32
@ -25,6 +28,10 @@ esphome:
white: 100% white: 100%
build_path: build/test1 build_path: build/test1
packages:
wifi: !include test_packages/test_packages_package_wifi.yaml
pkg_test: !include test_packages/test_packages_package1.yaml
wifi: wifi:
networks: networks:
- ssid: 'MySSID' - ssid: 'MySSID'

View File

@ -0,0 +1,2 @@
sensor:
- <<: !include ./test_uptime_sensor.yaml

View File

@ -0,0 +1,4 @@
wifi:
networks:
- ssid: 'WiFiFromPackage'
password: 'password1'

View File

@ -0,0 +1,5 @@
# Uptime sensor.
platform: uptime
id: ${devicename}_uptime_pcg
name: Uptime From Package
update_interval: 5min