mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
parent
31d964c16a
commit
b7dff4bbab
4 changed files with 95 additions and 57 deletions
|
@ -18,12 +18,12 @@ from esphome.components.substitutions import CONF_SUBSTITUTIONS
|
|||
from esphome.const import CONF_ESPHOME, CONF_PLATFORM, ESP_PLATFORMS
|
||||
from esphome.core import CORE, EsphomeError # noqa
|
||||
from esphome.helpers import color, indent
|
||||
from esphome.py_compat import text_type, IS_PY2, decode_text
|
||||
from esphome.py_compat import text_type, IS_PY2, decode_text, string_types
|
||||
from esphome.util import safe_print, OrderedDict
|
||||
|
||||
from typing import List, Optional, Tuple, Union # noqa
|
||||
from esphome.core import ConfigType # noqa
|
||||
from esphome.yaml_util import is_secret, ESPHomeDataBase
|
||||
from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue
|
||||
from esphome.voluptuous_schema import ExtraKeysInvalid
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -380,6 +380,24 @@ def do_id_pass(result): # type: (Config) -> None
|
|||
result.add_str_error("Couldn't resolve ID for type '{}'".format(id.type), path)
|
||||
|
||||
|
||||
def recursive_check_replaceme(value):
|
||||
import esphome.config_validation as cv
|
||||
|
||||
if isinstance(value, list):
|
||||
return cv.Schema([recursive_check_replaceme])(value)
|
||||
if isinstance(value, dict):
|
||||
return cv.Schema({cv.valid: recursive_check_replaceme})(value)
|
||||
if isinstance(value, ESPForceValue):
|
||||
pass
|
||||
if isinstance(value, string_types) and value == 'REPLACEME':
|
||||
raise cv.Invalid("Found 'REPLACEME' in configuration, this is most likely an error. "
|
||||
"Please make sure you have replaced all fields from the sample "
|
||||
"configuration.\n"
|
||||
"If you want to use the literal REPLACEME string, "
|
||||
"please use \"!force REPLACEME\"")
|
||||
return value
|
||||
|
||||
|
||||
def validate_config(config):
|
||||
result = Config()
|
||||
|
||||
|
@ -393,6 +411,12 @@ def validate_config(config):
|
|||
result.add_error(err)
|
||||
return result
|
||||
|
||||
# 1.1. Check for REPLACEME special value
|
||||
try:
|
||||
recursive_check_replaceme(config)
|
||||
except vol.Invalid as err:
|
||||
result.add_error(err)
|
||||
|
||||
if 'esphomeyaml' in config:
|
||||
_LOGGER.warning("The esphomeyaml section has been renamed to esphome in 1.11.0. "
|
||||
"Please replace 'esphomeyaml:' in your configuration with 'esphome:'.")
|
||||
|
@ -588,7 +612,7 @@ def _nested_getitem(data, path):
|
|||
|
||||
def humanize_error(config, validation_error):
|
||||
validation_error = text_type(validation_error)
|
||||
m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error)
|
||||
m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error, re.DOTALL)
|
||||
if m is not None:
|
||||
validation_error = m.group(1)
|
||||
validation_error = validation_error.strip()
|
||||
|
|
|
@ -19,7 +19,7 @@ from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY,
|
|||
CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE
|
||||
from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \
|
||||
TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes
|
||||
from esphome.helpers import list_starts_with
|
||||
from esphome.helpers import list_starts_with, add_class_to_obj
|
||||
from esphome.py_compat import integer_types, string_types, text_type, IS_PY2, decode_text
|
||||
from esphome.voluptuous_schema import _Schema
|
||||
|
||||
|
@ -964,11 +964,8 @@ def enum(mapping, **kwargs):
|
|||
one_of_validator = one_of(*mapping, **kwargs)
|
||||
|
||||
def validator(value):
|
||||
from esphome.yaml_util import make_data_base
|
||||
|
||||
value = make_data_base(one_of_validator(value))
|
||||
cls = value.__class__
|
||||
value.__class__ = cls.__class__(cls.__name__ + "Enum", (cls, core.EnumValue), {})
|
||||
value = one_of_validator(value)
|
||||
value = add_class_to_obj(value, core.EnumValue)
|
||||
value.enum_value = mapping[value]
|
||||
return value
|
||||
|
||||
|
|
|
@ -261,3 +261,51 @@ def file_compare(path1, path2):
|
|||
if not blob1:
|
||||
# Reached end
|
||||
return True
|
||||
|
||||
|
||||
# A dict of types that need to be converted to heaptypes before a class can be added
|
||||
# to the object
|
||||
_TYPE_OVERLOADS = {
|
||||
int: type('int', (int,), dict()),
|
||||
float: type('float', (float,), dict()),
|
||||
str: type('str', (str,), dict()),
|
||||
dict: type('dict', (str,), dict()),
|
||||
list: type('list', (list,), dict()),
|
||||
}
|
||||
|
||||
if IS_PY2:
|
||||
_TYPE_OVERLOADS[long] = type('long', (long,), dict())
|
||||
_TYPE_OVERLOADS[unicode] = type('unicode', (unicode,), dict())
|
||||
|
||||
# cache created classes here
|
||||
_CLASS_LOOKUP = {}
|
||||
|
||||
|
||||
def add_class_to_obj(value, cls):
|
||||
"""Add a class to a python type.
|
||||
|
||||
This function modifies value so that it has cls as a basetype.
|
||||
The value itself may be modified by this action! You must use the return
|
||||
value of this function however, since some types need to be copied first (heaptypes).
|
||||
"""
|
||||
if isinstance(value, cls):
|
||||
# If already is instance, do not add
|
||||
return value
|
||||
|
||||
try:
|
||||
orig_cls = value.__class__
|
||||
key = (orig_cls, cls)
|
||||
new_cls = _CLASS_LOOKUP.get(key)
|
||||
if new_cls is None:
|
||||
new_cls = orig_cls.__class__(orig_cls.__name__, (orig_cls, cls), {})
|
||||
_CLASS_LOOKUP[key] = new_cls
|
||||
value.__class__ = new_cls
|
||||
return value
|
||||
except TypeError:
|
||||
# Non heap type, look in overloads dict
|
||||
for type_, func in _TYPE_OVERLOADS.items():
|
||||
# Use type() here, we only need to trigger if it's the exact type,
|
||||
# as otherwise we don't need to overload the class
|
||||
if type(value) is type_: # pylint: disable=unidiomatic-typecheck
|
||||
return add_class_to_obj(func(value), cls)
|
||||
raise
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import print_function
|
|||
|
||||
import fnmatch
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
|
@ -13,6 +14,7 @@ import yaml.constructor
|
|||
from esphome import core
|
||||
from esphome.config_helpers import read_config_file
|
||||
from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange
|
||||
from esphome.helpers import add_class_to_obj
|
||||
from esphome.py_compat import text_type, IS_PY2
|
||||
from esphome.util import OrderedDict, filter_yaml_files
|
||||
|
||||
|
@ -26,14 +28,6 @@ _SECRET_CACHE = {}
|
|||
_SECRET_VALUES = {}
|
||||
|
||||
|
||||
class NodeListClass(list):
|
||||
pass
|
||||
|
||||
|
||||
class NodeStrClass(text_type):
|
||||
pass
|
||||
|
||||
|
||||
class ESPHomeDataBase(object):
|
||||
@property
|
||||
def esp_range(self):
|
||||
|
@ -44,56 +38,25 @@ class ESPHomeDataBase(object):
|
|||
self._esp_range = DocumentRange.from_marks(node.start_mark, node.end_mark)
|
||||
|
||||
|
||||
class ESPInt(int, ESPHomeDataBase):
|
||||
class ESPForceValue(object):
|
||||
pass
|
||||
|
||||
|
||||
class ESPFloat(float, ESPHomeDataBase):
|
||||
pass
|
||||
|
||||
|
||||
class ESPStr(str, ESPHomeDataBase):
|
||||
pass
|
||||
|
||||
|
||||
class ESPDict(OrderedDict, ESPHomeDataBase):
|
||||
pass
|
||||
|
||||
|
||||
class ESPList(list, ESPHomeDataBase):
|
||||
pass
|
||||
|
||||
|
||||
class ESPLambda(Lambda, ESPHomeDataBase):
|
||||
pass
|
||||
|
||||
|
||||
ESP_TYPES = {
|
||||
int: ESPInt,
|
||||
float: ESPFloat,
|
||||
str: ESPStr,
|
||||
dict: ESPDict,
|
||||
list: ESPList,
|
||||
Lambda: ESPLambda,
|
||||
}
|
||||
if IS_PY2:
|
||||
class ESPUnicode(unicode, ESPHomeDataBase):
|
||||
pass
|
||||
|
||||
ESP_TYPES[unicode] = ESPUnicode
|
||||
|
||||
|
||||
def make_data_base(value):
|
||||
for typ, cons in ESP_TYPES.items():
|
||||
if isinstance(value, typ):
|
||||
return cons(value)
|
||||
return value
|
||||
return add_class_to_obj(value, ESPHomeDataBase)
|
||||
|
||||
|
||||
def _add_data_ref(fn):
|
||||
@functools.wraps(fn)
|
||||
def wrapped(loader, node):
|
||||
res = fn(loader, node)
|
||||
# newer PyYAML versions use generators, resolve them
|
||||
if inspect.isgenerator(res):
|
||||
generator = res
|
||||
res = next(generator)
|
||||
# Let generator finish
|
||||
for _ in generator:
|
||||
pass
|
||||
res = make_data_base(res)
|
||||
if isinstance(res, ESPHomeDataBase):
|
||||
res.from_node(node)
|
||||
|
@ -296,6 +259,11 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||
def construct_lambda(self, node):
|
||||
return Lambda(text_type(node.value))
|
||||
|
||||
@_add_data_ref
|
||||
def construct_force(self, node):
|
||||
obj = self.construct_scalar(node)
|
||||
return add_class_to_obj(obj, ESPForceValue)
|
||||
|
||||
|
||||
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:int', ESPHomeLoader.construct_yaml_int)
|
||||
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:float', ESPHomeLoader.construct_yaml_float)
|
||||
|
@ -314,6 +282,7 @@ ESPHomeLoader.add_constructor('!include_dir_named', ESPHomeLoader.construct_incl
|
|||
ESPHomeLoader.add_constructor('!include_dir_merge_named',
|
||||
ESPHomeLoader.construct_include_dir_merge_named)
|
||||
ESPHomeLoader.add_constructor('!lambda', ESPHomeLoader.construct_lambda)
|
||||
ESPHomeLoader.add_constructor('!force', ESPHomeLoader.construct_force)
|
||||
|
||||
|
||||
def load_yaml(fname):
|
||||
|
|
Loading…
Reference in a new issue