Add more json schema generation features (#1690)

* some enums

* extract enums, light effects remote_receiver etc

* more pins schema

* update to core changes
This commit is contained in:
Guillermo Ruffino 2021-05-14 20:35:39 -03:00 committed by GitHub
parent 4f6982fbc5
commit 3d6dcc9eee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 9 deletions

View file

@ -1,3 +1,4 @@
from esphome.jsonschema import jschema_extractor
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation from esphome import automation
@ -452,7 +453,11 @@ def addressable_flicker_effect_to_code(config, effect_id):
def validate_effects(allowed_effects): def validate_effects(allowed_effects):
@jschema_extractor("effects")
def validator(value): 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) value = cv.validate_registry("effect", EFFECTS_REGISTRY)(value)
errors = [] errors = []
names = set() names = set()

View file

@ -46,7 +46,13 @@ from esphome.core import (
TimePeriodMinutes, TimePeriodMinutes,
) )
from esphome.helpers import list_starts_with, add_class_to_obj 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.voluptuous_schema import _Schema
from esphome.yaml_util import make_data_base from esphome.yaml_util import make_data_base
@ -1121,7 +1127,12 @@ def one_of(*values, **kwargs):
if kwargs: if kwargs:
raise ValueError raise ValueError
@jschema_extractor("one_of")
def validator(value): def validator(value):
# pylint: disable=comparison-with-callable
if value == jschema_extractor:
return values
if string_: if string_:
value = string(value) value = string(value)
value = value.replace(" ", space) value = value.replace(" ", space)
@ -1161,7 +1172,12 @@ def enum(mapping, **kwargs):
assert isinstance(mapping, dict) assert isinstance(mapping, dict)
one_of_validator = one_of(*mapping, **kwargs) one_of_validator = one_of(*mapping, **kwargs)
@jschema_extractor("enum")
def validator(value): def validator(value):
# pylint: disable=comparison-with-callable
if value == jschema_extractor:
return mapping
value = one_of_validator(value) value = one_of_validator(value)
value = add_class_to_obj(value, core.EnumValue) value = add_class_to_obj(value, core.EnumValue)
value.enum_value = mapping[value] value.enum_value = mapping[value]

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from esphome.cpp_generator import MockObj
import json import json
import argparse import argparse
import os import os
@ -54,7 +55,7 @@ def is_ref(jschema):
def unref(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): def add_definition_array_or_single_object(ref):
@ -104,8 +105,11 @@ def add_registry(registry_name, registry):
for name in registry.keys(): for name in registry.keys():
schema = get_jschema(str(name), registry[name].schema, create_return_ref=False) schema = get_jschema(str(name), registry[name].schema, create_return_ref=False)
if not schema: if not schema:
schema = {"type": "string"} schema = {"type": "null"}
o_schema = {"type": "object", JSC_PROPERTIES: {name: schema}} 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) validators.append(o_schema)
definitions[registry_name] = {JSC_ANYOF: validators} definitions[registry_name] = {JSC_ANYOF: validators}
@ -134,7 +138,7 @@ def add_module_schemas(name, module):
def get_dirs(): def get_dirs():
from esphome.config import CORE_COMPONENTS_PATH from esphome.loader import CORE_COMPONENTS_PATH
dir_names = [ dir_names = [
d d
@ -146,7 +150,7 @@ def get_dirs():
def get_logger_tags(): def get_logger_tags():
from esphome.config import CORE_COMPONENTS_PATH from esphome.loader import CORE_COMPONENTS_PATH
import glob import glob
pattern = re.compile(r'^static const char(\*\s|\s\*)TAG = "(\w.*)";', re.MULTILINE) 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: elif c.config_schema is not None:
# adds root components which are not platforms, e.g. api: logger: # 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 = get_jschema(domain, c.config_schema)
schema = add_definition_array_or_single_object(schema) schema = add_definition_array_or_single_object(schema)
else: else:
@ -322,7 +326,6 @@ def get_entry(parent_key, vschema):
elif str(vschema) in ejs.list_schemas: elif str(vschema) in ejs.list_schemas:
ref = get_jschema(parent_key, ejs.list_schemas[str(vschema)][0]) ref = get_jschema(parent_key, ejs.list_schemas[str(vschema)][0])
entry = {JSC_ANYOF: [ref, {"type": "array", "items": ref}]} entry = {JSC_ANYOF: [ref, {"type": "array", "items": ref}]}
elif str(vschema) in ejs.typed_schemas: elif str(vschema) in ejs.typed_schemas:
schema_types = [{"type": "object", "properties": {"type": {"type": "string"}}}] schema_types = [{"type": "object", "properties": {"type": {"type": "string"}}}]
entry = {"allOf": schema_types} entry = {"allOf": schema_types}
@ -342,6 +345,22 @@ def get_entry(parent_key, vschema):
entry = get_automation_schema(parent_key, inner_vschema) entry = get_automation_schema(parent_key, inner_vschema)
elif type == "maybe": elif type == "maybe":
entry = get_jschema(parent_key, inner_vschema) 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: else:
raise ValueError("Unknown extracted schema type") raise ValueError("Unknown extracted schema type")
elif str(vschema).startswith("<function invalid."): elif str(vschema).startswith("<function invalid."):
@ -374,7 +393,10 @@ def default_schema():
def is_default_schema(jschema): def is_default_schema(jschema):
if is_ref(jschema): if is_ref(jschema):
return is_default_schema(unref(jschema)) jschema = unref(jschema)
if not jschema:
return False
return is_default_schema(jschema)
return "type" in jschema and jschema["type"] == default_schema()["type"] return "type" in jschema and jschema["type"] == default_schema()["type"]
@ -512,6 +534,10 @@ def convert_schema(path, vschema, un_extend=True):
# When schema contains all, all also has a schema which points # When schema contains all, all also has a schema which points
# back to the containing schema # back to the containing schema
if isinstance(vschema, MockObj):
return output
while hasattr(vschema, "schema") and not hasattr(vschema, "validators"): while hasattr(vschema, "schema") and not hasattr(vschema, "validators"):
vschema = vschema.schema vschema = vschema.schema
@ -531,7 +557,6 @@ def convert_schema(path, vschema, un_extend=True):
output = val_schema output = val_schema
else: else:
output = {**output, **val_schema} output = {**output, **val_schema}
return output return output
if not vschema: if not vschema:
@ -679,6 +704,7 @@ def dump_schema():
pins.output_pin, pins.output_pin,
pins.input_pin, pins.input_pin,
pins.input_pullup_pin, pins.input_pullup_pin,
cv.float_with_unit,
cv.subscribe_topic, cv.subscribe_topic,
cv.publish_topic, cv.publish_topic,
cv.mqtt_payload, cv.mqtt_payload,
@ -698,9 +724,13 @@ def dump_schema():
for v in [pins.gpio_input_pin_schema, pins.gpio_input_pullup_pin_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") schema_registry[v] = get_ref("PIN.GPIO_FULL_INPUT_PIN_SCHEMA")
for v in [pins.internal_gpio_input_pin_schema, pins.input_pin]:
schema_registry[v] = get_ref("PIN.INPUT_INTERNAL")
for v in [pins.gpio_output_pin_schema, pins.internal_gpio_output_pin_schema]: 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") schema_registry[v] = get_ref("PIN.GPIO_FULL_OUTPUT_PIN_SCHEMA")
for v in [pins.internal_gpio_output_pin_schema, pins.output_pin]:
schema_registry[v] = get_ref("PIN.OUTPUT_INTERNAL")
add_module_schemas("CONFIG", cv) add_module_schemas("CONFIG", cv)
get_jschema("POLLING_COMPONENT", cv.polling_component_schema("60s")) get_jschema("POLLING_COMPONENT", cv.polling_component_schema("60s"))