Add magic value REPLACEME (#881)

* Add magic value REPLACEME

* Lint
This commit is contained in:
Otto Winter 2019-12-04 15:58:40 +01:00 committed by GitHub
parent 31d964c16a
commit b7dff4bbab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 57 deletions

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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):