From e6f42fa6f0934a4fae64b110d1c91e0c950d09a2 Mon Sep 17 00:00:00 2001 From: Dmitry Berezovsky Date: Mon, 13 Jul 2020 17:45:06 +0300 Subject: [PATCH] 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 --- esphome/components/packages/__init__.py | 51 +++++++++++++++++++ esphome/config.py | 13 ++++- esphome/const.py | 1 + requirements.txt | 1 + requirements_test.txt | 1 + setup.py | 1 + tests/test1.yaml | 7 +++ .../test_packages/test_packages_package1.yaml | 2 + .../test_packages_package_wifi.yaml | 4 ++ tests/test_packages/test_uptime_sensor.yaml | 5 ++ 10 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 esphome/components/packages/__init__.py create mode 100644 tests/test_packages/test_packages_package1.yaml create mode 100644 tests/test_packages/test_packages_package_wifi.yaml create mode 100644 tests/test_packages/test_uptime_sensor.yaml diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py new file mode 100644 index 000000000..729af4a9a --- /dev/null +++ b/esphome/components/packages/__init__.py @@ -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) diff --git a/esphome/config.py b/esphome/config.py index 741b4fb04..6f3a49844 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -12,8 +12,9 @@ import voluptuous as vol from esphome import core, core_config, yaml_util from esphome.components import substitutions +from esphome.components.packages import do_packages_pass 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.helpers import color, indent from esphome.util import safe_print, OrderedDict @@ -390,6 +391,16 @@ def recursive_check_replaceme(value): def validate_config(config, command_line_substitutions): 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 if CONF_SUBSTITUTIONS in config: result[CONF_SUBSTITUTIONS] = {**config[CONF_SUBSTITUTIONS], **command_line_substitutions} diff --git a/esphome/const.py b/esphome/const.py index 1760db0da..45b5e5e20 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -342,6 +342,7 @@ CONF_OUTPUT = 'output' CONF_OUTPUT_ID = 'output_id' CONF_OUTPUTS = 'outputs' CONF_OVERSAMPLING = 'oversampling' +CONF_PACKAGES = 'packages' CONF_PAGE_ID = 'page_id' CONF_PAGES = 'pages' CONF_PANASONIC = 'panasonic' diff --git a/requirements.txt b/requirements.txt index 0c99eaccb..0a3573f7b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ ifaddr==0.1.6 platformio==4.3.3 esptool==2.8 click==7.1.2 +deepmerge==0.1.0 diff --git a/requirements_test.txt b/requirements_test.txt index b90c4ea96..da4656b08 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,6 +10,7 @@ pyserial==3.4 ifaddr==0.1.6 platformio==4.3.3 esptool==2.8 +deepmerge==0.1.0 pylint==2.5.0 flake8==3.7.9 diff --git a/setup.py b/setup.py index 0a19dccf9..ede80b34e 100755 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ REQUIRES = [ 'pytz==2020.1', 'pyserial==3.4', 'ifaddr==0.1.6', + 'deepmerge==0.1.0' ] # If you have problems importing platformio and esptool as modules you can set diff --git a/tests/test1.yaml b/tests/test1.yaml index 4a47ffd7b..b131b1a6a 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1,3 +1,6 @@ +substitutions: + devicename: test1 + esphome: name: test1 platform: ESP32 @@ -25,6 +28,10 @@ esphome: white: 100% build_path: build/test1 +packages: + wifi: !include test_packages/test_packages_package_wifi.yaml + pkg_test: !include test_packages/test_packages_package1.yaml + wifi: networks: - ssid: 'MySSID' diff --git a/tests/test_packages/test_packages_package1.yaml b/tests/test_packages/test_packages_package1.yaml new file mode 100644 index 000000000..0495984d4 --- /dev/null +++ b/tests/test_packages/test_packages_package1.yaml @@ -0,0 +1,2 @@ +sensor: + - <<: !include ./test_uptime_sensor.yaml diff --git a/tests/test_packages/test_packages_package_wifi.yaml b/tests/test_packages/test_packages_package_wifi.yaml new file mode 100644 index 000000000..7d5d41dda --- /dev/null +++ b/tests/test_packages/test_packages_package_wifi.yaml @@ -0,0 +1,4 @@ +wifi: + networks: + - ssid: 'WiFiFromPackage' + password: 'password1' diff --git a/tests/test_packages/test_uptime_sensor.yaml b/tests/test_packages/test_uptime_sensor.yaml new file mode 100644 index 000000000..1bf52a6d0 --- /dev/null +++ b/tests/test_packages/test_uptime_sensor.yaml @@ -0,0 +1,5 @@ +# Uptime sensor. +platform: uptime +id: ${devicename}_uptime_pcg +name: Uptime From Package +update_interval: 5min