diff --git a/pype/settings/handlers.py b/pype/settings/handlers.py index b2381ed4f6..c1829273b1 100644 --- a/pype/settings/handlers.py +++ b/pype/settings/handlers.py @@ -1,5 +1,24 @@ +import os +import json +import logging from abc import ABCMeta, abstractmethod import six +import pype +from .constants import ( + M_OVERRIDEN_KEY, + M_ENVIRONMENT_KEY, + M_DYNAMIC_KEY_LABEL, + M_POP_KEY, + + METADATA_KEYS, + + SYSTEM_SETTINGS_KEY, + ENVIRONMENTS_KEY, + PROJECT_SETTINGS_KEY, + PROJECT_ANATOMY_KEY +) + +JSON_EXC = getattr(json.decoder, "JSONDecodeError", ValueError) @six.add_metaclass(ABCMeta) @@ -87,3 +106,192 @@ class SettingsHandler: """ pass + +class SettingsFileHandler(SettingsHandler): + def __init__(self): + self.log = logging.getLogger("SettingsFileHandler") + + # Folder where studio overrides are stored + studio_overrides_dir = os.getenv("PYPE_PROJECT_CONFIGS") + if not studio_overrides_dir: + studio_overrides_dir = os.path.dirname(os.path.dirname( + os.path.abspath(pype.__file__) + )) + system_settings_path = os.path.join( + studio_overrides_dir, SYSTEM_SETTINGS_KEY + ".json" + ) + + # File where studio's default project overrides are stored + project_settings_filename = PROJECT_SETTINGS_KEY + ".json" + project_settings_path = os.path.join( + studio_overrides_dir, project_settings_filename + ) + + project_anatomy_filename = PROJECT_ANATOMY_KEY + ".json" + project_anatomy_path = os.path.join( + studio_overrides_dir, project_anatomy_filename + ) + + self.studio_overrides_dir = studio_overrides_dir + self.system_settings_path = system_settings_path + + self.project_settings_filename = project_settings_filename + self.project_anatomy_filename = project_anatomy_filename + + self.project_settings_path = project_settings_path + self.project_anatomy_path = project_anatomy_path + + def load_json_file(self, fpath): + # Load json data + try: + with open(fpath, "r") as opened_file: + return json.load(opened_file) + + except JSON_EXC: + self.log.warning( + "File has invalid json format \"{}\"".format(fpath), + exc_info=True + ) + return {} + + def path_to_project_settings(self, project_name): + if not project_name: + return self.project_settings_path + return os.path.join( + self.studio_overrides_dir, + project_name, + self.project_settings_filename + ) + + def path_to_project_anatomy(self, project_name): + if not project_name: + return self.project_anatomy_path + return os.path.join( + self.studio_overrides_dir, + project_name, + self.project_anatomy_filename + ) + + def save_studio_settings(self, data): + """Save studio overrides of system settings. + + Do not use to store whole system settings data with defaults but only + it's overrides with metadata defining how overrides should be applied + in load function. For loading should be used function + `studio_system_settings`. + + Args: + data(dict): Data of studio overrides with override metadata. + """ + dirpath = os.path.dirname(self.system_settings_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + self.log.debug( + "Saving studio overrides. Output path: {}".format( + self.system_settings_path + ) + ) + with open(self.system_settings_path, "w") as file_stream: + json.dump(data, file_stream, indent=4) + + def save_project_settings(self, project_name, overrides): + """Save studio overrides of project settings. + + Data are saved for specific project or as defaults for all projects. + + Do not use to store whole project settings data with defaults but only + it's overrides with metadata defining how overrides should be applied + in load function. For loading should be used function + `get_studio_project_settings_overrides` for global project settings + and `get_project_settings_overrides` for project specific settings. + + Args: + project_name(str, null): Project name for which overrides are + or None for global settings. + data(dict): Data of project overrides with override metadata. + """ + project_overrides_json_path = self.path_to_project_settings( + project_name + ) + dirpath = os.path.dirname(project_overrides_json_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + self.log.debug( + "Saving overrides of project \"{}\". Output path: {}".format( + project_name, project_overrides_json_path + ) + ) + with open(project_overrides_json_path, "w") as file_stream: + json.dump(overrides, file_stream, indent=4) + + def save_project_anatomy(self, project_name, anatomy_data): + """Save studio overrides of project anatomy data. + + Args: + project_name(str, null): Project name for which overrides are + or None for global settings. + data(dict): Data of project overrides with override metadata. + """ + project_anatomy_json_path = self.path_to_project_anatomy(project_name) + dirpath = os.path.dirname(project_anatomy_json_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + self.log.debug( + "Saving anatomy of project \"{}\". Output path: {}".format( + project_name, project_anatomy_json_path + ) + ) + with open(project_anatomy_json_path, "w") as file_stream: + json.dump(anatomy_data, file_stream, indent=4) + + def get_studio_system_settings_overrides(self): + """Studio overrides of system settings.""" + if os.path.exists(self.system_settings_path): + return self.load_json_file(self.system_settings_path) + return {} + + def get_studio_project_settings_overrides(self): + """Studio overrides of default project settings.""" + if os.path.exists(self.project_settings_path): + return self.load_json_file(self.project_settings_path) + return {} + + def get_studio_project_anatomy_overrides(self): + """Studio overrides of default project anatomy data.""" + if os.path.exists(self.project_anatomy_path): + return self.load_json_file(self.project_anatomy_path) + return {} + + def get_project_settings_overrides(self, project_name): + """Studio overrides of project settings for specific project. + + Args: + project_name(str): Name of project for which data should be loaded. + + Returns: + dict: Only overrides for entered project, may be empty dictionary. + """ + path_to_json = self.path_to_project_settings(project_name) + if not os.path.exists(path_to_json): + return {} + return self.load_json_file(path_to_json) + + def get_project_anatomy_overrides(self, project_name): + """Studio overrides of project anatomy for specific project. + + Args: + project_name(str): Name of project for which data should be loaded. + + Returns: + dict: Only overrides for entered project, may be empty dictionary. + """ + if not project_name: + return {} + + path_to_json = self.path_to_project_anatomy(project_name) + if not os.path.exists(path_to_json): + return {} + return self.load_json_file(path_to_json) diff --git a/pype/settings/lib.py b/pype/settings/lib.py index 62febfc1d2..0852c92a24 100644 --- a/pype/settings/lib.py +++ b/pype/settings/lib.py @@ -15,6 +15,8 @@ from .constants import ( PROJECT_SETTINGS_KEY, PROJECT_ANATOMY_KEY ) +from .handlers import ( + SettingsFileHandler ) log = logging.getLogger(__name__) @@ -29,6 +31,9 @@ DEFAULTS_DIR = os.path.join(os.path.dirname(__file__), "defaults") # Variable where cache of default settings are stored _DEFAULT_SETTINGS = None +# Handler of studio overrides +SETTINGS_HANDLER = SettingsFileHandler() + class DuplicatedEnvGroups(Exception): def __init__(self, duplicated): @@ -220,150 +225,6 @@ def subkey_merge(_dict, value, keys): return _dict -def path_to_project_settings(project_name): - if not project_name: - return PROJECT_SETTINGS_PATH - return os.path.join( - STUDIO_OVERRIDES_PATH, - project_name, - PROJECT_SETTINGS_FILENAME - ) - - -def path_to_project_anatomy(project_name): - if not project_name: - return PROJECT_ANATOMY_PATH - return os.path.join( - STUDIO_OVERRIDES_PATH, - project_name, - PROJECT_ANATOMY_FILENAME - ) - - -def save_studio_settings(data): - """Save studio overrides of system settings. - - Do not use to store whole system settings data with defaults but only it's - overrides with metadata defining how overrides should be applied in load - function. For loading should be used function `studio_system_settings`. - - Args: - data(dict): Data of studio overrides with override metadata. - """ - dirpath = os.path.dirname(SYSTEM_SETTINGS_PATH) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - print("Saving studio overrides. Output path: {}".format( - SYSTEM_SETTINGS_PATH - )) - with open(SYSTEM_SETTINGS_PATH, "w") as file_stream: - json.dump(data, file_stream, indent=4) - - -def save_project_settings(project_name, overrides): - """Save studio overrides of project settings. - - Data are saved for specific project or as defaults for all projects. - - Do not use to store whole project settings data with defaults but only it's - overrides with metadata defining how overrides should be applied in load - function. For loading should be used function - `get_studio_project_settings_overrides` for global project settings - and `get_project_settings_overrides` for project specific settings. - - Args: - project_name(str, null): Project name for which overrides are - or None for global settings. - data(dict): Data of project overrides with override metadata. - """ - project_overrides_json_path = path_to_project_settings(project_name) - dirpath = os.path.dirname(project_overrides_json_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - print("Saving overrides of project \"{}\". Output path: {}".format( - project_name, project_overrides_json_path - )) - with open(project_overrides_json_path, "w") as file_stream: - json.dump(overrides, file_stream, indent=4) - - -def save_project_anatomy(project_name, anatomy_data): - """Save studio overrides of project anatomy data. - - Args: - project_name(str, null): Project name for which overrides are - or None for global settings. - data(dict): Data of project overrides with override metadata. - """ - project_anatomy_json_path = path_to_project_anatomy(project_name) - dirpath = os.path.dirname(project_anatomy_json_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - print("Saving anatomy of project \"{}\". Output path: {}".format( - project_name, project_anatomy_json_path - )) - with open(project_anatomy_json_path, "w") as file_stream: - json.dump(anatomy_data, file_stream, indent=4) - - -def get_studio_system_settings_overrides(): - """Studio overrides of system settings.""" - if os.path.exists(SYSTEM_SETTINGS_PATH): - return load_json_file(SYSTEM_SETTINGS_PATH) - return {} - - -def get_studio_project_settings_overrides(): - """Studio overrides of default project settings.""" - if os.path.exists(PROJECT_SETTINGS_PATH): - return load_json_file(PROJECT_SETTINGS_PATH) - return {} - - -def get_studio_project_anatomy_overrides(): - """Studio overrides of default project anatomy data.""" - if os.path.exists(PROJECT_ANATOMY_PATH): - return load_json_file(PROJECT_ANATOMY_PATH) - return {} - - -def get_project_settings_overrides(project_name): - """Studio overrides of project settings for specific project. - - Args: - project_name(str): Name of project for which data should be loaded. - - Returns: - dict: Only overrides for entered project, may be empty dictionary. - """ - - path_to_json = path_to_project_settings(project_name) - if not os.path.exists(path_to_json): - return {} - return load_json_file(path_to_json) - - -def get_project_anatomy_overrides(project_name): - """Studio overrides of project anatomy for specific project. - - Args: - project_name(str): Name of project for which data should be loaded. - - Returns: - dict: Only overrides for entered project, may be empty dictionary. - """ - if not project_name: - return {} - - path_to_json = path_to_project_anatomy(project_name) - if not os.path.exists(path_to_json): - return {} - return load_json_file(path_to_json) - - def merge_overrides(source_dict, override_dict): """Merge data from override_dict to source_dict.""" @@ -397,7 +258,7 @@ def apply_overrides(source_data, override_data): def get_system_settings(clear_metadata=True): """System settings with applied studio overrides.""" default_values = get_default_settings()[SYSTEM_SETTINGS_KEY] - studio_values = get_studio_system_settings_overrides() + studio_values = SETTINGS_HANDLER.get_studio_system_settings_overrides() result = apply_overrides(default_values, studio_values) if clear_metadata: clear_metadata_from_settings(result) @@ -407,7 +268,7 @@ def get_system_settings(clear_metadata=True): def get_default_project_settings(clear_metadata=True): """Project settings with applied studio's default project overrides.""" default_values = get_default_settings()[PROJECT_SETTINGS_KEY] - studio_values = get_studio_project_settings_overrides() + studio_values = SETTINGS_HANDLER.get_studio_project_settings_overrides() result = apply_overrides(default_values, studio_values) if clear_metadata: clear_metadata_from_settings(result) @@ -417,7 +278,7 @@ def get_default_project_settings(clear_metadata=True): def get_default_anatomy_settings(clear_metadata=True): """Project anatomy data with applied studio's default project overrides.""" default_values = get_default_settings()[PROJECT_ANATOMY_KEY] - studio_values = get_studio_project_anatomy_overrides() + studio_values = SETTINGS_HANDLER.get_studio_project_anatomy_overrides() result = apply_overrides(default_values, studio_values) if clear_metadata: clear_metadata_from_settings(result) @@ -433,7 +294,9 @@ def get_anatomy_settings(project_name, clear_metadata=True): ) studio_overrides = get_default_anatomy_settings(False) - project_overrides = get_project_anatomy_overrides(project_name) + project_overrides = SETTINGS_HANDLER.get_project_anatomy_overrides( + project_name + ) result = apply_overrides(studio_overrides, project_overrides) if clear_metadata: @@ -450,7 +313,9 @@ def get_project_settings(project_name, clear_metadata=True): ) studio_overrides = get_default_project_settings(False) - project_overrides = get_project_settings_overrides(project_name) + project_overrides = SETTINGS_HANDLER.get_project_settings_overrides( + project_name + ) result = apply_overrides(studio_overrides, project_overrides) if clear_metadata: diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index ddd814e38e..7c09abad73 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -9,21 +9,11 @@ from pype.settings.constants import ( ) from pype.settings.lib import ( DEFAULTS_DIR, + SETTINGS_HANDLER, reset_default_settings, get_default_settings, - get_studio_system_settings_overrides, - get_studio_project_settings_overrides, - get_studio_project_anatomy_overrides, - - get_project_settings_overrides, - get_project_anatomy_overrides, - - save_studio_settings, - save_project_settings, - save_project_anatomy, - apply_overrides, find_environments, DuplicatedEnvGroups @@ -370,7 +360,7 @@ class SystemWidget(SettingsCategoryWidget): if not self.duplicated_env_group_validation(overrides=values): return - save_studio_settings(values) + SETTINGS_HANDLER.save_studio_settings(values) def update_values(self): default_values = lib.convert_data_to_gui_data({ @@ -382,8 +372,11 @@ class SystemWidget(SettingsCategoryWidget): if self._hide_studio_overrides: system_values = lib.NOT_SET else: + studio_system_overrides = ( + SETTINGS_HANDLER.get_studio_system_settings_overrides() + ) system_values = lib.convert_overrides_to_gui_data( - {self.main_schema_key: get_studio_system_settings_overrides()} + {self.main_schema_key: studio_system_overrides} ) for input_field in self.input_fields: @@ -547,8 +540,12 @@ class ProjectWidget(SettingsCategoryWidget): _project_anatomy = lib.NOT_SET self.is_overidable = False else: - _project_overrides = get_project_settings_overrides(project_name) - _project_anatomy = get_project_anatomy_overrides(project_name) + _project_overrides = ( + SETTINGS_HANDLER.get_project_settings_overrides(project_name) + ) + _project_anatomy = ( + SETTINGS_HANDLER.get_project_anatomy_overrides(project_name) + ) self.is_overidable = True overrides = {self.main_schema_key: { @@ -582,11 +579,15 @@ class ProjectWidget(SettingsCategoryWidget): # Saving overrides data project_overrides_data = output_data.get(PROJECT_SETTINGS_KEY, {}) - save_project_settings(self.project_name, project_overrides_data) + SETTINGS_HANDLER.save_project_settings( + self.project_name, project_overrides_data + ) # Saving anatomy data project_anatomy_data = output_data.get(PROJECT_ANATOMY_KEY, {}) - save_project_anatomy(self.project_name, project_anatomy_data) + SETTINGS_HANDLER.save_project_anatomy( + self.project_name, project_anatomy_data + ) def update_values(self): if self.project_name is not None: @@ -602,14 +603,16 @@ class ProjectWidget(SettingsCategoryWidget): if self._hide_studio_overrides: studio_values = lib.NOT_SET else: + project_settings_value = ( + SETTINGS_HANDLER.get_studio_project_settings_overrides() + ) + project_anatomy_value = ( + SETTINGS_HANDLER.get_studio_project_anatomy_overrides() + ) studio_values = lib.convert_overrides_to_gui_data({ self.main_schema_key: { - PROJECT_SETTINGS_KEY: ( - get_studio_project_settings_overrides() - ), - PROJECT_ANATOMY_KEY: ( - get_studio_project_anatomy_overrides() - ) + PROJECT_SETTINGS_KEY: project_settings_value, + PROJECT_ANATOMY_KEY: project_anatomy_value } })