From 3d6dcc9eeec5c5a8c0cd651ff88e4c01975c2a87 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Fri, 14 May 2021 20:35:39 -0300 Subject: [PATCH] Add more json schema generation features (#1690) * some enums * extract enums, light effects remote_receiver etc * more pins schema * update to core changes --- esphome/components/light/effects.py | 5 ++++ esphome/config_validation.py | 18 ++++++++++- script/build_jsonschema.py | 46 ++++++++++++++++++++++++----- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index 08a78d90ed..48785df523 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -1,3 +1,4 @@ +from esphome.jsonschema import jschema_extractor import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -452,7 +453,11 @@ def addressable_flicker_effect_to_code(config, effect_id): def validate_effects(allowed_effects): + @jschema_extractor("effects") def validator(value): + # pylint: disable=comparison-with-callable + if value == jschema_extractor: + return (allowed_effects, EFFECTS_REGISTRY) value = cv.validate_registry("effect", EFFECTS_REGISTRY)(value) errors = [] names = set() diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 24c86e6713..dcc0da8b2b 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -46,7 +46,13 @@ from esphome.core import ( TimePeriodMinutes, ) from esphome.helpers import list_starts_with, add_class_to_obj -from esphome.jsonschema import jschema_composite, jschema_registry, jschema_typed +from esphome.jsonschema import ( + jschema_composite, + jschema_extractor, + jschema_registry, + jschema_typed, +) + from esphome.voluptuous_schema import _Schema from esphome.yaml_util import make_data_base @@ -1121,7 +1127,12 @@ def one_of(*values, **kwargs): if kwargs: raise ValueError + @jschema_extractor("one_of") def validator(value): + # pylint: disable=comparison-with-callable + if value == jschema_extractor: + return values + if string_: value = string(value) value = value.replace(" ", space) @@ -1161,7 +1172,12 @@ def enum(mapping, **kwargs): assert isinstance(mapping, dict) one_of_validator = one_of(*mapping, **kwargs) + @jschema_extractor("enum") def validator(value): + # pylint: disable=comparison-with-callable + if value == jschema_extractor: + return mapping + value = one_of_validator(value) value = add_class_to_obj(value, core.EnumValue) value.enum_value = mapping[value] diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 6d19e25e29..89d621fd5a 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from esphome.cpp_generator import MockObj import json import argparse import os @@ -54,7 +55,7 @@ def is_ref(jschema): def unref(jschema): - return definitions[jschema[JSC_REF][len("#/definitions/") :]] + return definitions.get(jschema[JSC_REF][len("#/definitions/") :]) def add_definition_array_or_single_object(ref): @@ -104,8 +105,11 @@ def add_registry(registry_name, registry): for name in registry.keys(): schema = get_jschema(str(name), registry[name].schema, create_return_ref=False) if not schema: - schema = {"type": "string"} + schema = {"type": "null"} o_schema = {"type": "object", JSC_PROPERTIES: {name: schema}} + o_schema = create_ref( + registry_name + "-" + name, str(registry[name].schema) + "x", o_schema + ) validators.append(o_schema) definitions[registry_name] = {JSC_ANYOF: validators} @@ -134,7 +138,7 @@ def add_module_schemas(name, module): def get_dirs(): - from esphome.config import CORE_COMPONENTS_PATH + from esphome.loader import CORE_COMPONENTS_PATH dir_names = [ d @@ -146,7 +150,7 @@ def get_dirs(): def get_logger_tags(): - from esphome.config import CORE_COMPONENTS_PATH + from esphome.loader import CORE_COMPONENTS_PATH import glob pattern = re.compile(r'^static const char(\*\s|\s\*)TAG = "(\w.*)";', re.MULTILINE) @@ -241,7 +245,7 @@ def add_components(): elif c.config_schema is not None: # adds root components which are not platforms, e.g. api: logger: - if c.is_multi_conf: + if c.multi_conf: schema = get_jschema(domain, c.config_schema) schema = add_definition_array_or_single_object(schema) else: @@ -322,7 +326,6 @@ def get_entry(parent_key, vschema): elif str(vschema) in ejs.list_schemas: ref = get_jschema(parent_key, ejs.list_schemas[str(vschema)][0]) entry = {JSC_ANYOF: [ref, {"type": "array", "items": ref}]} - elif str(vschema) in ejs.typed_schemas: schema_types = [{"type": "object", "properties": {"type": {"type": "string"}}}] entry = {"allOf": schema_types} @@ -342,6 +345,22 @@ def get_entry(parent_key, vschema): entry = get_automation_schema(parent_key, inner_vschema) elif type == "maybe": entry = get_jschema(parent_key, inner_vschema) + elif type == "one_of": + entry = {"enum": list(inner_vschema)} + elif type == "enum": + entry = {"enum": list(inner_vschema.keys())} + elif type == "effects": + # Like list schema but subset from list. + subset_list = inner_vschema[0] + # get_jschema('strobex', registry['strobe'].schema) + registry_schemas = [] + for name in subset_list: + registry_schemas.append(get_ref("light.EFFECTS_REGISTRY-" + name)) + + entry = { + JSC_ANYOF: [{"type": "array", "items": {JSC_ANYOF: registry_schemas}}] + } + else: raise ValueError("Unknown extracted schema type") elif str(vschema).startswith("