Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Settings list can use template or schema as object type #1815

Merged
merged 12 commits into from
Aug 9, 2021
Merged
59 changes: 59 additions & 0 deletions openpype/settings/entities/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import copy
import inspect
import contextlib

from .exceptions import (
SchemaTemplateMissingKeys,
Expand Down Expand Up @@ -111,6 +112,10 @@ def __init__(self, schema_subfolder, reset=True):
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()
Expand All @@ -126,6 +131,60 @@ def reset(self):
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 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

@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.

Expand Down
45 changes: 41 additions & 4 deletions openpype/settings/entities/list_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,20 @@ def _item_initalization(self):
item_schema = self.schema_data["object_type"]
if not isinstance(item_schema, dict):
item_schema = {"type": item_schema}
self.item_schema = item_schema

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:
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._obj_template_name = obj_template_name

if self.group_item is None:
self.is_group = True
Expand All @@ -150,6 +163,12 @@ def _item_initalization(self):
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:
Expand All @@ -167,18 +186,36 @@ def schema_validations(self):
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

if 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)

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
for idx, _child_obj in enumerate(self.children):
Expand Down
61 changes: 61 additions & 0 deletions openpype/settings/entities/schemas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,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
```
Expand All @@ -434,6 +436,65 @@ 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`).

'template_object_example.json' :
[
{
"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
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down