Skip to content

Commit

Permalink
New vscode schema gen (esphome#3336)
Browse files Browse the repository at this point in the history
  • Loading branch information
glmnet authored Apr 3, 2022
1 parent 9de61fc commit 05dc970
Show file tree
Hide file tree
Showing 8 changed files with 887 additions and 58 deletions.
21 changes: 8 additions & 13 deletions esphome/automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,21 +262,16 @@ async def repeat_action_to_code(config, action_id, template_arg, args):
return var


def validate_wait_until(value):
schema = cv.Schema(
{
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
cv.Optional(CONF_TIMEOUT): cv.templatable(
cv.positive_time_period_milliseconds
),
}
)
if isinstance(value, dict) and CONF_CONDITION in value:
return schema(value)
return validate_wait_until({CONF_CONDITION: value})
_validate_wait_until = cv.maybe_simple_value(
{
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
cv.Optional(CONF_TIMEOUT): cv.templatable(cv.positive_time_period_milliseconds),
},
key=CONF_CONDITION,
)


@register_action("wait_until", WaitUntilAction, validate_wait_until)
@register_action("wait_until", WaitUntilAction, _validate_wait_until)
async def wait_until_action_to_code(config, action_id, template_arg, args):
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions)
Expand Down
18 changes: 5 additions & 13 deletions esphome/components/logger/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,6 @@ async def to_code(config):
)


def maybe_simple_message(schema):
def validator(value):
if isinstance(value, dict):
return cv.Schema(schema)(value)
return cv.Schema(schema)({CONF_FORMAT: value})

return validator


def validate_printf(value):
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
cfmt = r"""
Expand All @@ -234,17 +225,18 @@ def validate_printf(value):

CONF_LOGGER_LOG = "logger.log"
LOGGER_LOG_ACTION_SCHEMA = cv.All(
maybe_simple_message(
cv.maybe_simple_value(
{
cv.Required(CONF_FORMAT): cv.string,
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
cv.Optional(CONF_LEVEL, default="DEBUG"): cv.one_of(
*LOG_LEVEL_TO_ESP_LOG, upper=True
),
cv.Optional(CONF_TAG, default="main"): cv.string,
}
),
validate_printf,
},
validate_printf,
key=CONF_FORMAT,
)
)


Expand Down
8 changes: 4 additions & 4 deletions esphome/components/modbus_controller/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@
"S_DWORD": SensorValueType.S_DWORD,
"S_DWORD_R": SensorValueType.S_DWORD_R,
"U_QWORD": SensorValueType.U_QWORD,
"U_QWORDU_R": SensorValueType.U_QWORD_R,
"U_QWORD_R": SensorValueType.U_QWORD_R,
"S_QWORD": SensorValueType.S_QWORD,
"U_QWORD_R": SensorValueType.S_QWORD_R,
"S_QWORD_R": SensorValueType.S_QWORD_R,
"FP32": SensorValueType.FP32,
"FP32_R": SensorValueType.FP32_R,
}
Expand All @@ -87,9 +87,9 @@
"S_DWORD": 2,
"S_DWORD_R": 2,
"U_QWORD": 4,
"U_QWORDU_R": 4,
"S_QWORD": 4,
"U_QWORD_R": 4,
"S_QWORD": 4,
"S_QWORD_R": 4,
"FP32": 2,
"FP32_R": 2,
}
Expand Down
2 changes: 0 additions & 2 deletions esphome/components/modbus_controller/select/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import esphome.config_validation as cv
from esphome.components import select
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC
from esphome.jsonschema import jschema_composite

from .. import (
SENSOR_VALUE_TYPE,
Expand Down Expand Up @@ -30,7 +29,6 @@
)


@jschema_composite
def ensure_option_map():
def validator(value):
cv.check_not_templatable(value)
Expand Down
10 changes: 7 additions & 3 deletions esphome/config_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
)
from esphome.helpers import list_starts_with, add_class_to_obj
from esphome.jsonschema import (
jschema_composite,
jschema_list,
jschema_extractor,
jschema_registry,
jschema_typed,
Expand Down Expand Up @@ -327,7 +327,7 @@ def boolean(value):
)


@jschema_composite
@jschema_list
def ensure_list(*validators):
"""Validate this configuration option to be a list.
Expand Down Expand Up @@ -494,7 +494,11 @@ def templatable(other_validators):
"""
schema = Schema(other_validators)

@jschema_extractor("templatable")
def validator(value):
# pylint: disable=comparison-with-callable
if value == jschema_extractor:
return other_validators
if isinstance(value, Lambda):
return returning_lambda(value)
if isinstance(other_validators, dict):
Expand Down Expand Up @@ -1546,7 +1550,7 @@ def validate_registry(name, registry):
return ensure_list(validate_registry_entry(name, registry))


@jschema_composite
@jschema_list
def maybe_simple_value(*validators, **kwargs):
key = kwargs.pop("key", CONF_VALUE)
validator = All(*validators)
Expand Down
14 changes: 7 additions & 7 deletions esphome/jsonschema.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Helpers to retrieve schema from voluptuous validators.
These are a helper decorators to help get schema from some
components which uses volutuous in a way where validation
components which uses voluptuous in a way where validation
is hidden in local functions
These decorators should not modify at all what the functions
originally do.
Expand All @@ -24,7 +24,7 @@ def jschema_extractor(validator_name):
if EnableJsonSchemaCollect:

def decorator(func):
hidden_schemas[str(func)] = validator_name
hidden_schemas[repr(func)] = validator_name
return func

return decorator
Expand All @@ -41,21 +41,21 @@ def jschema_extended(func):
def decorate(*args, **kwargs):
ret = func(*args, **kwargs)
assert len(args) == 2
extended_schemas[str(ret)] = args
extended_schemas[repr(ret)] = args
return ret

return decorate

return func


def jschema_composite(func):
def jschema_list(func):
if EnableJsonSchemaCollect:

def decorate(*args, **kwargs):
ret = func(*args, **kwargs)
# args length might be 2, but 2nd is always validator
list_schemas[str(ret)] = args
list_schemas[repr(ret)] = args
return ret

return decorate
Expand All @@ -67,7 +67,7 @@ def jschema_registry(registry):
if EnableJsonSchemaCollect:

def decorator(func):
registry_schemas[str(func)] = registry
registry_schemas[repr(func)] = registry
return func

return decorator
Expand All @@ -83,7 +83,7 @@ def jschema_typed(func):

def decorate(*args, **kwargs):
ret = func(*args, **kwargs)
typed_schemas[str(ret)] = (args, kwargs)
typed_schemas[repr(ret)] = (args, kwargs)
return ret

return decorate
Expand Down
59 changes: 43 additions & 16 deletions script/build_jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def add_definition_array_or_single_object(ref):
def add_core():
from esphome.core.config import CONFIG_SCHEMA

base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA.schema)
base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA)


def add_buses():
Expand Down Expand Up @@ -216,7 +216,7 @@ def add_components():
add_module_registries(domain, c.module)
add_module_schemas(domain, c.module)

# need first to iterate all platforms then iteate components
# need first to iterate all platforms then iterate components
# a platform component can have other components as properties,
# e.g. climate components usually have a temperature sensor

Expand Down Expand Up @@ -325,7 +325,9 @@ def get_entry(parent_key, vschema):
if DUMP_COMMENTS:
entry[JSC_COMMENT] = "entry: " + parent_key + "/" + str(vschema)

if isinstance(vschema, list):
if isinstance(vschema, dict):
entry = {"what": "is_this"}
elif isinstance(vschema, list):
ref = get_jschema(parent_key + "[]", vschema[0])
entry = {"type": "array", "items": ref}
elif isinstance(vschema, schema_type) and hasattr(vschema, "schema"):
Expand Down Expand Up @@ -387,8 +389,10 @@ def get_entry(parent_key, vschema):

v = vschema(None)
if isinstance(v, ID):
if v.type.base != "script::Script" and (
v.type.inherits_from(Trigger) or v.type == Automation
if (
v.type.base != "script::Script"
and v.type.base != "switch_::Switch"
and (v.type.inherits_from(Trigger) or v.type == Automation)
):
return None
entry = {"type": "string", "id_type": v.type.base}
Expand All @@ -410,6 +414,8 @@ def default_schema():


def is_default_schema(jschema):
if jschema is None:
return False
if is_ref(jschema):
jschema = unref(jschema)
if not jschema:
Expand All @@ -425,6 +431,9 @@ def get_jschema(path, vschema, create_return_ref=True):

jschema = convert_schema(path, vschema)

if jschema is None:
return None

if is_ref(jschema):
# this can happen when returned extended
# schemas where all properties found in previous extended schema
Expand All @@ -450,6 +459,9 @@ def get_schema_str(vschema):


def create_ref(name, vschema, jschema):
if jschema is None:
raise ValueError("Cannot create a ref with null jschema for " + name)

if name in schema_names:
raise ValueError("Not supported")

Expand Down Expand Up @@ -523,6 +535,15 @@ def convert_schema(path, vschema, un_extend=True):
extended = ejs.extended_schemas.get(str(vschema))
if extended:
lhs = get_jschema(path, extended[0], False)

# The midea actions are extending an empty schema (resulted in the templatize not templatizing anything)
# this causes a recursion in that this extended looks the same in extended schema as the extended[1]
if ejs.extended_schemas.get(str(vschema)) == ejs.extended_schemas.get(
str(extended[1])
):
assert path.startswith("midea_ac")
return convert_schema(path, extended[1], False)

rhs = get_jschema(path, extended[1], False)

# check if we are not merging properties which are already in base component
Expand Down Expand Up @@ -567,6 +588,8 @@ def convert_schema(path, vschema, un_extend=True):
# we should take the valid schema,
# commonly all is used to validate a schema, and then a function which
# is not a schema es also given, get_schema will then return a default_schema()
if v == dict:
continue # this is a dict in the SCHEMA of packages
val_schema = get_jschema(path, v, False)
if is_default_schema(val_schema):
if not output:
Expand Down Expand Up @@ -673,6 +696,11 @@ def add_pin_registry():

for mode in ("INPUT", "OUTPUT"):
schema_name = f"PIN.GPIO_FULL_{mode}_PIN_SCHEMA"

# TODO: get pin definitions properly
if schema_name not in definitions:
definitions[schema_name] = {"type": ["object", "null"], JSC_PROPERTIES: {}}

internal = definitions[schema_name]
definitions[schema_name]["additionalItems"] = False
definitions[f"PIN.{mode}_INTERNAL"] = internal
Expand All @@ -683,12 +711,11 @@ def add_pin_registry():
definitions[schema_name] = {"oneOf": schemas, "type": ["string", "object"]}

for k, v in pin_registry.items():
pin_jschema = get_jschema(
f"PIN.{mode}_" + k, v[1][0 if mode == "OUTPUT" else 1]
)
if unref(pin_jschema):
pin_jschema["required"] = [k]
schemas.append(pin_jschema)
if isinstance(v[1], vol.validators.All):
pin_jschema = get_jschema(f"PIN.{mode}_" + k, v[1])
if unref(pin_jschema):
pin_jschema["required"] = [k]
schemas.append(pin_jschema)


def dump_schema():
Expand Down Expand Up @@ -730,9 +757,9 @@ def dump_schema():
cv.valid_name,
cv.hex_int,
cv.hex_int_range,
pins.output_pin,
pins.input_pin,
pins.input_pullup_pin,
pins.gpio_output_pin_schema,
pins.gpio_input_pin_schema,
pins.gpio_input_pullup_pin_schema,
cv.float_with_unit,
cv.subscribe_topic,
cv.publish_topic,
Expand All @@ -753,12 +780,12 @@ def dump_schema():

for v in [pins.gpio_input_pin_schema, pins.gpio_input_pullup_pin_schema]:
schema_registry[v] = get_ref("PIN.GPIO_FULL_INPUT_PIN_SCHEMA")
for v in [pins.internal_gpio_input_pin_schema, pins.input_pin]:
for v in [pins.internal_gpio_input_pin_schema, pins.gpio_input_pin_schema]:
schema_registry[v] = get_ref("PIN.INPUT_INTERNAL")

for v in [pins.gpio_output_pin_schema, pins.internal_gpio_output_pin_schema]:
schema_registry[v] = get_ref("PIN.GPIO_FULL_OUTPUT_PIN_SCHEMA")
for v in [pins.internal_gpio_output_pin_schema, pins.output_pin]:
for v in [pins.internal_gpio_output_pin_schema, pins.gpio_output_pin_schema]:
schema_registry[v] = get_ref("PIN.OUTPUT_INTERNAL")

add_module_schemas("CONFIG", cv)
Expand Down
Loading

0 comments on commit 05dc970

Please sign in to comment.