Merge pull request #1387 from pypeclub/feature/ftrack_settings_changes

Ftrack handling of save settings
This commit is contained in:
Milan Kolar 2021-04-22 10:41:33 +02:00 committed by GitHub
commit 162e11fece
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 442 additions and 130 deletions

View file

@ -263,14 +263,32 @@ class Application:
class ApplicationManager:
def __init__(self):
self.log = PypeLogger().get_logger(self.__class__.__name__)
"""Load applications and tools and store them by their full name.
Args:
system_settings (dict): Preloaded system settings. When passed manager
will always use these values. Gives ability to create manager
using different settings.
"""
def __init__(self, system_settings=None):
self.log = PypeLogger.get_logger(self.__class__.__name__)
self.app_groups = {}
self.applications = {}
self.tool_groups = {}
self.tools = {}
self._system_settings = system_settings
self.refresh()
def set_system_settings(self, system_settings):
"""Ability to change init system settings.
This will trigger refresh of manager.
"""
self._system_settings = system_settings
self.refresh()
def refresh(self):
@ -280,9 +298,12 @@ class ApplicationManager:
self.tool_groups.clear()
self.tools.clear()
settings = get_system_settings(
clear_metadata=False, exclude_locals=False
)
if self._system_settings is not None:
settings = copy.deepcopy(self._system_settings)
else:
settings = get_system_settings(
clear_metadata=False, exclude_locals=False
)
app_defs = settings["applications"]
for group_name, variant_defs in app_defs.items():

View file

@ -123,6 +123,8 @@ class PypeFormatter(logging.Formatter):
if record.exc_info is not None:
line_len = len(str(record.exc_info[1]))
if line_len > 30:
line_len = 30
out = "{}\n{}\n{}\n{}\n{}".format(
out,
line_len * "=",

View file

@ -2,9 +2,9 @@ import json
from openpype.api import ProjectSettings
from openpype.modules.ftrack.lib import ServerAction
from openpype.modules.ftrack.lib.avalon_sync import (
get_pype_attr,
from openpype.modules.ftrack.lib import (
ServerAction,
get_openpype_attr,
CUST_ATTR_AUTO_SYNC
)
@ -159,7 +159,7 @@ class PrepareProjectServer(ServerAction):
for key, entity in project_anatom_settings["attributes"].items():
attribute_values_by_key[key] = entity.value
cust_attrs, hier_cust_attrs = get_pype_attr(self.session, True)
cust_attrs, hier_cust_attrs = get_openpype_attr(self.session, True)
for attr in hier_cust_attrs:
key = attr["key"]

View file

@ -18,12 +18,15 @@ from avalon import schema
from avalon.api import AvalonMongoDB
from openpype.modules.ftrack.lib import (
get_openpype_attr,
CUST_ATTR_ID_KEY,
CUST_ATTR_AUTO_SYNC,
avalon_sync,
BaseEvent
)
from openpype.modules.ftrack.lib.avalon_sync import (
CUST_ATTR_ID_KEY,
CUST_ATTR_AUTO_SYNC,
EntitySchemas
)
@ -125,7 +128,7 @@ class SyncToAvalonEvent(BaseEvent):
@property
def avalon_cust_attrs(self):
if self._avalon_cust_attrs is None:
self._avalon_cust_attrs = avalon_sync.get_pype_attr(
self._avalon_cust_attrs = get_openpype_attr(
self.process_session, query_keys=self.cust_attr_query_keys
)
return self._avalon_cust_attrs

View file

@ -1,7 +1,10 @@
import collections
import ftrack_api
from openpype.modules.ftrack.lib import BaseAction, statics_icon
from openpype.modules.ftrack.lib.avalon_sync import get_pype_attr
from openpype.modules.ftrack.lib import (
BaseAction,
statics_icon,
get_openpype_attr
)
class CleanHierarchicalAttrsAction(BaseAction):
@ -52,7 +55,7 @@ class CleanHierarchicalAttrsAction(BaseAction):
)
entity_ids_joined = ", ".join(all_entities_ids)
attrs, hier_attrs = get_pype_attr(session)
attrs, hier_attrs = get_openpype_attr(session)
for attr in hier_attrs:
configuration_key = attr["key"]

View file

@ -2,10 +2,20 @@ import collections
import json
import arrow
import ftrack_api
from openpype.modules.ftrack.lib import BaseAction, statics_icon
from openpype.modules.ftrack.lib.avalon_sync import (
CUST_ATTR_ID_KEY, CUST_ATTR_GROUP, default_custom_attributes_definition
from openpype.modules.ftrack.lib import (
BaseAction,
statics_icon,
CUST_ATTR_ID_KEY,
CUST_ATTR_GROUP,
CUST_ATTR_TOOLS,
CUST_ATTR_APPLICATIONS,
default_custom_attributes_definition,
app_definitions_from_app_manager,
tool_definitions_from_app_manager
)
from openpype.api import get_system_settings
from openpype.lib import ApplicationManager
@ -370,24 +380,12 @@ class CustomAttributes(BaseAction):
exc_info=True
)
def app_defs_from_app_manager(self):
app_definitions = []
for app_name, app in self.app_manager.applications.items():
if app.enabled and app.is_host:
app_definitions.append({
app_name: app.full_label
})
if not app_definitions:
app_definitions.append({"empty": "< Empty >"})
return app_definitions
def applications_attribute(self, event):
apps_data = self.app_defs_from_app_manager()
apps_data = app_definitions_from_app_manager(self.app_manager)
applications_custom_attr_data = {
"label": "Applications",
"key": "applications",
"key": CUST_ATTR_APPLICATIONS,
"type": "enumerator",
"entity_type": "show",
"group": CUST_ATTR_GROUP,
@ -399,19 +397,11 @@ class CustomAttributes(BaseAction):
self.process_attr_data(applications_custom_attr_data, event)
def tools_attribute(self, event):
tools_data = []
for tool_name, tool in self.app_manager.tools.items():
tools_data.append({
tool_name: tool.label
})
# Make sure there is at least one item
if not tools_data:
tools_data.append({"empty": "< Empty >"})
tools_data = tool_definitions_from_app_manager(self.app_manager)
tools_custom_attr_data = {
"label": "Tools",
"key": "tools_env",
"key": CUST_ATTR_TOOLS,
"type": "enumerator",
"is_hierarchical": True,
"group": CUST_ATTR_GROUP,

View file

@ -4,10 +4,8 @@ from openpype.api import ProjectSettings
from openpype.modules.ftrack.lib import (
BaseAction,
statics_icon
)
from openpype.modules.ftrack.lib.avalon_sync import (
get_pype_attr,
statics_icon,
get_openpype_attr,
CUST_ATTR_AUTO_SYNC
)
@ -162,7 +160,7 @@ class PrepareProjectLocal(BaseAction):
for key, entity in project_anatom_settings["attributes"].items():
attribute_values_by_key[key] = entity.value
cust_attrs, hier_cust_attrs = get_pype_attr(self.session, True)
cust_attrs, hier_cust_attrs = get_openpype_attr(self.session, True)
for attr in hier_cust_attrs:
key = attr["key"]

View file

@ -1,4 +1,5 @@
import os
import json
import collections
from abc import ABCMeta, abstractmethod
import six
@ -11,6 +12,7 @@ from openpype.modules import (
ILaunchHookPaths,
ISettingsChangeListener
)
from openpype.settings import SaveWarningExc
FTRACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
@ -121,10 +123,86 @@ class FtrackModule(
if self.tray_module:
self.tray_module.stop_timer_manager()
def on_system_settings_save(self, *_args, **_kwargs):
def on_system_settings_save(
self, old_value, new_value, changes, new_value_metadata
):
"""Implementation of ISettingsChangeListener interface."""
# Ignore
return
try:
session = self.create_ftrack_session()
except Exception:
self.log.warning("Couldn't create ftrack session.", exc_info=True)
raise SaveWarningExc((
"Saving of attributes to ftrack wasn't successful,"
" try running Create/Update Avalon Attributes in ftrack."
))
from .lib import (
get_openpype_attr,
CUST_ATTR_APPLICATIONS,
CUST_ATTR_TOOLS,
app_definitions_from_app_manager,
tool_definitions_from_app_manager
)
from openpype.api import ApplicationManager
query_keys = [
"id",
"key",
"config"
]
custom_attributes = get_openpype_attr(
session,
split_hierarchical=False,
query_keys=query_keys
)
app_attribute = None
tool_attribute = None
for custom_attribute in custom_attributes:
key = custom_attribute["key"]
if key == CUST_ATTR_APPLICATIONS:
app_attribute = custom_attribute
elif key == CUST_ATTR_TOOLS:
tool_attribute = custom_attribute
app_manager = ApplicationManager(new_value_metadata)
missing_attributes = []
if not app_attribute:
missing_attributes.append(CUST_ATTR_APPLICATIONS)
else:
config = json.loads(app_attribute["config"])
new_data = app_definitions_from_app_manager(app_manager)
prepared_data = []
for item in new_data:
for key, label in item.items():
prepared_data.append({
"menu": label,
"value": key
})
config["data"] = json.dumps(prepared_data)
app_attribute["config"] = json.dumps(config)
if not tool_attribute:
missing_attributes.append(CUST_ATTR_TOOLS)
else:
config = json.loads(tool_attribute["config"])
new_data = tool_definitions_from_app_manager(app_manager)
prepared_data = []
for item in new_data:
for key, label in item.items():
prepared_data.append({
"menu": label,
"value": key
})
config["data"] = json.dumps(prepared_data)
tool_attribute["config"] = json.dumps(config)
session.commit()
if missing_attributes:
raise SaveWarningExc((
"Couldn't find custom attribute/s ({}) to update."
" Try running Create/Update Avalon Attributes in ftrack."
).format(", ".join(missing_attributes)))
def on_project_settings_save(self, *_args, **_kwargs):
"""Implementation of ISettingsChangeListener interface."""
@ -132,7 +210,7 @@ class FtrackModule(
return
def on_project_anatomy_save(
self, old_value, new_value, changes, project_name
self, old_value, new_value, changes, project_name, new_value_metadata
):
"""Implementation of ISettingsChangeListener interface."""
if not project_name:
@ -143,32 +221,49 @@ class FtrackModule(
return
import ftrack_api
from openpype.modules.ftrack.lib import avalon_sync
from openpype.modules.ftrack.lib import get_openpype_attr
try:
session = self.create_ftrack_session()
except Exception:
self.log.warning("Couldn't create ftrack session.", exc_info=True)
raise SaveWarningExc((
"Saving of attributes to ftrack wasn't successful,"
" try running Create/Update Avalon Attributes in ftrack."
))
session = self.create_ftrack_session()
project_entity = session.query(
"Project where full_name is \"{}\"".format(project_name)
).first()
if not project_entity:
self.log.warning((
"Ftrack project with names \"{}\" was not found."
" Skipping settings attributes change callback."
))
return
msg = (
"Ftrack project with name \"{}\" was not found in Ftrack."
" Can't push attribute changes."
).format(project_name)
self.log.warning(msg)
raise SaveWarningExc(msg)
project_id = project_entity["id"]
cust_attr, hier_attr = avalon_sync.get_pype_attr(session)
cust_attr, hier_attr = get_openpype_attr(session)
cust_attr_by_key = {attr["key"]: attr for attr in cust_attr}
hier_attrs_by_key = {attr["key"]: attr for attr in hier_attr}
failed = {}
missing = {}
for key, value in attributes_changes.items():
configuration = hier_attrs_by_key.get(key)
if not configuration:
configuration = cust_attr_by_key.get(key)
if not configuration:
self.log.warning(
"Custom attribute \"{}\" was not found.".format(key)
)
missing[key] = value
continue
# TODO add add permissions check
# TODO add value validations
# - value type and list items
entity_key = collections.OrderedDict()
@ -182,10 +277,45 @@ class FtrackModule(
"value",
ftrack_api.symbol.NOT_SET,
value
)
)
session.commit()
try:
session.commit()
self.log.debug(
"Changed project custom attribute \"{}\" to \"{}\"".format(
key, value
)
)
except Exception:
self.log.warning(
"Failed to set \"{}\" to \"{}\"".format(key, value),
exc_info=True
)
session.rollback()
failed[key] = value
if not failed and not missing:
return
error_msg = (
"Values were not updated on Ftrack which may cause issues."
" try running Create/Update Avalon Attributes in ftrack "
" and resave project settings."
)
if missing:
error_msg += "\nMissing Custom attributes on Ftrack: {}.".format(
", ".join([
'"{}"'.format(key)
for key in missing.keys()
])
)
if failed:
joined_failed = ", ".join([
'"{}": "{}"'.format(key, value)
for key, value in failed.items()
])
error_msg += "\nFailed to set: {}".format(joined_failed)
raise SaveWarningExc(error_msg)
def create_ftrack_session(self, **session_kwargs):
import ftrack_api

View file

@ -1,7 +1,21 @@
from .constants import (
CUST_ATTR_ID_KEY,
CUST_ATTR_AUTO_SYNC,
CUST_ATTR_GROUP,
CUST_ATTR_TOOLS,
CUST_ATTR_APPLICATIONS
)
from . settings import (
get_ftrack_url_from_settings,
get_ftrack_event_mongo_info
)
from .custom_attributes import (
default_custom_attributes_definition,
app_definitions_from_app_manager,
tool_definitions_from_app_manager,
get_openpype_attr
)
from . import avalon_sync
from . import credentials
from .ftrack_base_handler import BaseHandler
@ -10,9 +24,20 @@ from .ftrack_action_handler import BaseAction, ServerAction, statics_icon
__all__ = (
"CUST_ATTR_ID_KEY",
"CUST_ATTR_AUTO_SYNC",
"CUST_ATTR_GROUP",
"CUST_ATTR_TOOLS",
"CUST_ATTR_APPLICATIONS",
"get_ftrack_url_from_settings",
"get_ftrack_event_mongo_info",
"default_custom_attributes_definition",
"app_definitions_from_app_manager",
"tool_definitions_from_app_manager",
"get_openpype_attr",
"avalon_sync",
"credentials",

View file

@ -14,17 +14,21 @@ else:
from avalon.api import AvalonMongoDB
import avalon
from openpype.api import (
Logger,
Anatomy,
get_anatomy_settings
)
from openpype.lib import ApplicationManager
from .constants import CUST_ATTR_ID_KEY
from .custom_attributes import get_openpype_attr
from bson.objectid import ObjectId
from bson.errors import InvalidId
from pymongo import UpdateOne
import ftrack_api
from openpype.lib import ApplicationManager
log = Logger.get_logger(__name__)
@ -36,23 +40,6 @@ EntitySchemas = {
"config": "openpype:config-2.0"
}
# Group name of custom attributes
CUST_ATTR_GROUP = "openpype"
# name of Custom attribute that stores mongo_id from avalon db
CUST_ATTR_ID_KEY = "avalon_mongo_id"
CUST_ATTR_AUTO_SYNC = "avalon_auto_sync"
def default_custom_attributes_definition():
json_file_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"custom_attributes.json"
)
with open(json_file_path, "r") as json_stream:
data = json.load(json_stream)
return data
def check_regex(name, entity_type, in_schema=None, schema_patterns=None):
schema_name = "asset-3.0"
@ -91,39 +78,6 @@ def join_query_keys(keys):
return ",".join(["\"{}\"".format(key) for key in keys])
def get_pype_attr(session, split_hierarchical=True, query_keys=None):
custom_attributes = []
hier_custom_attributes = []
if not query_keys:
query_keys = [
"id",
"entity_type",
"object_type_id",
"is_hierarchical",
"default"
]
# TODO remove deprecated "pype" group from query
cust_attrs_query = (
"select {}"
" from CustomAttributeConfiguration"
# Kept `pype` for Backwards Compatiblity
" where group.name in (\"pype\", \"{}\")"
).format(", ".join(query_keys), CUST_ATTR_GROUP)
all_avalon_attr = session.query(cust_attrs_query).all()
for cust_attr in all_avalon_attr:
if split_hierarchical and cust_attr["is_hierarchical"]:
hier_custom_attributes.append(cust_attr)
continue
custom_attributes.append(cust_attr)
if split_hierarchical:
# return tuple
return custom_attributes, hier_custom_attributes
return custom_attributes
def get_python_type_for_custom_attribute(cust_attr, cust_attr_type_name=None):
"""Python type that should value of custom attribute have.
@ -921,7 +875,7 @@ class SyncEntitiesFactory:
def set_cutom_attributes(self):
self.log.debug("* Preparing custom attributes")
# Get custom attributes and values
custom_attrs, hier_attrs = get_pype_attr(
custom_attrs, hier_attrs = get_openpype_attr(
self.session, query_keys=self.cust_attr_query_keys
)
ent_types = self.session.query("select id, name from ObjectType").all()
@ -2508,7 +2462,7 @@ class SyncEntitiesFactory:
if new_entity_id not in p_chilren:
self.entities_dict[parent_id]["children"].append(new_entity_id)
cust_attr, _ = get_pype_attr(self.session)
cust_attr, _ = get_openpype_attr(self.session)
for _attr in cust_attr:
key = _attr["key"]
if key not in av_entity["data"]:

View file

@ -0,0 +1,12 @@
# Group name of custom attributes
CUST_ATTR_GROUP = "openpype"
# name of Custom attribute that stores mongo_id from avalon db
CUST_ATTR_ID_KEY = "avalon_mongo_id"
# Auto sync of project
CUST_ATTR_AUTO_SYNC = "avalon_auto_sync"
# Applications custom attribute name
CUST_ATTR_APPLICATIONS = "applications"
# Environment tools custom attribute
CUST_ATTR_TOOLS = "tools_env"

View file

@ -0,0 +1,73 @@
import os
import json
from .constants import CUST_ATTR_GROUP
def default_custom_attributes_definition():
json_file_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"custom_attributes.json"
)
with open(json_file_path, "r") as json_stream:
data = json.load(json_stream)
return data
def app_definitions_from_app_manager(app_manager):
app_definitions = []
for app_name, app in app_manager.applications.items():
if app.enabled and app.is_host:
app_definitions.append({
app_name: app.full_label
})
if not app_definitions:
app_definitions.append({"empty": "< Empty >"})
return app_definitions
def tool_definitions_from_app_manager(app_manager):
tools_data = []
for tool_name, tool in app_manager.tools.items():
tools_data.append({
tool_name: tool.label
})
# Make sure there is at least one item
if not tools_data:
tools_data.append({"empty": "< Empty >"})
return tools_data
def get_openpype_attr(session, split_hierarchical=True, query_keys=None):
custom_attributes = []
hier_custom_attributes = []
if not query_keys:
query_keys = [
"id",
"entity_type",
"object_type_id",
"is_hierarchical",
"default"
]
# TODO remove deprecated "pype" group from query
cust_attrs_query = (
"select {}"
" from CustomAttributeConfiguration"
# Kept `pype` for Backwards Compatiblity
" where group.name in (\"pype\", \"{}\")"
).format(", ".join(query_keys), CUST_ATTR_GROUP)
all_avalon_attr = session.query(cust_attrs_query).all()
for cust_attr in all_avalon_attr:
if split_hierarchical and cust_attr["is_hierarchical"]:
hier_custom_attributes.append(cust_attr)
continue
custom_attributes.append(cust_attr)
if split_hierarchical:
# return tuple
return custom_attributes, hier_custom_attributes
return custom_attributes

View file

@ -16,18 +16,20 @@ class ISettingsChangeListener:
}
"""
@abstractmethod
def on_system_settings_save(self, old_value, new_value, changes):
def on_system_settings_save(
self, old_value, new_value, changes, new_value_metadata
):
pass
@abstractmethod
def on_project_settings_save(
self, old_value, new_value, changes, project_name
self, old_value, new_value, changes, project_name, new_value_metadata
):
pass
@abstractmethod
def on_project_anatomy_save(
self, old_value, new_value, changes, project_name
self, old_value, new_value, changes, project_name, new_value_metadata
):
pass

View file

@ -1,3 +1,6 @@
from .exceptions import (
SaveWarningExc
)
from .lib import (
get_system_settings,
get_project_settings,
@ -13,6 +16,8 @@ from .entities import (
__all__ = (
"SaveWarningExc",
"get_system_settings",
"get_project_settings",
"get_current_project_settings",

View file

@ -23,6 +23,7 @@ from openpype.settings.constants import (
PROJECT_ANATOMY_KEY,
KEY_REGEX
)
from openpype.settings.exceptions import SaveWarningExc
from openpype.settings.lib import (
DEFAULTS_DIR,
@ -724,8 +725,19 @@ class ProjectSettings(RootEntity):
project_settings = settings_value.get(PROJECT_SETTINGS_KEY) or {}
project_anatomy = settings_value.get(PROJECT_ANATOMY_KEY) or {}
save_project_settings(self.project_name, project_settings)
save_project_anatomy(self.project_name, project_anatomy)
warnings = []
try:
save_project_settings(self.project_name, project_settings)
except SaveWarningExc as exc:
warnings.extend(exc.warnings)
try:
save_project_anatomy(self.project_name, project_anatomy)
except SaveWarningExc as exc:
warnings.extend(exc.warnings)
if warnings:
raise SaveWarningExc(warnings)
def _validate_defaults_to_save(self, value):
"""Valiations of default values before save."""

View file

@ -0,0 +1,11 @@
class SaveSettingsValidation(Exception):
pass
class SaveWarningExc(SaveSettingsValidation):
def __init__(self, warnings):
if isinstance(warnings, str):
warnings = [warnings]
self.warnings = warnings
msg = " | ".join(warnings)
super(SaveWarningExc, self).__init__(msg)

View file

@ -4,6 +4,9 @@ import functools
import logging
import platform
import copy
from .exceptions import (
SaveWarningExc
)
from .constants import (
M_OVERRIDEN_KEY,
M_ENVIRONMENT_KEY,
@ -101,8 +104,14 @@ def save_studio_settings(data):
For saving of data cares registered Settings handler.
Warning messages are not logged as module raising them should log it within
it's logger.
Args:
data(dict): Overrides data with metadata defying studio overrides.
Raises:
SaveWarningExc: If any module raises the exception.
"""
# Notify Pype modules
from openpype.modules import ModulesManager, ISettingsChangeListener
@ -110,15 +119,25 @@ def save_studio_settings(data):
old_data = get_system_settings()
default_values = get_default_settings()[SYSTEM_SETTINGS_KEY]
new_data = apply_overrides(default_values, copy.deepcopy(data))
new_data_with_metadata = copy.deepcopy(new_data)
clear_metadata_from_settings(new_data)
changes = calculate_changes(old_data, new_data)
modules_manager = ModulesManager(_system_settings=new_data)
warnings = []
for module in modules_manager.get_enabled_modules():
if isinstance(module, ISettingsChangeListener):
module.on_system_settings_save(old_data, new_data, changes)
try:
module.on_system_settings_save(
old_data, new_data, changes, new_data_with_metadata
)
except SaveWarningExc as exc:
warnings.extend(exc.warnings)
return _SETTINGS_HANDLER.save_studio_settings(data)
_SETTINGS_HANDLER.save_studio_settings(data)
if warnings:
raise SaveWarningExc(warnings)
@require_handler
@ -130,10 +149,16 @@ def save_project_settings(project_name, overrides):
For saving of data cares registered Settings handler.
Warning messages are not logged as module raising them should log it within
it's logger.
Args:
project_name (str): Project name for which overrides are passed.
Default project's value is None.
overrides(dict): Overrides data with metadata defying studio overrides.
Raises:
SaveWarningExc: If any module raises the exception.
"""
# Notify Pype modules
from openpype.modules import ModulesManager, ISettingsChangeListener
@ -151,17 +176,29 @@ def save_project_settings(project_name, overrides):
old_data = get_default_project_settings(exclude_locals=True)
new_data = apply_overrides(default_values, copy.deepcopy(overrides))
new_data_with_metadata = copy.deepcopy(new_data)
clear_metadata_from_settings(new_data)
changes = calculate_changes(old_data, new_data)
modules_manager = ModulesManager()
warnings = []
for module in modules_manager.get_enabled_modules():
if isinstance(module, ISettingsChangeListener):
module.on_project_settings_save(
old_data, new_data, project_name, changes
)
try:
module.on_project_settings_save(
old_data,
new_data,
project_name,
changes,
new_data_with_metadata
)
except SaveWarningExc as exc:
warnings.extend(exc.warnings)
return _SETTINGS_HANDLER.save_project_settings(project_name, overrides)
_SETTINGS_HANDLER.save_project_settings(project_name, overrides)
if warnings:
raise SaveWarningExc(warnings)
@require_handler
@ -173,10 +210,16 @@ def save_project_anatomy(project_name, anatomy_data):
For saving of data cares registered Settings handler.
Warning messages are not logged as module raising them should log it within
it's logger.
Args:
project_name (str): Project name for which overrides are passed.
Default project's value is None.
overrides(dict): Overrides data with metadata defying studio overrides.
Raises:
SaveWarningExc: If any module raises the exception.
"""
# Notify Pype modules
from openpype.modules import ModulesManager, ISettingsChangeListener
@ -194,17 +237,29 @@ def save_project_anatomy(project_name, anatomy_data):
old_data = get_default_anatomy_settings(exclude_locals=True)
new_data = apply_overrides(default_values, copy.deepcopy(anatomy_data))
new_data_with_metadata = copy.deepcopy(new_data)
clear_metadata_from_settings(new_data)
changes = calculate_changes(old_data, new_data)
modules_manager = ModulesManager()
warnings = []
for module in modules_manager.get_enabled_modules():
if isinstance(module, ISettingsChangeListener):
module.on_project_anatomy_save(
old_data, new_data, changes, project_name
)
try:
module.on_project_anatomy_save(
old_data,
new_data,
changes,
project_name,
new_data_with_metadata
)
except SaveWarningExc as exc:
warnings.extend(exc.warnings)
return _SETTINGS_HANDLER.save_project_anatomy(project_name, anatomy_data)
_SETTINGS_HANDLER.save_project_anatomy(project_name, anatomy_data)
if warnings:
raise SaveWarningExc(warnings)
@require_handler

View file

@ -27,7 +27,7 @@ from openpype.settings.entities import (
SchemaError
)
from openpype.settings.lib import get_system_settings
from openpype.settings import SaveWarningExc
from .widgets import ProjectListWidget
from . import lib
@ -272,6 +272,22 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
# not required.
self.reset()
except SaveWarningExc as exc:
warnings = [
"<b>Settings were saved but few issues happened.</b>"
]
for item in exc.warnings:
warnings.append(item.replace("\n", "<br>"))
msg = "<br><br>".join(warnings)
dialog = QtWidgets.QMessageBox(self)
dialog.setText(msg)
dialog.setIcon(QtWidgets.QMessageBox.Warning)
dialog.exec_()
self.reset()
except Exception as exc:
formatted_traceback = traceback.format_exception(*sys.exc_info())
dialog = QtWidgets.QMessageBox(self)