diff --git a/doc/generate_json_schema.py b/doc/generate_json_schema.py new file mode 100644 index 000000000..1abf88915 --- /dev/null +++ b/doc/generate_json_schema.py @@ -0,0 +1,4 @@ +from yunohost.utils.configpanel import ConfigPanelModel + + +print(ConfigPanelModel.schema_json(indent=2)) diff --git a/src/utils/configpanel.py b/src/utils/configpanel.py index 325f6579d..e3ceeff88 100644 --- a/src/utils/configpanel.py +++ b/src/utils/configpanel.py @@ -88,6 +88,14 @@ class SectionModel(ContainerModel, OptionsModel): optional: bool = True is_action_section: bool = False + class Config: + @staticmethod + def schema_extra(schema: dict[str, Any]) -> None: + del schema["properties"]["id"] + options = schema["properties"].pop("options") + del schema["required"] + schema["additionalProperties"] = options["items"] + # Don't forget to pass arguments to super init def __init__( self, @@ -137,6 +145,13 @@ class PanelModel(ContainerModel): class Config: extra = Extra.allow + @staticmethod + def schema_extra(schema: dict[str, Any]) -> None: + del schema["properties"]["id"] + del schema["properties"]["sections"] + del schema["required"] + schema["additionalProperties"] = {"$ref": "#/definitions/SectionModel"} + # Don't forget to pass arguments to super init def __init__( self, @@ -170,6 +185,26 @@ class ConfigPanelModel(BaseModel): arbitrary_types_allowed = True extra = Extra.allow + @staticmethod + def schema_extra(schema: dict[str, Any]) -> None: + """Update the schema to the expected input + In actual TOML definition, schema is like: + ```toml + [panel_1] + [panel_1.section_1] + [panel_1.section_1.option_1] + ``` + Which is equivalent to `{"panel_1": {"section_1": {"option_1": {}}}}` + so `section_id` (and `option_id`) are additional property of `panel_id`, + which is convinient to write but not ideal to iterate. + In ConfigPanelModel we gather additional properties of panels, sections + and options as lists so that structure looks like: + `{"panels`: [{"id": "panel_1", "sections": [{"id": "section_1", "options": [{"id": "option_1"}]}]}] + """ + del schema["properties"]["panels"] + del schema["required"] + schema["additionalProperties"] = {"$ref": "#/definitions/PanelModel"} + # Don't forget to pass arguments to super init def __init__( self, diff --git a/src/utils/form.py b/src/utils/form.py index 70afeb8f3..bd373badb 100644 --- a/src/utils/form.py +++ b/src/utils/form.py @@ -319,10 +319,14 @@ class BaseOption(BaseModel): extra = Extra.forbid @staticmethod - def schema_extra(schema: dict[str, Any], model: Type["BaseOption"]) -> None: - # FIXME Do proper doctstring for Options - del schema["description"] - schema["additionalProperties"] = False + def schema_extra(schema: dict[str, Any]) -> None: + del schema["properties"]["id"] + del schema["properties"]["name"] + schema["required"] = [ + required for required in schema.get("required", []) if required != "id" + ] + if not schema["required"]: + del schema["required"] @validator("id", pre=True) def check_id_is_not_forbidden(cls, value: str) -> str: