From 3e87997b401da0ba7e57ab0707b78ada9168ff2c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 10:54:05 +0200 Subject: [PATCH 01/43] modified how default settings are loaded --- openpype/settings/lib.py | 49 ++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 4a363910b8..04d8753869 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -329,6 +329,41 @@ def reset_default_settings(): _DEFAULT_SETTINGS = None +def _get_default_settings(): + from openpype.modules import get_module_settings_defs + + defaults = load_openpype_default_settings() + + module_settings_defs = get_module_settings_defs() + for module_settings_def_cls in module_settings_defs: + module_settings_def = module_settings_def_cls() + system_defaults = module_settings_def.get_system_defaults() + for path, value in system_defaults.items(): + if not path: + continue + + subdict = defaults["system_settings"] + path_items = list(path.split("/")) + last_key = path_items.pop(-1) + for key in path_items: + subdict = subdict[key] + subdict[last_key] = value + + project_defaults = module_settings_def.get_project_defaults() + for path, value in project_defaults.items(): + if not path: + continue + + subdict = defaults["project_settings"] + path_items = list(path.split("/")) + last_key = path_items.pop(-1) + for key in path_items: + subdict = subdict[key] + subdict[last_key] = value + + return defaults + + def get_default_settings(): """Get default settings. @@ -339,11 +374,11 @@ def get_default_settings(): dict: Loaded default settings. """ # TODO add cacher - return load_openpype_default_settings() - # global _DEFAULT_SETTINGS - # if _DEFAULT_SETTINGS is None: - # _DEFAULT_SETTINGS = load_jsons_from_dir(DEFAULTS_DIR) - # return copy.deepcopy(_DEFAULT_SETTINGS) + + global _DEFAULT_SETTINGS + if _DEFAULT_SETTINGS is None: + _DEFAULT_SETTINGS = _get_default_settings() + return copy.deepcopy(_DEFAULT_SETTINGS) def load_json_file(fpath): @@ -380,8 +415,8 @@ def load_jsons_from_dir(path, *args, **kwargs): "data1": "CONTENT OF FILE" }, "folder2": { - "data1": { - "subfolder1": "CONTENT OF FILE" + "subfolder1": { + "data2": "CONTENT OF FILE" } } } From aa2f5d85701fa56ed8eb5b1a568dceced3c2ead1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 10:54:31 +0200 Subject: [PATCH 02/43] defined class which defined base settings --- openpype/modules/__init__.py | 13 ++++++- openpype/modules/base.py | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 81853faa38..261d65d2ee 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -1,16 +1,25 @@ # -*- coding: utf-8 -*- from .base import ( OpenPypeModule, + OpenPypeAddOn, OpenPypeInterface, + ModulesManager, - TrayModulesManager + TrayModulesManager, + + ModuleSettingsDef, + get_module_settings_defs ) __all__ = ( "OpenPypeModule", + "OpenPypeAddOn", "OpenPypeInterface", "ModulesManager", - "TrayModulesManager" + "TrayModulesManager", + + "ModuleSettingsDef", + "get_module_settings_defs" ) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index d43d5635d1..18bbb75cec 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -920,3 +920,74 @@ class TrayModulesManager(ModulesManager): ), exc_info=True ) + + +def get_module_settings_defs(): + load_modules() + + import openpype_modules + + settings_defs = [] + + log = PypeLogger.get_logger("ModuleSettingsLoad") + + for raw_module in openpype_modules: + for attr_name in dir(raw_module): + attr = getattr(raw_module, attr_name) + if ( + not inspect.isclass(attr) + or attr is ModuleSettingsDef + or not issubclass(attr, ModuleSettingsDef) + ): + continue + + if inspect.isabstract(attr): + # Find missing implementations by convetion on `abc` module + not_implemented = [] + for attr_name in dir(attr): + attr = getattr(attr, attr_name, None) + abs_method = getattr( + attr, "__isabstractmethod__", None + ) + if attr and abs_method: + not_implemented.append(attr_name) + + # Log missing implementations + log.warning(( + "Skipping abstract Class: {} in module {}." + " Missing implementations: {}" + ).format( + attr_name, raw_module.__name__, ", ".join(not_implemented) + )) + continue + + settings_defs.append(attr) + + return settings_defs + + +@six.add_metaclass(ABCMeta) +class ModuleSettingsDef: + @abstractmethod + def get_system_schemas(self): + pass + + @abstractmethod + def get_project_schemas(self): + pass + + @abstractmethod + def save_system_defaults(self, data): + pass + + @abstractmethod + def save_project_defaults(self, data): + pass + + @abstractmethod + def get_system_defaults(self): + pass + + @abstractmethod + def get_project_defaults(self): + pass From f76b5b08679f1e327d1047b5e5217a3e662dc37f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 14:28:12 +0200 Subject: [PATCH 03/43] use constants for schema keys --- openpype/settings/entities/lib.py | 3 +++ openpype/settings/entities/root_entities.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index e58281644a..307792edc9 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -23,6 +23,9 @@ TEMPLATE_METADATA_KEYS = ( DEFAULT_VALUES_KEY, ) +SCHEMA_KEY_SYSTEM_SETTINGS = "system_schema" +SCHEMA_KEY_PROJECT_SETTINGS = "projects_schema" + template_key_pattern = re.compile(r"(\{.*?[^{0]*\})") diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index 00677480e8..39b5cb5096 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -9,6 +9,8 @@ from .base_entity import BaseItemEntity from .lib import ( NOT_SET, WRAPPER_TYPES, + SCHEMA_KEY_SYSTEM_SETTINGS, + SCHEMA_KEY_PROJECT_SETTINGS, OverrideState, SchemasHub ) @@ -468,7 +470,7 @@ class SystemSettings(RootEntity): ): if schema_hub is None: # Load system schemas - schema_hub = SchemasHub("system_schema") + schema_hub = SchemasHub(SCHEMA_KEY_SYSTEM_SETTINGS) super(SystemSettings, self).__init__(schema_hub, reset) @@ -599,7 +601,7 @@ class ProjectSettings(RootEntity): if schema_hub is None: # Load system schemas - schema_hub = SchemasHub("projects_schema") + schema_hub = SchemasHub(SCHEMA_KEY_PROJECT_SETTINGS) super(ProjectSettings, self).__init__(schema_hub, reset) From 50e2fce229992d8f1ca5800b3dcaff3796604cd4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 14:28:19 +0200 Subject: [PATCH 04/43] remove TODO --- openpype/settings/lib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 04d8753869..04e8bffd8f 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -373,8 +373,6 @@ def get_default_settings(): Returns: dict: Loaded default settings. """ - # TODO add cacher - global _DEFAULT_SETTINGS if _DEFAULT_SETTINGS is None: _DEFAULT_SETTINGS = _get_default_settings() From 5db15b273e2a6a36309c51067f432d7d784b369e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 18:20:08 +0200 Subject: [PATCH 05/43] settings def has id --- openpype/modules/base.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 18bbb75cec..8b575bc8cd 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -968,6 +968,14 @@ def get_module_settings_defs(): @six.add_metaclass(ABCMeta) class ModuleSettingsDef: + _id = None + + @property + def id(self): + if self._id is None: + self._id = uuid4() + return self._id + @abstractmethod def get_system_schemas(self): pass From 9d7f0db6d8177860930e533d082bf7e549f2e074 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 18:25:07 +0200 Subject: [PATCH 06/43] changed how schemas are get from openpype --- openpype/modules/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 8b575bc8cd..3d3d7ae6cb 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -977,11 +977,11 @@ class ModuleSettingsDef: return self._id @abstractmethod - def get_system_schemas(self): + def get_settings_schemas(self, schema_type): pass @abstractmethod - def get_project_schemas(self): + def get_dynamic_schemas(self, schema_type): pass @abstractmethod From 5099f5f853d96e5846c7eab31aa0557c3c2ff4da Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 10:33:28 +0200 Subject: [PATCH 07/43] small condition modifications --- openpype/settings/entities/base_entity.py | 2 +- openpype/settings/entities/dict_conditional.py | 2 +- openpype/settings/entities/dict_mutable_keys_entity.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index b4ebe885f5..d9dcf633e5 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -253,7 +253,7 @@ class BaseItemEntity(BaseEntity): # Validate that env group entities will be stored into file. # - env group entities must store metadata which is not possible if # metadata would be outside of file - if not self.file_item and self.is_env_group: + if self.file_item is None and self.is_env_group: reason = ( "Environment item is not inside file" " item so can't store metadata for defaults." diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index d275d8ac3d..fc7cbfdee5 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -469,7 +469,7 @@ class DictConditionalEntity(ItemEntity): output = {} for key, child_obj in children_items: child_value = child_obj.settings_value() - if not child_obj.is_file and not child_obj.file_item: + if not child_obj.is_file and child_obj.file_item is None: for _key, _value in child_value.items(): new_key = "/".join([key, _key]) output[new_key] = _value diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index c3df935269..f75fb23d82 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -261,7 +261,7 @@ class DictMutableKeysEntity(EndpointEntity): raise EntitySchemaError(self, reason) # TODO Ability to store labels should be defined with different key - if self.collapsible_key and not self.file_item: + if self.collapsible_key and self.file_item is None: reason = ( "Modifiable dictionary with collapsible keys is not under" " file item so can't store metadata." From 13720249575860a6aa01c1b7b2e1f21d3ccfefc6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 10:34:26 +0200 Subject: [PATCH 08/43] added loading of setttings modules definitions in SchemaHub --- openpype/settings/entities/lib.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 307792edc9..a72908967f 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -117,14 +117,27 @@ class SchemasHub: # It doesn't make sence to reload types on each reset as they can't be # changed self._load_types() + # Attributes for modules settings + self._modules_settings_defs_by_id = {} + self._dynamic_schemas_by_module_id = {} # Trigger reset if reset: self.reset() def reset(self): + self._load_modules_settings_defs() self._load_schemas() + def _load_modules_settings_defs(self): + from openpype.modules import get_module_settings_defs + + module_settings_defs = get_module_settings_defs() + for module_settings_def_cls in module_settings_defs: + module_settings_def = module_settings_def_cls() + def_id = module_settings_def.id + self._modules_settings_defs_by_id[def_id] = module_settings_def + @property def gui_types(self): return self._gui_types From 2e9e0ba09ff17393b90d18717b0f4fc94e09cb27 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 10:34:52 +0200 Subject: [PATCH 09/43] use constant for extending schema types --- openpype/settings/entities/lib.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index a72908967f..4b6ed5a365 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -26,6 +26,10 @@ TEMPLATE_METADATA_KEYS = ( SCHEMA_KEY_SYSTEM_SETTINGS = "system_schema" SCHEMA_KEY_PROJECT_SETTINGS = "projects_schema" +SCHEMA_EXTEND_TYPES = ( + "schema", "template", "schema_template", "dynamic_schema" +) + template_key_pattern = re.compile(r"(\{.*?[^{0]*\})") @@ -217,7 +221,7 @@ class SchemasHub: list: Resolved schema data. """ schema_type = schema_data["type"] - if schema_type not in ("schema", "template", "schema_template"): + if schema_type not in SCHEMA_EXTEND_TYPES: return [schema_data] if schema_type == "schema": From 2bb74c68e9b8ba41de9268f95c0264f4201bc98a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 10:35:48 +0200 Subject: [PATCH 10/43] added resolving of dynamic module items --- openpype/settings/entities/lib.py | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 4b6ed5a365..dd216f4d90 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -146,6 +146,23 @@ class SchemasHub: def gui_types(self): return self._gui_types + def resolve_dynamic_schema(self, dynamic_key): + output = [] + for def_id, def_keys in self._dynamic_schemas_by_module_id.items(): + if dynamic_key in def_keys: + def_schema = def_keys[dynamic_key] + if not def_schema: + continue + + if isinstance(def_schema, dict): + def_schema = [def_schema] + + for item in def_schema: + item["_module_id"] = def_id + item["_module_store_key"] = dynamic_key + output.extend(def_schema) + return output + def get_schema(self, schema_name): """Get schema definition data by it's name. @@ -229,6 +246,9 @@ class SchemasHub: self.get_schema(schema_data["name"]) ) + if schema_type == "dynamic_schema": + return self.resolve_dynamic_schema(schema_data["name"]) + template_name = schema_data["name"] template_def = self.get_template(template_name) @@ -329,6 +349,7 @@ class SchemasHub: self._crashed_on_load = {} self._loaded_templates = {} self._loaded_schemas = {} + self._dynamic_schemas_by_module_id = {} dirpath = os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -337,6 +358,7 @@ class SchemasHub: ) loaded_schemas = {} loaded_templates = {} + dynamic_schemas_by_module_id = {} for root, _, filenames in os.walk(dirpath): for filename in filenames: basename, ext = os.path.splitext(filename) @@ -386,8 +408,31 @@ class SchemasHub: ) loaded_schemas[basename] = schema_data + defs_iter = self._modules_settings_defs_by_id.items() + for def_id, module_settings_def in defs_iter: + dynamic_schemas_by_module_id[def_id] = ( + module_settings_def.get_dynamic_schemas(self._schema_subfolder) + ) + module_schemas = module_settings_def.get_settings_schemas( + self._schema_subfolder + ) + for key, schema_data in module_schemas.items(): + if isinstance(schema_data, list): + if key in loaded_templates: + raise KeyError( + "Duplicated template key \"{}\"".format(key) + ) + loaded_templates[key] = schema_data + else: + if key in loaded_schemas: + raise KeyError( + "Duplicated schema key \"{}\"".format(key) + ) + loaded_schemas[key] = schema_data + self._loaded_templates = loaded_templates self._loaded_schemas = loaded_schemas + self._dynamic_schemas_by_module_id = dynamic_schemas_by_module_id def _fill_template(self, child_data, template_def): """Fill template based on schema definition and template definition. From b648dd7dc325029906cf69dd8a6887ee31e567ca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 10:35:58 +0200 Subject: [PATCH 11/43] load types on each reset --- openpype/settings/entities/lib.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index dd216f4d90..91e66eec8e 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -118,9 +118,6 @@ class SchemasHub: self._loaded_templates = {} self._loaded_schemas = {} - # It doesn't make sence to reload types on each reset as they can't be - # changed - self._load_types() # Attributes for modules settings self._modules_settings_defs_by_id = {} self._dynamic_schemas_by_module_id = {} @@ -131,6 +128,7 @@ class SchemasHub: def reset(self): self._load_modules_settings_defs() + self._load_types() self._load_schemas() def _load_modules_settings_defs(self): From 32011838b3e4404c09249b2b60c69e533d63b300 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:10:33 +0200 Subject: [PATCH 12/43] renamed variable --- openpype/settings/entities/lib.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 91e66eec8e..b87845b95e 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -120,7 +120,7 @@ class SchemasHub: # Attributes for modules settings self._modules_settings_defs_by_id = {} - self._dynamic_schemas_by_module_id = {} + self._dynamic_schemas_def_by_id = {} # Trigger reset if reset: @@ -146,7 +146,7 @@ class SchemasHub: def resolve_dynamic_schema(self, dynamic_key): output = [] - for def_id, def_keys in self._dynamic_schemas_by_module_id.items(): + for def_id, def_keys in self._dynamic_schemas_def_by_id.items(): if dynamic_key in def_keys: def_schema = def_keys[dynamic_key] if not def_schema: @@ -347,7 +347,7 @@ class SchemasHub: self._crashed_on_load = {} self._loaded_templates = {} self._loaded_schemas = {} - self._dynamic_schemas_by_module_id = {} + self._dynamic_schemas_def_by_id = {} dirpath = os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -356,7 +356,7 @@ class SchemasHub: ) loaded_schemas = {} loaded_templates = {} - dynamic_schemas_by_module_id = {} + dynamic_schemas_def_by_id = {} for root, _, filenames in os.walk(dirpath): for filename in filenames: basename, ext = os.path.splitext(filename) @@ -408,7 +408,7 @@ class SchemasHub: defs_iter = self._modules_settings_defs_by_id.items() for def_id, module_settings_def in defs_iter: - dynamic_schemas_by_module_id[def_id] = ( + dynamic_schemas_def_by_id[def_id] = ( module_settings_def.get_dynamic_schemas(self._schema_subfolder) ) module_schemas = module_settings_def.get_settings_schemas( @@ -430,7 +430,7 @@ class SchemasHub: self._loaded_templates = loaded_templates self._loaded_schemas = loaded_schemas - self._dynamic_schemas_by_module_id = dynamic_schemas_by_module_id + self._dynamic_schemas_def_by_id = dynamic_schemas_def_by_id def _fill_template(self, child_data, template_def): """Fill template based on schema definition and template definition. From f38c7a462e96755d6dce8255becef57810de9b06 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:15:21 +0200 Subject: [PATCH 13/43] added few attributes for dynamic schemas --- openpype/settings/entities/base_entity.py | 12 ++++++++++++ openpype/settings/entities/lib.py | 3 +-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index d9dcf633e5..832c8ab854 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -104,6 +104,12 @@ class BaseItemEntity(BaseEntity): self.is_group = False # Entity's value will be stored into file with name of it's key self.is_file = False + # Default values are not stored to an openpype file + # - these must not be set through schemas directly + self.dynamic_schema_id = None + self.is_dynamic_schema_node = False + self.is_in_dynamic_schema_node = False + # Reference to parent entity which has `is_group` == True # - stays as None if none of parents is group self.group_item = None @@ -800,6 +806,12 @@ class ItemEntity(BaseItemEntity): self.is_dynamic_item = is_dynamic_item self.is_file = self.schema_data.get("is_file", False) + # These keys have underscore as they must not be set in schemas + self.dynamic_schema_id = self.schema_data.get( + "_dynamic_schema_id", None + ) + self.is_dynamic_schema_node = self.dynamic_schema_id is not None + self.is_group = self.schema_data.get("is_group", False) self.is_in_dynamic_item = bool( not self.is_dynamic_item diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index b87845b95e..2a1bbaa115 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -156,8 +156,7 @@ class SchemasHub: def_schema = [def_schema] for item in def_schema: - item["_module_id"] = def_id - item["_module_store_key"] = dynamic_key + item["_dynamic_schema_id"] = def_id output.extend(def_schema) return output From 5541b3fd0cc8fd5fe60cb11a8c22dfbc8f4911db Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:16:57 +0200 Subject: [PATCH 14/43] added few conditions so it is possbile to load dynamic schemas --- openpype/settings/entities/base_entity.py | 25 ++++++++++++++++---- openpype/settings/entities/input_entities.py | 6 ++++- openpype/settings/entities/item_entities.py | 7 +++++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index 832c8ab854..bea90882a7 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -841,10 +841,20 @@ class ItemEntity(BaseItemEntity): self._require_restart_on_change = require_restart_on_change # File item reference - if self.parent.is_file: - self.file_item = self.parent - elif self.parent.file_item: - self.file_item = self.parent.file_item + if not self.is_dynamic_schema_node: + self.is_in_dynamic_schema_node = ( + self.parent.is_dynamic_schema_node + or self.parent.is_in_dynamic_schema_node + ) + + if ( + not self.is_dynamic_schema_node + and not self.is_in_dynamic_schema_node + ): + if self.parent.is_file: + self.file_item = self.parent + elif self.parent.file_item: + self.file_item = self.parent.file_item # Group item reference if self.parent.is_group: @@ -903,7 +913,12 @@ class ItemEntity(BaseItemEntity): ) raise EntitySchemaError(self, reason) - if self.is_file and self.file_item is not None: + if ( + not self.is_dynamic_schema_node + and not self.is_in_dynamic_schema_node + and self.is_file + and self.file_item is not None + ): reason = ( "Entity has set `is_file` to true but" " it's parent is already marked as file item." diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 6952529963..b65c1c440e 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -116,7 +116,11 @@ class InputEntity(EndpointEntity): def schema_validations(self): # Input entity must have file parent. - if not self.file_item: + if ( + not self.is_dynamic_schema_node + and not self.is_in_dynamic_schema_node + and self.file_item is None + ): raise EntitySchemaError(self, "Missing parent file entity.") super(InputEntity, self).schema_validations() diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index 7e84f8c801..1e4f1025cc 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -215,7 +215,12 @@ class ListStrictEntity(ItemEntity): def schema_validations(self): # List entity must have file parent. - if not self.file_item and not self.is_file: + if ( + not self.is_dynamic_schema_node + and not self.is_in_dynamic_schema_node + and not self.is_file + and self.file_item is None + ): raise EntitySchemaError( self, "Missing file entity in hierarchy." ) From eaa1499510566f5d6ebc8308db50b4c5d8780c5a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:17:17 +0200 Subject: [PATCH 15/43] conditional dict does not care about paths as it must be group --- openpype/settings/entities/dict_conditional.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index fc7cbfdee5..8a944e5fdc 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -468,13 +468,7 @@ class DictConditionalEntity(ItemEntity): output = {} for key, child_obj in children_items: - child_value = child_obj.settings_value() - if not child_obj.is_file and child_obj.file_item is None: - for _key, _value in child_value.items(): - new_key = "/".join([key, _key]) - output[new_key] = _value - else: - output[key] = child_value + output[key] = child_obj.settings_value() return output if self.is_group: From f65dee0a0e8b40f42eb8485faf1377d01542f52a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:22:53 +0200 Subject: [PATCH 16/43] added method which collects dynamic schema entities --- openpype/settings/entities/base_entity.py | 22 ++++++++++++++++++- .../settings/entities/dict_conditional.py | 4 ++++ .../entities/dict_immutable_keys_entity.py | 7 ++++++ openpype/settings/entities/input_entities.py | 4 ++++ openpype/settings/entities/item_entities.py | 7 ++++++ openpype/settings/entities/lib.py | 9 ++++++++ openpype/settings/entities/root_entities.py | 13 ++++++++++- 7 files changed, 64 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index bea90882a7..0d2923f9e0 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -476,7 +476,15 @@ class BaseItemEntity(BaseEntity): @abstractmethod def settings_value(self): - """Value of an item without key.""" + """Value of an item without key without dynamic items.""" + pass + + @abstractmethod + def collect_dynamic_schema_entities(self): + """Collect entities that are on top of dynamically added schemas. + + This method make sence only when defaults are saved. + """ pass @abstractmethod @@ -905,6 +913,18 @@ class ItemEntity(BaseItemEntity): def root_key(self): return self.root_item.root_key + @abstractmethod + def collect_dynamic_schema_entities(self, collector): + """Collect entities that are on top of dynamically added schemas. + + This method make sence only when defaults are saved. + + Args: + collector(DynamicSchemaValueCollector): Object where dynamic + entities are stored. + """ + pass + def schema_validations(self): if not self.label and self.use_label_wrap: reason = ( diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8a944e5fdc..44775e9113 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -455,6 +455,10 @@ class DictConditionalEntity(ItemEntity): return True return False + def collect_dynamic_schema_entities(self, collector): + if self.is_dynamic_schema_node: + collector.add_entity(self) + def settings_value(self): if self._override_state is OverrideState.NOT_DEFINED: return NOT_SET diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index bde5304787..24cd9401b9 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -318,6 +318,13 @@ class DictImmutableKeysEntity(ItemEntity): return True return False + def collect_dynamic_schema_entities(self, collector): + for child_obj in self.non_gui_children.values(): + child_obj.collect_dynamic_schema_entities(collector) + + if self.is_dynamic_schema_node: + collector.add_entity(self) + def settings_value(self): if self._override_state is OverrideState.NOT_DEFINED: return NOT_SET diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index b65c1c440e..469fdee310 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -49,6 +49,10 @@ class EndpointEntity(ItemEntity): super(EndpointEntity, self).schema_validations() + def collect_dynamic_schema_entities(self, collector): + if self.is_dynamic_schema_node: + collector.add_entity(self) + @abstractmethod def _settings_value(self): pass diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index 1e4f1025cc..3823a25c60 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -112,6 +112,9 @@ class PathEntity(ItemEntity): def set(self, value): self.child_obj.set(value) + def collect_dynamic_schema_entities(self, *args, **kwargs): + self.child_obj.collect_dynamic_schema_entities(*args, **kwargs) + def settings_value(self): if self._override_state is OverrideState.NOT_DEFINED: return NOT_SET @@ -251,6 +254,10 @@ class ListStrictEntity(ItemEntity): for idx, item in enumerate(new_value): self.children[idx].set(item) + def collect_dynamic_schema_entities(self, collector): + if self.is_dynamic_schema_node: + collector.add_entity(self) + def settings_value(self): if self._override_state is OverrideState.NOT_DEFINED: return NOT_SET diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 2a1bbaa115..98dede39e8 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -663,3 +663,12 @@ class SchemasHub: if found_idx is not None: metadata_item = template_def.pop(found_idx) return metadata_item + + +class DynamicSchemaValueCollector: + def __init__(self, schema_hub): + self._schema_hub = schema_hub + self._dynamic_entities = [] + + def add_entity(self, entity): + self._dynamic_entities.append(entity) diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index 39b5cb5096..2c88016344 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -12,7 +12,8 @@ from .lib import ( SCHEMA_KEY_SYSTEM_SETTINGS, SCHEMA_KEY_PROJECT_SETTINGS, OverrideState, - SchemasHub + SchemasHub, + DynamicSchemaValueCollector ) from .exceptions import ( SchemaError, @@ -259,6 +260,16 @@ class RootEntity(BaseItemEntity): output[key] = child_obj.value return output + def collect_dynamic_schema_entities(self): + output = DynamicSchemaValueCollector(self.schema_hub) + if self._override_state is not OverrideState.DEFAULTS: + return output + + for child_obj in self.non_gui_children.values(): + child_obj.collect_dynamic_schema_entities(output) + + return output + def settings_value(self): """Value for current override state with metadata. From 6282e8d1ad57e85e7c8c8e11ddb201b4bf56b21a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:23:23 +0200 Subject: [PATCH 17/43] skip dynamic schema entities in settings values method --- openpype/settings/entities/dict_immutable_keys_entity.py | 3 +++ openpype/settings/entities/root_entities.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index 24cd9401b9..a81a64c183 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -332,6 +332,9 @@ class DictImmutableKeysEntity(ItemEntity): if self._override_state is OverrideState.DEFAULTS: output = {} for key, child_obj in self.non_gui_children.items(): + if child_obj.is_dynamic_schema_node: + continue + child_value = child_obj.settings_value() if not child_obj.is_file and not child_obj.file_item: for _key, _value in child_value.items(): diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index 2c88016344..b178e3fa36 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -281,6 +281,8 @@ class RootEntity(BaseItemEntity): if self._override_state is not OverrideState.DEFAULTS: output = {} for key, child_obj in self.non_gui_children.items(): + if child_obj.is_dynamic_schema_node: + continue value = child_obj.settings_value() if value is not NOT_SET: output[key] = value From 37ef6d022355f7f325b933c6192665c5811cde6c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:23:39 +0200 Subject: [PATCH 18/43] ignore file handling for dynamic schema nodes --- openpype/settings/entities/dict_immutable_keys_entity.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index a81a64c183..8871a3a3d9 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -330,13 +330,20 @@ class DictImmutableKeysEntity(ItemEntity): return NOT_SET if self._override_state is OverrideState.DEFAULTS: + is_dynamic_schema_node = ( + self.is_dynamic_schema_node or self.is_in_dynamic_schema_node + ) output = {} for key, child_obj in self.non_gui_children.items(): if child_obj.is_dynamic_schema_node: continue child_value = child_obj.settings_value() - if not child_obj.is_file and not child_obj.file_item: + if ( + not is_dynamic_schema_node + and not child_obj.is_file + and not child_obj.file_item + ): for _key, _value in child_value.items(): new_key = "/".join([key, _key]) output[new_key] = _value From cf9114b0f1dfbea06bb8b688dbfa3627a885d41c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:23:57 +0200 Subject: [PATCH 19/43] added schema validation of dynamic schemas --- openpype/settings/entities/base_entity.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index 0d2923f9e0..f5f5b4d761 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -253,9 +253,18 @@ class BaseItemEntity(BaseEntity): ) # Group item can be only once in on hierarchy branch. - if self.is_group and self.group_item: + if self.is_group and self.group_item is not None: raise SchemeGroupHierarchyBug(self) + # Group item can be only once in on hierarchy branch. + if self.group_item is not None and self.is_dynamic_schema_node: + reason = ( + "Dynamic schema is inside grouped item {}." + " Change group hierarchy or remove dynamic" + " schema to be able work properly." + ).format(self.group_item.path) + raise EntitySchemaError(self, reason) + # Validate that env group entities will be stored into file. # - env group entities must store metadata which is not possible if # metadata would be outside of file From fff590f7f88aaf0130adfc7cf6acd98cc6dac05c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 12:43:16 +0200 Subject: [PATCH 20/43] add getter method for dynamic schema definitions --- openpype/settings/entities/lib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 98dede39e8..3877b49648 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -431,6 +431,9 @@ class SchemasHub: self._loaded_schemas = loaded_schemas self._dynamic_schemas_def_by_id = dynamic_schemas_def_by_id + def get_dynamic_schema_def(self, schema_def_id): + return self._dynamic_schemas_def_by_id.get(schema_def_id) + def _fill_template(self, child_data, template_def): """Fill template based on schema definition and template definition. From b507b4e9d33b6a65003954545f7fa271030d995d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 13:02:08 +0200 Subject: [PATCH 21/43] modified dynamic schemas attributes --- openpype/settings/entities/lib.py | 36 +++++++++++++++++-------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 3877b49648..457468b18b 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -108,8 +108,8 @@ class OverrideState: class SchemasHub: - def __init__(self, schema_subfolder, reset=True): - self._schema_subfolder = schema_subfolder + def __init__(self, schema_type, reset=True): + self._schema_type = schema_type self._loaded_types = {} self._gui_types = tuple() @@ -119,13 +119,17 @@ class SchemasHub: self._loaded_schemas = {} # Attributes for modules settings - self._modules_settings_defs_by_id = {} - self._dynamic_schemas_def_by_id = {} + self._dynamic_schemas_defs_by_id = {} + self._dynamic_schemas_by_id = {} # Trigger reset if reset: self.reset() + @property + def schema_type(self): + return self._schema_type + def reset(self): self._load_modules_settings_defs() self._load_types() @@ -138,7 +142,7 @@ class SchemasHub: for module_settings_def_cls in module_settings_defs: module_settings_def = module_settings_def_cls() def_id = module_settings_def.id - self._modules_settings_defs_by_id[def_id] = module_settings_def + self._dynamic_schemas_defs_by_id[def_id] = module_settings_def @property def gui_types(self): @@ -146,7 +150,7 @@ class SchemasHub: def resolve_dynamic_schema(self, dynamic_key): output = [] - for def_id, def_keys in self._dynamic_schemas_def_by_id.items(): + for def_id, def_keys in self._dynamic_schemas_by_id.items(): if dynamic_key in def_keys: def_schema = def_keys[dynamic_key] if not def_schema: @@ -346,16 +350,16 @@ class SchemasHub: self._crashed_on_load = {} self._loaded_templates = {} self._loaded_schemas = {} - self._dynamic_schemas_def_by_id = {} + self._dynamic_schemas_by_id = {} dirpath = os.path.join( os.path.dirname(os.path.abspath(__file__)), "schemas", - self._schema_subfolder + self.schema_type ) loaded_schemas = {} loaded_templates = {} - dynamic_schemas_def_by_id = {} + dynamic_schemas_by_id = {} for root, _, filenames in os.walk(dirpath): for filename in filenames: basename, ext = os.path.splitext(filename) @@ -405,13 +409,13 @@ class SchemasHub: ) loaded_schemas[basename] = schema_data - defs_iter = self._modules_settings_defs_by_id.items() + defs_iter = self._dynamic_schemas_defs_by_id.items() for def_id, module_settings_def in defs_iter: - dynamic_schemas_def_by_id[def_id] = ( - module_settings_def.get_dynamic_schemas(self._schema_subfolder) + dynamic_schemas_by_id[def_id] = ( + module_settings_def.get_dynamic_schemas(self.schema_type) ) module_schemas = module_settings_def.get_settings_schemas( - self._schema_subfolder + self.schema_type ) for key, schema_data in module_schemas.items(): if isinstance(schema_data, list): @@ -429,10 +433,10 @@ class SchemasHub: self._loaded_templates = loaded_templates self._loaded_schemas = loaded_schemas - self._dynamic_schemas_def_by_id = dynamic_schemas_def_by_id + self._dynamic_schemas_by_id = dynamic_schemas_by_id - def get_dynamic_schema_def(self, schema_def_id): - return self._dynamic_schemas_def_by_id.get(schema_def_id) + def get_dynamic_modules_settings_defs(self, schema_def_id): + return self._dynamic_schemas_defs_by_id.get(schema_def_id) def _fill_template(self, child_data, template_def): """Fill template based on schema definition and template definition. From 90076d519f389a8a5a50e1df5f9771294a2128b0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 13:02:45 +0200 Subject: [PATCH 22/43] removed project_settings getter --- openpype/settings/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 04e8bffd8f..d7684082f3 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -354,7 +354,7 @@ def _get_default_settings(): if not path: continue - subdict = defaults["project_settings"] + subdict = defaults path_items = list(path.split("/")) last_key = path_items.pop(-1) for key in path_items: From 9d31ec70116589ab0d00bf6a6c0d840420999924 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 13:03:43 +0200 Subject: [PATCH 23/43] implemented save for dynamic schemas --- openpype/settings/entities/lib.py | 21 +++++++++++++++++++++ openpype/settings/entities/root_entities.py | 3 +++ 2 files changed, 24 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 457468b18b..13037ac373 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 collections from .exceptions import ( SchemaTemplateMissingKeys, @@ -679,3 +680,23 @@ class DynamicSchemaValueCollector: def add_entity(self, entity): self._dynamic_entities.append(entity) + + def create_hierarchy(self): + output = collections.defaultdict(dict) + for entity in self._dynamic_entities: + output[entity.dynamic_schema_id][entity.path] = ( + entity.settings_value() + ) + return output + + def save_values(self): + hierarchy = self.create_hierarchy() + + for schema_def_id, schema_def_value in hierarchy.items(): + schema_def = self._schema_hub.get_dynamic_modules_settings_defs( + schema_def_id + ) + if self._schema_hub.schema_type == SCHEMA_KEY_SYSTEM_SETTINGS: + schema_def.save_system_defaults(schema_def_value) + elif self._schema_hub.schema_type == SCHEMA_KEY_PROJECT_SETTINGS: + schema_def.save_project_defaults(schema_def_value) diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index b178e3fa36..6f444d5394 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -428,6 +428,9 @@ class RootEntity(BaseItemEntity): with open(output_path, "w") as file_stream: json.dump(value, file_stream, indent=4) + dynamic_values_item = self.collect_dynamic_schema_entities() + dynamic_values_item.save_values() + @abstractmethod def _save_studio_values(self): """Save studio override values.""" From 8cfe9bb270e2859c09a0ec17554b11aac4860a87 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 13:04:04 +0200 Subject: [PATCH 24/43] added first dynamic_schema item in schemas --- .../entities/schemas/projects_schema/schema_main.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 4a8a9d496e..058ff492f3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -121,6 +121,10 @@ { "type": "schema", "name": "schema_project_unreal" + }, + { + "type": "dynamic_schema", + "name": "project_settings/global" } ] } From 1aa3d42704813ce67722f3053bd1a9462a90247c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 13:06:43 +0200 Subject: [PATCH 25/43] reset defaults on save defaults --- openpype/settings/entities/root_entities.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index 6f444d5394..78e8aad47f 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -31,6 +31,7 @@ from openpype.settings.lib import ( DEFAULTS_DIR, get_default_settings, + reset_default_settings, get_studio_system_settings_overrides, save_studio_settings, @@ -381,6 +382,7 @@ class RootEntity(BaseItemEntity): if self._override_state is OverrideState.DEFAULTS: self._save_default_values() + reset_default_settings() elif self._override_state is OverrideState.STUDIO: self._save_studio_values() From 6b6877e76ed934b22bfc3b4206de7ed16984a52e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 15:52:49 +0200 Subject: [PATCH 26/43] fixed get_general_environments function --- openpype/settings/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/__init__.py b/openpype/settings/__init__.py index b5810deef4..9797458fd5 100644 --- a/openpype/settings/__init__.py +++ b/openpype/settings/__init__.py @@ -2,6 +2,7 @@ from .exceptions import ( SaveWarningExc ) from .lib import ( + get_general_environments, get_system_settings, get_project_settings, get_current_project_settings, @@ -18,6 +19,7 @@ from .entities import ( __all__ = ( "SaveWarningExc", + "get_general_environments", "get_system_settings", "get_project_settings", "get_current_project_settings", From cf811c7e0f56353af0a3a18e01593db310d1edce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 15:53:03 +0200 Subject: [PATCH 27/43] added addons_path key to settings --- .../settings/defaults/system_settings/modules.json | 5 +++++ .../schemas/system_schema/schema_modules.json | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 3a70b90590..12cca7ccf1 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -1,4 +1,9 @@ { + "addon_paths": { + "windows": [], + "darwin": [], + "linux": [] + }, "avalon": { "AVALON_TIMEOUT": 1000, "AVALON_THUMBNAIL_ROOT": { diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 75c08b2cd9..0e52cea69e 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -5,6 +5,18 @@ "collapsible": true, "is_file": true, "children": [ + { + "type": "path", + "key": "addon_paths", + "label": "OpenPype AddOn Paths", + "use_label_wrap": true, + "multiplatform": true, + "multipath": true, + "require_restart": true + }, + { + "type": "separator" + }, { "type": "dict", "key": "avalon", From b92621a270704bb8b61842d977d883ea230b304f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 15:53:19 +0200 Subject: [PATCH 28/43] don't crash if path does not exists --- openpype/modules/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 3d3d7ae6cb..e407a34606 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -165,6 +165,9 @@ def _load_interfaces(): os.path.join(get_default_modules_dir(), "interfaces.py") ) for dirpath in dirpaths: + if not os.path.exists(dirpath): + continue + for filename in os.listdir(dirpath): if filename in ("__pycache__", ): continue From bf1a5c85ccf4db4d3be2c99a7ff3f1c9d5cdf519 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 15:54:08 +0200 Subject: [PATCH 29/43] added get_dynamic_modules_dirs to be able get paths to openpype addons --- openpype/modules/base.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index e407a34606..a3269e99e9 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -115,11 +115,24 @@ def get_default_modules_dir(): return os.path.join(current_dir, "default_modules") +def get_dynamic_modules_dirs(): + output = [] + return output + + def get_module_dirs(): """List of paths where OpenPype modules can be found.""" - dirpaths = [ - get_default_modules_dir() - ] + _dirpaths = [] + _dirpaths.append(get_default_modules_dir()) + _dirpaths.extend(get_dynamic_modules_dirs()) + + dirpaths = [] + for path in _dirpaths: + if not path: + continue + normalized = os.path.normpath(path) + if normalized not in dirpaths: + dirpaths.append(normalized) return dirpaths From e3754a85a662dbcbaf2d06cc8285638b7ea9d7d2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 15:54:23 +0200 Subject: [PATCH 30/43] implemented logic of dynamic addons paths --- openpype/modules/base.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index a3269e99e9..d3b83e85b1 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -5,6 +5,7 @@ import sys import time import inspect import logging +import platform import threading import collections from uuid import uuid4 @@ -13,6 +14,7 @@ import six import openpype from openpype.settings import get_system_settings +from openpype.settings.lib import get_studio_system_settings_overrides from openpype.lib import PypeLogger @@ -117,6 +119,21 @@ def get_default_modules_dir(): def get_dynamic_modules_dirs(): output = [] + value = get_studio_system_settings_overrides() + for key in ("modules", "addon_paths", platform.system().lower()): + if key not in value: + return output + value = value[key] + + for path in value: + if not path: + continue + + try: + path = path.format(**os.environ) + except Exception: + pass + output.append(path) return output From 2495cffd509a51838fc1c8c5c77d05a007794322 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 16:44:58 +0200 Subject: [PATCH 31/43] don't crash whole openpype on broken addon/module --- openpype/modules/base.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index d3b83e85b1..9df9b3a97b 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -305,12 +305,19 @@ def _load_modules(): # TODO add more logic how to define if folder is module or not # - check manifest and content of manifest - if os.path.isdir(fullpath): - import_module_from_dirpath(dirpath, filename, modules_key) + try: + if os.path.isdir(fullpath): + import_module_from_dirpath(dirpath, filename, modules_key) - elif ext in (".py", ): - module = import_filepath(fullpath) - setattr(openpype_modules, basename, module) + elif ext in (".py", ): + module = import_filepath(fullpath) + setattr(openpype_modules, basename, module) + + except Exception: + log.error( + "Failed to import '{}'.".format(fullpath), + exc_info=True + ) class _OpenPypeInterfaceMeta(ABCMeta): From bd791c971985bd2229c256d8946f808733307597 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 17:08:44 +0200 Subject: [PATCH 32/43] moved few settings constants to constants.py --- openpype/settings/__init__.py | 25 +++++++++++++++++++++++++ openpype/settings/constants.py | 9 ++++++++- openpype/settings/entities/lib.py | 7 ++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/openpype/settings/__init__.py b/openpype/settings/__init__.py index 9797458fd5..0adb5db0bd 100644 --- a/openpype/settings/__init__.py +++ b/openpype/settings/__init__.py @@ -1,3 +1,16 @@ +from .constants import ( + GLOBAL_SETTINGS_KEY, + SYSTEM_SETTINGS_KEY, + PROJECT_SETTINGS_KEY, + PROJECT_ANATOMY_KEY, + LOCAL_SETTING_KEY, + + SCHEMA_KEY_SYSTEM_SETTINGS, + SCHEMA_KEY_PROJECT_SETTINGS, + + KEY_ALLOWED_SYMBOLS, + KEY_REGEX +) from .exceptions import ( SaveWarningExc ) @@ -17,6 +30,18 @@ from .entities import ( __all__ = ( + "GLOBAL_SETTINGS_KEY", + "SYSTEM_SETTINGS_KEY", + "PROJECT_SETTINGS_KEY", + "PROJECT_ANATOMY_KEY", + "LOCAL_SETTING_KEY", + + "SCHEMA_KEY_SYSTEM_SETTINGS", + "SCHEMA_KEY_PROJECT_SETTINGS", + + "KEY_ALLOWED_SYMBOLS", + "KEY_REGEX", + "SaveWarningExc", "get_general_environments", diff --git a/openpype/settings/constants.py b/openpype/settings/constants.py index a53e88a91e..2ea19ead4b 100644 --- a/openpype/settings/constants.py +++ b/openpype/settings/constants.py @@ -14,13 +14,17 @@ METADATA_KEYS = ( M_DYNAMIC_KEY_LABEL ) -# File where studio's system overrides are stored +# Keys where studio's system overrides are stored GLOBAL_SETTINGS_KEY = "global_settings" SYSTEM_SETTINGS_KEY = "system_settings" PROJECT_SETTINGS_KEY = "project_settings" PROJECT_ANATOMY_KEY = "project_anatomy" LOCAL_SETTING_KEY = "local_settings" +# Schema hub names +SCHEMA_KEY_SYSTEM_SETTINGS = "system_schema" +SCHEMA_KEY_PROJECT_SETTINGS = "projects_schema" + DEFAULT_PROJECT_KEY = "__default_project__" KEY_ALLOWED_SYMBOLS = "a-zA-Z0-9-_ " @@ -39,6 +43,9 @@ __all__ = ( "PROJECT_ANATOMY_KEY", "LOCAL_SETTING_KEY", + "SCHEMA_KEY_SYSTEM_SETTINGS", + "SCHEMA_KEY_PROJECT_SETTINGS", + "DEFAULT_PROJECT_KEY", "KEY_ALLOWED_SYMBOLS", diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index f7036726d2..d4b0e10864 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -11,6 +11,10 @@ from .exceptions import ( SchemaDuplicatedEnvGroupKeys ) +from openpype.settings.constants import ( + SCHEMA_KEY_SYSTEM_SETTINGS, + SCHEMA_KEY_PROJECT_SETTINGS +) try: STRING_TYPE = basestring except Exception: @@ -25,9 +29,6 @@ TEMPLATE_METADATA_KEYS = ( DEFAULT_VALUES_KEY, ) -SCHEMA_KEY_SYSTEM_SETTINGS = "system_schema" -SCHEMA_KEY_PROJECT_SETTINGS = "projects_schema" - SCHEMA_EXTEND_TYPES = ( "schema", "template", "schema_template", "dynamic_schema" ) From 2706c7759f6ed01aecb7a205f4cfc806aa22b7d3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 17:15:06 +0200 Subject: [PATCH 33/43] a littlebit safer return value check --- openpype/settings/lib.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index d7684082f3..60ed54bd4a 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -337,7 +337,9 @@ def _get_default_settings(): module_settings_defs = get_module_settings_defs() for module_settings_def_cls in module_settings_defs: module_settings_def = module_settings_def_cls() - system_defaults = module_settings_def.get_system_defaults() + system_defaults = module_settings_def.get_defaults( + SYSTEM_SETTINGS_KEY + ) or {} for path, value in system_defaults.items(): if not path: continue @@ -349,7 +351,9 @@ def _get_default_settings(): subdict = subdict[key] subdict[last_key] = value - project_defaults = module_settings_def.get_project_defaults() + project_defaults = module_settings_def.get_defaults( + PROJECT_SETTINGS_KEY + ) or {} for path, value in project_defaults.items(): if not path: continue From 735f4b847b7e990c8e6c0e22ed45a5d6d4c6c829 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 17:15:29 +0200 Subject: [PATCH 34/43] added mapping of schema hub key to top key value --- openpype/settings/entities/lib.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index d4b0e10864..f207322dee 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -12,6 +12,8 @@ from .exceptions import ( ) from openpype.settings.constants import ( + SYSTEM_SETTINGS_KEY, + PROJECT_SETTINGS_KEY, SCHEMA_KEY_SYSTEM_SETTINGS, SCHEMA_KEY_PROJECT_SETTINGS ) @@ -734,6 +736,12 @@ class SchemasHub: class DynamicSchemaValueCollector: + # Map schema hub type to store keys + schema_hub_type_map = { + SCHEMA_KEY_SYSTEM_SETTINGS: SYSTEM_SETTINGS_KEY, + SCHEMA_KEY_PROJECT_SETTINGS: PROJECT_SETTINGS_KEY + } + def __init__(self, schema_hub): self._schema_hub = schema_hub self._dynamic_entities = [] @@ -756,7 +764,7 @@ class DynamicSchemaValueCollector: schema_def = self._schema_hub.get_dynamic_modules_settings_defs( schema_def_id ) - if self._schema_hub.schema_type == SCHEMA_KEY_SYSTEM_SETTINGS: - schema_def.save_system_defaults(schema_def_value) - elif self._schema_hub.schema_type == SCHEMA_KEY_PROJECT_SETTINGS: - schema_def.save_project_defaults(schema_def_value) + top_key = self.schema_hub_type_map.get( + self._schema_hub.schema_type + ) + schema_def.save_defaults(top_key, schema_def_value) From f30253697127285c21650dc400f81de577f24074 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 17:15:46 +0200 Subject: [PATCH 35/43] eliminated methods in ModuleSettingsDef --- openpype/modules/base.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 9df9b3a97b..ce555c6bbf 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -1025,17 +1025,9 @@ class ModuleSettingsDef: pass @abstractmethod - def save_system_defaults(self, data): + def get_defaults(self, top_key): pass @abstractmethod - def save_project_defaults(self, data): - pass - - @abstractmethod - def get_system_defaults(self): - pass - - @abstractmethod - def get_project_defaults(self): + def save_defaults(self, top_key, data): pass From c16cee6f810ecd1cd0a45b99cf77079e83c6d51f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 17:47:37 +0200 Subject: [PATCH 36/43] added few docstrings --- openpype/modules/base.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index ce555c6bbf..9972126136 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -1008,26 +1008,63 @@ def get_module_settings_defs(): @six.add_metaclass(ABCMeta) class ModuleSettingsDef: + """Definition of settings for OpenPype module or AddOn.""" _id = None @property def id(self): + """ID created on initialization. + + ID should be per created object. Helps to store objects. + """ if self._id is None: self._id = uuid4() return self._id @abstractmethod def get_settings_schemas(self, schema_type): + """Setting schemas for passed schema type. + + These are main schemas by dynamic schema keys. If they're using + sub schemas or templates they should be loaded with + `get_dynamic_schemas`. + + Returns: + dict: Schema by `dynamic_schema` keys. + """ pass @abstractmethod def get_dynamic_schemas(self, schema_type): + """Settings schemas and templates that can be used anywhere. + + It is recommended to add prefix specific for addon/module to keys + (e.g. "my_addon/real_schema_name"). + + Returns: + dict: Schemas and templates by their keys. + """ pass @abstractmethod def get_defaults(self, top_key): + """Default values for passed top key. + + Top keys are (currently) "system_settings" or "project_settings". + + Should return exactly what was passed with `save_defaults`. + + Returns: + dict: Default values by path to first key in OpenPype defaults. + """ pass @abstractmethod def save_defaults(self, top_key, data): + """Save default values for passed top key. + + Top keys are (currently) "system_settings" or "project_settings". + + Passed data are by path to first key defined in main schemas. + """ pass From 60ff21534219da5eeada4c69873ae0baff4b71a0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 17:54:04 +0200 Subject: [PATCH 37/43] added few infor to readme --- openpype/settings/entities/schemas/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 2034d4e463..a34732fbad 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -112,6 +112,22 @@ ``` - It is possible to define default values for unfilled fields to do so one of items in list must be dictionary with key `"__default_values__"` and value as dictionary with default key: values (as in example above). +### dynamic_schema +- dynamic templates that can be defined by class of `ModuleSettingsDef` +- example: +``` +{ + "type": "dynamic_schema", + "name": "project_settings/global" +} +``` +- all valid `ModuleSettingsDef` classes where calling of `get_settings_schemas` + will return dictionary where is key "project_settings/global" with schemas + will extend and replace this item +- works almost the same way as templates + - one item can be replaced by multiple items (or by 0 items) +- goal is to dynamically loaded settings of OpenPype addons without having + their schemas or default values in main repository ## Basic Dictionary inputs - these inputs wraps another inputs into {key: value} relation From b869f2ab82657d24af093554654c48538a177be6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 24 Aug 2021 18:00:18 +0200 Subject: [PATCH 38/43] added few more docstrings --- openpype/modules/base.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 9972126136..c8cc911ca6 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -118,6 +118,18 @@ def get_default_modules_dir(): def get_dynamic_modules_dirs(): + """Possible paths to OpenPype Addons of Modules. + + Paths are loaded from studio settings under: + `modules -> addon_paths -> {platform name}` + + Path may contain environment variable as a formatting string. + + They are not validated or checked their existence. + + Returns: + list: Paths loaded from studio overrides. + """ output = [] value = get_studio_system_settings_overrides() for key in ("modules", "addon_paths", platform.system().lower()): @@ -963,6 +975,17 @@ class TrayModulesManager(ModulesManager): def get_module_settings_defs(): + """Check loaded addons/modules for existence of thei settings definition. + + Check if OpenPype addon/module as python module has class that inherit + from `ModuleSettingsDef` in python module variables (imported + in `__init__py`). + + Returns: + list: All valid and not abstract settings definitions from imported + openpype addons and modules. + """ + # Make sure modules are loaded load_modules() import openpype_modules From 4c55040a58c9b68ebf1a804b7b2df84fb45b9c16 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Aug 2021 16:55:10 +0200 Subject: [PATCH 39/43] enhanced `ModuleSettingsDef` to split each method into 2 separated abstract methods --- openpype/modules/__init__.py | 2 + openpype/modules/base.py | 124 ++++++++++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 6169f99f77..6b3c0dc3a6 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -9,6 +9,7 @@ from .base import ( ModulesManager, TrayModulesManager, + BaseModuleSettingsDef, ModuleSettingsDef, get_module_settings_defs ) @@ -24,6 +25,7 @@ __all__ = ( "ModulesManager", "TrayModulesManager", + "BaseModuleSettingsDef", "ModuleSettingsDef", "get_module_settings_defs" ) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index c8cc911ca6..66f962526f 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -13,8 +13,17 @@ from abc import ABCMeta, abstractmethod import six import openpype -from openpype.settings import get_system_settings -from openpype.settings.lib import get_studio_system_settings_overrides +from openpype.settings import ( + get_system_settings, + SYSTEM_SETTINGS_KEY, + PROJECT_SETTINGS_KEY, + SCHEMA_KEY_SYSTEM_SETTINGS, + SCHEMA_KEY_PROJECT_SETTINGS +) + +from openpype.settings.lib import ( + get_studio_system_settings_overrides, +) from openpype.lib import PypeLogger @@ -1030,7 +1039,7 @@ def get_module_settings_defs(): @six.add_metaclass(ABCMeta) -class ModuleSettingsDef: +class BaseModuleSettingsDef: """Definition of settings for OpenPype module or AddOn.""" _id = None @@ -1091,3 +1100,112 @@ class ModuleSettingsDef: Passed data are by path to first key defined in main schemas. """ pass + + +class ModuleSettingsDef(BaseModuleSettingsDef): + def get_defaults(self, top_key): + """Split method into 2 methods by top key.""" + if top_key == SYSTEM_SETTINGS_KEY: + return self.get_default_system_settings() or {} + elif top_key == PROJECT_SETTINGS_KEY: + return self.get_default_project_settings() or {} + return {} + + def save_defaults(self, top_key, data): + """Split method into 2 methods by top key.""" + if top_key == SYSTEM_SETTINGS_KEY: + self.save_system_defaults(data) + elif top_key == PROJECT_SETTINGS_KEY: + self.save_project_defaults(data) + + def get_settings_schemas(self, schema_type): + """Split method into 2 methods by schema type.""" + if schema_type == SCHEMA_KEY_SYSTEM_SETTINGS: + return self.get_system_settings_schemas() or {} + elif schema_type == SCHEMA_KEY_PROJECT_SETTINGS: + return self.get_project_settings_schemas() or {} + return {} + + def get_dynamic_schemas(self, schema_type): + """Split method into 2 methods by schema type.""" + if schema_type == SCHEMA_KEY_SYSTEM_SETTINGS: + return self.get_system_dynamic_schemas() or {} + elif schema_type == SCHEMA_KEY_PROJECT_SETTINGS: + return self.get_project_dynamic_schemas() or {} + return {} + + @abstractmethod + def get_system_settings_schemas(self): + """Schemas and templates usable in system settings schemas. + + Returns: + dict: Schemas and templates by it's names. Names must be unique + across whole OpenPype. + """ + pass + + @abstractmethod + def get_project_settings_schemas(self): + """Schemas and templates usable in project settings schemas. + + Returns: + dict: Schemas and templates by it's names. Names must be unique + across whole OpenPype. + """ + pass + + @abstractmethod + def get_system_dynamic_schemas(self): + """System schemas by dynamic schema name. + + If dynamic schema name is not available in then schema will not used. + + Returns: + dict: Schemas or list of schemas by dynamic schema name. + """ + pass + + @abstractmethod + def get_project_dynamic_schemas(self): + """Project schemas by dynamic schema name. + + If dynamic schema name is not available in then schema will not used. + + Returns: + dict: Schemas or list of schemas by dynamic schema name. + """ + pass + + @abstractmethod + def get_default_system_settings(self): + """Default system settings values. + + Returns: + dict: Default values by path to first key. + """ + pass + + @abstractmethod + def get_default_project_settings(self): + """Default project settings values. + + Returns: + dict: Default values by path to first key. + """ + pass + + @abstractmethod + def save_system_defaults(self, data): + """Save default system settings values. + + Passed data are by path to first key defined in main schemas. + """ + pass + + @abstractmethod + def save_project_defaults(self, data): + """Save default project settings values. + + Passed data are by path to first key defined in main schemas. + """ + pass From ab7ea51bab5eacfb8ccd6d4c913acb89b115855e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Aug 2021 17:12:35 +0200 Subject: [PATCH 40/43] preimplemented json files settings definition which needs only one method to implement --- openpype/modules/__init__.py | 4 + openpype/modules/base.py | 195 +++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 6b3c0dc3a6..68b5f6c247 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -11,6 +11,8 @@ from .base import ( BaseModuleSettingsDef, ModuleSettingsDef, + JsonFilesSettingsDef, + get_module_settings_defs ) @@ -27,5 +29,7 @@ __all__ = ( "BaseModuleSettingsDef", "ModuleSettingsDef", + "JsonFilesSettingsDef", + "get_module_settings_defs" ) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 66f962526f..a11867ea15 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -2,6 +2,7 @@ """Base class for Pype Modules.""" import os import sys +import json import time import inspect import logging @@ -23,6 +24,7 @@ from openpype.settings import ( from openpype.settings.lib import ( get_studio_system_settings_overrides, + load_json_file ) from openpype.lib import PypeLogger @@ -1103,6 +1105,11 @@ class BaseModuleSettingsDef: class ModuleSettingsDef(BaseModuleSettingsDef): + """Settings definiton with separated system and procect settings parts. + + Reduce conditions that must be checked and adds predefined methods for + each case. + """ def get_defaults(self, top_key): """Split method into 2 methods by top key.""" if top_key == SYSTEM_SETTINGS_KEY: @@ -1209,3 +1216,191 @@ class ModuleSettingsDef(BaseModuleSettingsDef): Passed data are by path to first key defined in main schemas. """ pass + + +class JsonFilesSettingsDef(ModuleSettingsDef): + """Preimplemented settings definition using json files and file structure. + + Expected file structure: + ┕ root + │ + │ # Default values + ┝ defaults + │ ┝ system_settings.json + │ ┕ project_settings.json + │ + │ # Schemas for `dynamic_template` type + ┝ dynamic_schemas + │ ┝ system_dynamic_schemas.json + │ ┕ project_dynamic_schemas.json + │ + │ # Schemas that can be used anywhere (enhancement for `dynamic_schemas`) + ┕ schemas + ┝ system_schemas + │ ┝ # Any schema or template files + │ ┕ ... + ┕ project_schemas + ┝ # Any schema or template files + ┕ ... + + Schemas can be loaded with prefix to avoid duplicated schema/template names + across all OpenPype addons/modules. Prefix can be defined with class + attribute `schema_prefix`. + + Only think which must be implemented in `get_settings_root_dir` which + should return directory path to `root` (in structure graph above). + """ + # Possible way how to define `schemas` prefix + schema_prefix = "" + + @abstractmethod + def get_settings_root_dir(self): + """Directory path where settings and it's schemas are located.""" + pass + + def __init__(self): + settings_root_dir = self.get_settings_root_dir() + defaults_dir = os.path.join( + settings_root_dir, "defaults" + ) + dynamic_schemas_dir = os.path.join( + settings_root_dir, "dynamic_schemas" + ) + schemas_dir = os.path.join( + settings_root_dir, "schemas" + ) + + self.system_defaults_filepath = os.path.join( + defaults_dir, "system_settings.json" + ) + self.project_defaults_filepath = os.path.join( + defaults_dir, "project_settings.json" + ) + + self.system_dynamic_schemas_filepath = os.path.join( + dynamic_schemas_dir, "system_dynamic_schemas.json" + ) + self.project_dynamic_schemas_filepath = os.path.join( + dynamic_schemas_dir, "project_dynamic_schemas.json" + ) + + self.system_schemas_dir = os.path.join( + schemas_dir, "system_schemas" + ) + self.project_schemas_dir = os.path.join( + schemas_dir, "project_schemas" + ) + + def _load_json_file_data(self, path): + if os.path.exists(path): + return load_json_file(path) + return {} + + def get_default_system_settings(self): + """Default system settings values. + + Returns: + dict: Default values by path to first key. + """ + return self._load_json_file_data(self.system_defaults_filepath) + + def get_default_project_settings(self): + """Default project settings values. + + Returns: + dict: Default values by path to first key. + """ + return self._load_json_file_data(self.project_defaults_filepath) + + def _save_data_to_filepath(self, path, data): + dirpath = os.path.dirname(path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + with open(path, "w") as file_stream: + json.dump(data, file_stream, indent=4) + + def save_system_defaults(self, data): + """Save default system settings values. + + Passed data are by path to first key defined in main schemas. + """ + self._save_data_to_filepath(self.system_defaults_filepath, data) + + def save_project_defaults(self, data): + """Save default project settings values. + + Passed data are by path to first key defined in main schemas. + """ + self._save_data_to_filepath(self.project_defaults_filepath, data) + + def get_system_dynamic_schemas(self): + """System schemas by dynamic schema name. + + If dynamic schema name is not available in then schema will not used. + + Returns: + dict: Schemas or list of schemas by dynamic schema name. + """ + return self._load_json_file_data(self.system_dynamic_schemas_filepath) + + def get_project_dynamic_schemas(self): + """Project schemas by dynamic schema name. + + If dynamic schema name is not available in then schema will not used. + + Returns: + dict: Schemas or list of schemas by dynamic schema name. + """ + return self._load_json_file_data(self.project_dynamic_schemas_filepath) + + def _load_files_from_path(self, path): + output = {} + if not path or not os.path.exists(path): + return output + + if os.path.isfile(path): + filename = os.path.basename(path) + basename, ext = os.path.splitext(filename) + if ext == ".json": + if self.schema_prefix: + key = "{}/{}".format(self.schema_prefix, basename) + else: + key = basename + output[key] = self._load_json_file_data(path) + return output + + path = os.path.normpath(path) + for root, _, files in os.walk(path, topdown=False): + for filename in files: + basename, ext = os.path.splitext(filename) + if ext != ".json": + continue + + json_path = os.path.join(root, filename) + store_key = os.path.join( + root.replace(path, ""), basename + ).replace("\\", "/") + if self.schema_prefix: + store_key = "{}/{}".format(self.schema_prefix, store_key) + output[store_key] = self._load_json_file_data(json_path) + + return output + + def get_system_settings_schemas(self): + """Schemas and templates usable in system settings schemas. + + Returns: + dict: Schemas and templates by it's names. Names must be unique + across whole OpenPype. + """ + return self._load_files_from_path(self.system_schemas_dir) + + def get_project_settings_schemas(self): + """Schemas and templates usable in project settings schemas. + + Returns: + dict: Schemas and templates by it's names. Names must be unique + across whole OpenPype. + """ + return self._load_files_from_path(self.project_schemas_dir) From 82f361d48aa041e6b9da4b2c919dc567e2f8aae1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Aug 2021 17:14:20 +0200 Subject: [PATCH 41/43] enable addon by default --- openpype/modules/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index a11867ea15..1fc1d900a0 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -431,7 +431,8 @@ class OpenPypeModule: class OpenPypeAddOn(OpenPypeModule): - pass + # Enable Addon by default + enabled = True class ModulesManager: From 2188e62b694dc53a7a07959ee51ebbf36ef1f3d4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Aug 2021 17:14:33 +0200 Subject: [PATCH 42/43] implement abstract methods required for modules --- openpype/modules/base.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 1fc1d900a0..23ec3b8c6f 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -434,6 +434,14 @@ class OpenPypeAddOn(OpenPypeModule): # Enable Addon by default enabled = True + def initialize(self, module_settings): + """Initialization is not be required for most of addons.""" + pass + + def connect_with_modules(self, enabled_modules): + """Do not require to implement connection with modules for addon.""" + pass + class ModulesManager: """Manager of Pype modules helps to load and prepare them to work. From 5dd6d010609f91647b416d81e38ac282f7de3269 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Aug 2021 17:23:34 +0200 Subject: [PATCH 43/43] changed name of `get_settings_root_dir` to `get_settings_root_path` --- openpype/modules/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 23ec3b8c6f..01c3cebe60 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -1256,19 +1256,19 @@ class JsonFilesSettingsDef(ModuleSettingsDef): across all OpenPype addons/modules. Prefix can be defined with class attribute `schema_prefix`. - Only think which must be implemented in `get_settings_root_dir` which + Only think which must be implemented in `get_settings_root_path` which should return directory path to `root` (in structure graph above). """ # Possible way how to define `schemas` prefix schema_prefix = "" @abstractmethod - def get_settings_root_dir(self): + def get_settings_root_path(self): """Directory path where settings and it's schemas are located.""" pass def __init__(self): - settings_root_dir = self.get_settings_root_dir() + settings_root_dir = self.get_settings_root_path() defaults_dir = os.path.join( settings_root_dir, "defaults" )