From 08e8f6016193002979af8ed090d02634ca1c1db5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:04:28 +0200 Subject: [PATCH 01/11] list entity can use templates or schemas --- openpype/settings/entities/list_entity.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 64bbad28a7..ce200862f6 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -141,7 +141,21 @@ class ListEntity(EndpointEntity): item_schema = self.schema_data["object_type"] if not isinstance(item_schema, dict): item_schema = {"type": item_schema} - self.item_schema = item_schema + + schema_template_used = False + _item_schemas = self.schema_hub.resolve_schema_data(item_schema) + if len(_item_schemas) == 1: + self.item_schema = _item_schemas[0] + if self.item_schema != item_schema: + schema_template_used = True + if "label" in self.item_schema: + self.item_schema.pop("label") + self.item_schema["use_label_wrap"] = False + else: + self.item_schema = _item_schemas + + # Store if was used template or schema + self._schema_template_used = schema_template_used if self.group_item is None: self.is_group = True From ff7ccfecba187237465151d1fbd6f5024973ef93 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:07:35 +0200 Subject: [PATCH 02/11] validate children on schema validations only if was not used from schema --- openpype/settings/entities/list_entity.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index ce200862f6..4a2b5968d9 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -187,7 +187,11 @@ class ListEntity(EndpointEntity): child_validated = True break - if not child_validated: + # Do not validate if was used schema or template + # - that is validated on first created children + # - it is because template or schema can use itself inside children + # TODO Do validations maybe store to `schema_hub` what is validated + if not self._schema_template_used and not child_validated: idx = 0 tmp_child = self._add_new_item(idx) tmp_child.schema_validations() From 8b789df5ec7beaf19b8d473c1012949ff294fc84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:07:58 +0200 Subject: [PATCH 03/11] validate if item_schema is list --- openpype/settings/entities/list_entity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 4a2b5968d9..b12e6d8f5c 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -164,6 +164,12 @@ class ListEntity(EndpointEntity): self.initial_value = [] def schema_validations(self): + if isinstance(self.item_schema, list): + reason = ( + "`ListWidget` has multiple items as object type." + ) + raise EntitySchemaError(self, reason) + super(ListEntity, self).schema_validations() if self.is_dynamic_item and self.use_label_wrap: From 8f6e8b19885e836a8adf3892659d282909f1cbf0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:08:09 +0200 Subject: [PATCH 04/11] handle child validations --- openpype/settings/entities/list_entity.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index b12e6d8f5c..e89c7cadec 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -94,6 +94,12 @@ class ListEntity(EndpointEntity): def _add_new_item(self, idx=None): child_obj = self.create_schema_object(self.item_schema, self, True) + + # Validate child if was not validated yet + if not self._child_validated: + child_obj.schema_validations() + self._child_validated = True + if idx is None: self.children.append(child_obj) else: @@ -156,6 +162,8 @@ class ListEntity(EndpointEntity): # Store if was used template or schema self._schema_template_used = schema_template_used + # Store if child was validated + self._child_validated = False if self.group_item is None: self.is_group = True @@ -202,6 +210,9 @@ class ListEntity(EndpointEntity): tmp_child = self._add_new_item(idx) tmp_child.schema_validations() self.children.pop(idx) + child_validated = True + + self._child_validated = child_validated def get_child_path(self, child_obj): result_idx = None From 0356e60faf83c48db881cf94ea21e6fa8a482b99 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:31:34 +0200 Subject: [PATCH 05/11] get template name from item --- openpype/settings/entities/lib.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index e58281644a..dee80c09aa 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -126,6 +126,22 @@ class SchemasHub: def gui_types(self): return self._gui_types + def get_template_name(self, item_def, default=None): + """Get template name from passed item definition. + + Args: + item_def(dict): Definition of item with "type". + default(object): Default return value. + """ + output = default + if not item_def or not isinstance(item_def, dict): + return output + + item_type = item_def.get("type") + if item_type in ("template", "schema_template"): + output = item_def["name"] + return output + def get_schema(self, schema_name): """Get schema definition data by it's name. From 41218b61ec79a5ad7dc68a42f636a44562c4cef5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:32:13 +0200 Subject: [PATCH 06/11] added validation methods and variables --- openpype/settings/entities/lib.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index dee80c09aa..1c4a51b7c9 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -111,6 +111,10 @@ class SchemasHub: self._loaded_templates = {} self._loaded_schemas = {} + # Store validating and validated dynamic template or schemas + self._validating_dynamic = set() + self._validated_dynamic = set() + # It doesn't make sence to reload types on each reset as they can't be # changed self._load_types() @@ -142,6 +146,27 @@ class SchemasHub: output = item_def["name"] return output + def is_dynamic_template_validating(self, template_name): + """Is template validating using different entity. + + Returns: + bool: Is template validating. + """ + if template_name in self._validating_dynamic: + return True + return False + + def is_dynamic_template_validated(self, template_name): + """Is template already validated. + + Returns: + bool: Is template validated. + """ + + if template_name in self._validated_dynamic: + return True + return False + def get_schema(self, schema_name): """Get schema definition data by it's name. From 027cb48a13e718dfcdb0c25327ddba4fbed677d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:36:54 +0200 Subject: [PATCH 07/11] added context manager method for using validation of dynamic template --- openpype/settings/entities/lib.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 1c4a51b7c9..01f61d8bdf 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -3,6 +3,7 @@ import re import json import copy import inspect +import contextlib from .exceptions import ( SchemaTemplateMissingKeys, @@ -167,6 +168,23 @@ class SchemasHub: return True return False + @contextlib.contextmanager + def validating_dynamic(self, template_name): + """Template name is validating and validated. + + Context manager that cares about storing template name validations of + template. + + This is to avoid infinite loop of dynamic children validation. + """ + self._validating_dynamic.add(template_name) + try: + yield + self._validated_dynamic.add(template_name) + + finally: + self._validating_dynamic.remove(template_name) + def get_schema(self, schema_name): """Get schema definition data by it's name. From 148e1a9564c421fabcd31349aac1a146db2c70e6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:37:15 +0200 Subject: [PATCH 08/11] better children validation of dynamic templates --- openpype/settings/entities/list_entity.py | 42 ++++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index e89c7cadec..b07441251a 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -94,12 +94,6 @@ class ListEntity(EndpointEntity): def _add_new_item(self, idx=None): child_obj = self.create_schema_object(self.item_schema, self, True) - - # Validate child if was not validated yet - if not self._child_validated: - child_obj.schema_validations() - self._child_validated = True - if idx is None: self.children.append(child_obj) else: @@ -148,12 +142,11 @@ class ListEntity(EndpointEntity): if not isinstance(item_schema, dict): item_schema = {"type": item_schema} - schema_template_used = False + obj_template_name = self.schema_hub.get_template_name(item_schema) _item_schemas = self.schema_hub.resolve_schema_data(item_schema) if len(_item_schemas) == 1: self.item_schema = _item_schemas[0] if self.item_schema != item_schema: - schema_template_used = True if "label" in self.item_schema: self.item_schema.pop("label") self.item_schema["use_label_wrap"] = False @@ -161,9 +154,7 @@ class ListEntity(EndpointEntity): self.item_schema = _item_schemas # Store if was used template or schema - self._schema_template_used = schema_template_used - # Store if child was validated - self._child_validated = False + self._obj_template_name = obj_template_name if self.group_item is None: self.is_group = True @@ -195,24 +186,35 @@ class ListEntity(EndpointEntity): raise EntitySchemaError(self, reason) # Validate object type schema - child_validated = False + validate_children = True for child_entity in self.children: child_entity.schema_validations() - child_validated = True + validate_children = False break - # Do not validate if was used schema or template - # - that is validated on first created children - # - it is because template or schema can use itself inside children - # TODO Do validations maybe store to `schema_hub` what is validated - if not self._schema_template_used and not child_validated: + if validate_children and self._obj_template_name: + _validated = self.schema_hub.is_dynamic_template_validated( + self._obj_template_name + ) + _validating = self.schema_hub.is_dynamic_template_validating( + self._obj_template_name + ) + validate_children = not _validated and not _validating + + if not validate_children: + return + + def _validate(): idx = 0 tmp_child = self._add_new_item(idx) tmp_child.schema_validations() self.children.pop(idx) - child_validated = True - self._child_validated = child_validated + if self._obj_template_name: + with self.schema_hub.validating_dynamic(self._obj_template_name): + _validate() + else: + _validate() def get_child_path(self, child_obj): result_idx = None From 858e46d0f63e3e2e2575935f9e4a673d6932b7af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 13:53:58 +0200 Subject: [PATCH 09/11] added description and example --- openpype/settings/entities/schemas/README.md | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 3c360b892f..e098198c2c 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -404,6 +404,8 @@ How output of the schema could look like on save: - there are 2 possible ways how to set the type: 1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below) 2.) item type name as string without modifiers (e.g. `text`) + 3.) enhancement of 1.) there is also support of `template` type but be carefull about endless loop of templates + - goal of using `template` is to easily change same item definitions in multiple lists 1.) with item modifiers ``` @@ -429,6 +431,63 @@ How output of the schema could look like on save: } ``` +3.) with template definition +``` +# Schema of list item where template is used +{ + "type": "list", + "key": "menu_items", + "label": "Menu Items", + "object_type": { + "type": "template", + "name": "template_object_example" + } +} + +# WARNING: +# In this example the template use itself inside which will work in `list` +# but may cause an issue in other entity types (e.g. `dict`). +[ + { + "type": "dict-conditional", + "use_label_wrap": true, + "collapsible": true, + "key": "menu_items", + "label": "Menu items", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": { + "type": "template", + "name": "template_object_example" + } + } + ] + } + ] + } +] +``` + ### dict-modifiable - one of dictionary inputs, this is only used as value input - items in this input can be removed and added same way as in `list` input From e97d14634535bed8aa2daaa4a3078dc6bf252427 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Jul 2021 14:50:38 +0200 Subject: [PATCH 10/11] added more specific name of template filename --- openpype/settings/entities/schemas/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 02e3e0a83c..8760187038 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -452,6 +452,8 @@ How output of the schema could look like on save: # WARNING: # In this example the template use itself inside which will work in `list` # but may cause an issue in other entity types (e.g. `dict`). + +'template_object_example.json' : [ { "type": "dict-conditional", From 12063d2b5ff38f7d3d900f8d41d8c839be80120d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 29 Jul 2021 19:16:40 +0200 Subject: [PATCH 11/11] added example of using template as object type in list --- .../example_infinite_hierarchy.json | 58 +++++++++++++++++++ .../schemas/system_schema/example_schema.json | 11 ++++ 2 files changed, 69 insertions(+) create mode 100644 openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json diff --git a/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json b/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json new file mode 100644 index 0000000000..a2660e9bf2 --- /dev/null +++ b/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json @@ -0,0 +1,58 @@ +[ + { + "type": "dict-conditional", + "use_label_wrap": true, + "collapsible": true, + "key": "menu_items", + "label": "Menu items", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "command", + "label": "Comand" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": { + "type": "template", + "name": "example_infinite_hierarchy" + } + } + ] + }, + { + "key": "separator", + "label": "Separator" + } + ] + } +] diff --git a/openpype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json index c3287d7452..71a15ca721 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -57,6 +57,17 @@ } ] }, + { + "type": "list", + "use_label_wrap": true, + "collapsible": true, + "key": "infinite_hierarchy", + "label": "Infinite list template hierarchy", + "object_type": { + "type": "template", + "name": "example_infinite_hierarchy" + } + }, { "type": "dict", "key": "schema_template_exaples",